diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 43e600fb..e9f5d2f4 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -66,22 +66,34 @@ add_subdirectory( rtigo10 ) # Also homogeneous volume scattering is implemented in this example the same way as inside the MDL_renderer. (See scene_rtigo12_*.txt files for examples.) add_subdirectory( rtigo12 ) -# Based on rtigo9, but replacing all material handling with MDL materials. -# The MDL SDK is used to generate the minimal amount of direct callable programs per MDL material shader. -# The closesthit and anyhit programs inside hit.cu call the valid direct callable programs to handle the input values -# from MDL expressions and the sampling and evaluation of the material's distribution functions. if (MDL_SDK_FOUND) + # Based on rtigo9, but replacing all material handling with MDL materials. + # The MDL SDK is used to generate the minimal amount of direct callable programs per MDL material shader. + # The closesthit and anyhit programs inside hit.cu call the valid direct callable programs to handle the input values + # from MDL expressions and the sampling and evaluation of the material's distribution functions. add_subdirectory( MDL_renderer ) + # Based on MDL_renderer, but adding support for a custom Signed-Distance-Field (SDF) primitive + # which is defined by a box inside which a SDF as 3D texture is sampled. + # The 3D texture data can either be single-component fp16 (*.bin) or fp32 (any other extension than *.bin) formats. + # Supports any MDL material without emission or cutout-opacity. + add_subdirectory( MDL_sdf ) else() - message("WARNING: MDL_renderer requires the MDL_SDK. Set the MDL_SDK_PATH variable. Example excluded from build.") + message("WARNING: MDL_renderer and MDL_sdf require the MDL_SDK. Set the MDL_SDK_PATH variable. Examples excluded from build.") endif() # Note that the GLTF_renderer/CMakeLists.txt is now a standalone solution! -# That's because it is an example for using the native CMake LANGUAGES CUDA feature and -# a CMake Object Library for the OptiX device code translation. +# That's because it is an example for using the native CMake LANGUAGE CUDA feature and +# a CMake "Object Library" for the OptiX device code translation. # This allows running native CUDA kernels with the CUDA Runtime API chevron <<<>>> operator # which is used for the expensive skinning animations so far. # Unfortunately that CMake LANGUAGE CUDA feature affects all projects inside a solution and # would break the build for all examples using the custom build rules for the OptiX device code *.cu files. message("The GLTF_renderer example is a standalone solution now! Details inside the README.md files.") + +# Derived from nvlink_shared but adjusted to allow dynamic control about the resource sharing via the new system option peerToPeer bitfield. +# Allows selective sharing of resources via NVLINK or PCI-E (new), texture data, GAS and vertex attribute data, HDR environment CDFs. +add_subdirectory( bench_shared ) + +# Same as bench_shared without any window or OpenGL handling. +add_subdirectory( bench_shared_offscreen ) diff --git a/apps/MDL_sdf/CMakeLists.txt b/apps/MDL_sdf/CMakeLists.txt new file mode 100644 index 00000000..299d4d65 --- /dev/null +++ b/apps/MDL_sdf/CMakeLists.txt @@ -0,0 +1,298 @@ +# Copyright (c) 2013-2022, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +# FindCUDA.cmake is deprecated since CMake 3.10. +# Use FindCUDAToolkit.cmake added in CMake 3.17 instead. +cmake_minimum_required(VERSION 3.17) + +project( MDL_sdf ) +message("\nPROJECT_NAME = " "${PROJECT_NAME}") + +find_package(OpenGL REQUIRED) +find_package(GLFW REQUIRED) +find_package(GLEW REQUIRED) +find_package(CUDAToolkit 10.0 REQUIRED) +find_package(DevIL_1_8_0 REQUIRED) +find_package(ASSIMP REQUIRED) + +if(MDL_SDK_FOUND) + message("MDL_SDK_INCLUDE_DIRS = " "${MDL_SDK_INCLUDE_DIRS}") +else() + message(FATAL_ERROR "MDL SDK not found.") +endif() + +# OptiX SDK 7.x and 8.x versions are searched inside the top-level CMakeLists.txt. +# Make the build work with all currently released OptiX SDK 7.x and 8.x versions. +if(OptiX80_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX80_INCLUDE_DIR}") +elseif(OptiX77_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX77_INCLUDE_DIR}") +elseif(OptiX76_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX76_INCLUDE_DIR}") +elseif(OptiX75_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX75_INCLUDE_DIR}") +elseif(OptiX74_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX74_INCLUDE_DIR}") +elseif(OptiX73_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX73_INCLUDE_DIR}") +elseif(OptiX72_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX72_INCLUDE_DIR}") +elseif(OptiX71_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX71_INCLUDE_DIR}") +elseif(OptiX70_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX70_INCLUDE_DIR}") +else() + message(FATAL_ERROR "No OptiX SDK 8.x or 7.x found.") +endif() +message("OPTIX_INCLUDE_DIR = " "${OPTIX_INCLUDE_DIR}") + +# OptiX SDK 7.5.0 and CUDA 11.7 added support for a new OptiX IR target, which is a binary intermediate format for the module input. +# The default module build target is PTX. +set(USE_OPTIX_IR FALSE) +set(OPTIX_MODULE_EXTENSION ".ptx") +set(OPTIX_PROGRAM_TARGET "--ptx") + +if (OptiX80_FOUND OR OptiX77_FOUND OR OptiX76_FOUND OR OptiX75_FOUND) + # Define USE_OPTIX_IR and change the target to OptiX IR if the combination of OptiX SDK and CUDA Toolkit versions supports this mode. + if ((${CUDAToolkit_VERSION_MAJOR} GREATER 11) OR ((${CUDAToolkit_VERSION_MAJOR} EQUAL 11) AND (${CUDAToolkit_VERSION_MINOR} GREATER_EQUAL 7))) + set(USE_OPTIX_IR TRUE) + set(OPTIX_MODULE_EXTENSION ".optixir") + set(OPTIX_PROGRAM_TARGET "--optix-ir") + endif() +endif() + +set( IMGUI + imgui/imconfig.h + imgui/imgui.h + imgui/imgui_impl_glfw_gl3.h + imgui/imgui_internal.h + imgui/stb_rect_pack.h + imgui/stb_textedit.h + imgui/stb_truetype.h + imgui/imgui.cpp + imgui/imgui_demo.cpp + imgui/imgui_draw.cpp + imgui/imgui_impl_glfw_gl3.cpp +) + +# Reusing some routines from the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline +# Not built as a library, just using classes and functions directly. +# Asserts replaced with my own versions. +set( NVPRO_MATH + # Math functions: + dp/math/Config.h + dp/math/math.h + dp/math/Matmnt.h + dp/math/Quatt.h + dp/math/Trafo.h + dp/math/Vecnt.h + dp/math/src/Math.cpp + dp/math/src/Matmnt.cpp + dp/math/src/Quatt.cpp + dp/math/src/Trafo.cpp +) + +set( HEADERS + inc/Application.h + inc/Arena.h + inc/Camera.h + inc/CheckMacros.h + inc/CompileResult.h + inc/Device.h + inc/Hair.h + inc/LightGUI.h + inc/LoaderIES.h + inc/MaterialMDL.h + inc/MyAssert.h + inc/NVMLImpl.h + inc/Options.h + inc/Parser.h + inc/Picture.h + inc/Rasterizer.h + inc/Raytracer.h + inc/SceneGraph.h + inc/Texture.h + inc/Timer.h + inc/TonemapperGUI.h + inc/ShaderConfiguration.h +) + +set( SOURCES + src/Application.cpp + src/Arena.cpp + src/Assimp.cpp + src/Box.cpp + src/Camera.cpp + src/Curves.cpp + src/Device.cpp + src/Hair.cpp + src/LoaderIES.cpp + src/main.cpp + src/MaterialMDL.cpp + src/NVMLImpl.cpp + src/Options.cpp + src/Parser.cpp + src/Picture.cpp + src/Plane.cpp + src/Rasterizer.cpp + src/Raytracer.cpp + src/SceneGraph.cpp + src/SignedDistanceField.cpp + src/Sphere.cpp + src/Texture.cpp + src/Timer.cpp + src/Torus.cpp +) + +# Prefix the shaders with the full path name to allow stepping through errors with F8. +set( SHADERS + # Core shaders. + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/hit.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/intersection.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/exception.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/miss.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/raygeneration.cu + + # Direct callables + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/lens_shader.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/light_sample.cu +) + +set( KERNELS + # Native CUDA kernels + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/compositor.cu +) + +set( SHADERS_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/camera_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/compositor_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/config.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/curve.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/curve_attributes.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/function_indices.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/half_common.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/light_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/material_definition_mdl.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/per_ray_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/random_number_generators.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/sdf_attributes.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shader_common.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shader_configuration.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/system_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/transform.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/texture_handler.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/texture_lookup.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vector_math.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vertex_attributes.h +) + +# When using OptiX SDK 7.5.0 and CUDA 11.7 or higher, the modules can either be built from OptiX IR input or from PTX input. +# OPTIX_PROGRAM_TARGET and OPTIX_MODULE_EXTENSION switch the NVCC compilation between the two options. +NVCUDA_COMPILE_MODULE( + SOURCES ${SHADERS} + DEPENDENCIES ${SHADERS_HEADERS} + TARGET_PATH "${MODULE_TARGET_DIR}/MDL_sdf_core" + EXTENSION "${OPTIX_MODULE_EXTENSION}" + GENERATED_FILES PROGRAM_MODULES + NVCC_OPTIONS "${OPTIX_PROGRAM_TARGET}" "--machine=64" "--gpu-architecture=compute_50" "--use_fast_math" "--relocatable-device-code=true" "--generate-line-info" "-Wno-deprecated-gpu-targets" "--allow-unsupported-compiler" "-I${OPTIX_INCLUDE_DIR}" "-I${MDL_SDK_INCLUDE_DIRS}" "-I${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +# The native CUDA Kernels will be translated to *.ptx unconditionally. +NVCUDA_COMPILE_MODULE( + SOURCES ${KERNELS} + DEPENDENCIES ${SHADERS_HEADERS} + TARGET_PATH "${MODULE_TARGET_DIR}/MDL_sdf_core" + EXTENSION ".ptx" + GENERATED_FILES KERNEL_MODULES + NVCC_OPTIONS "--ptx" "--machine=64" "--gpu-architecture=compute_50" "--use_fast_math" "--relocatable-device-code=true" "--generate-line-info" "-Wno-deprecated-gpu-targets" "--allow-unsupported-compiler" "-I${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +source_group( "imgui" FILES ${IMGUI} ) +source_group( "nvpro_math" FILES ${NVPRO_MATH} ) +source_group( "headers" FILES ${HEADERS} ) +source_group( "sources" FILES ${SOURCES} ) +source_group( "shaders" FILES ${SHADERS} ) +source_group( "shaders_headers" FILES ${SHADERS_HEADERS} ) +source_group( "prg" FILES ${PROGRAM_MODULES} ) +source_group( "kernels" FILES ${KERNELS} ) +source_group( "ptx" FILES ${KERNEL_MODULES} ) + +include_directories( + "." + "inc" + "imgui" + ${GLEW_INCLUDE_DIRS} + ${GLFW_INCLUDE_DIR} + ${OPTIX_INCLUDE_DIR} + ${CUDAToolkit_INCLUDE_DIRS} + ${IL_INCLUDE_DIR} + ${ASSIMP_INCLUDE_DIRS} + ${MDL_SDK_INCLUDE_DIRS} +) + +add_definitions( + # Disable warnings for file operations fopen etc. + "-D_CRT_SECURE_NO_WARNINGS" + # DAR HACK Set this when building against the MDL SDK open-source version. + # There is some case inside the load_plugin() function which special cases the nv_freeimage.dll loading, but that is actually not reached. + "-DMDL_SOURCE_RELEASE" +) + +if(USE_OPTIX_IR) +add_definitions( + # This define switches the OptiX program module filenames to either *.optixir or *.ptx extensions at compile time. + "-DUSE_OPTIX_IR" +) +endif() + +add_executable( MDL_sdf + ${IMGUI} + ${NVPRO_MATH} + ${HEADERS} + ${SOURCES} + ${SHADERS_HEADERS} + ${SHADERS} + ${PROGRAM_MODULES} + ${KERNEL_MODULES} +) + +target_link_libraries( MDL_sdf + OpenGL::GL + ${GLEW_LIBRARIES} + ${GLFW_LIBRARIES} + CUDA::cuda_driver + ${IL_LIBRARIES} + ${ILU_LIBRARIES} + ${ILUT_LIBRARIES} + ${ASSIMP_LIBRARIES} +) + +if (UNIX) + target_link_libraries( MDL_sdf dl ) +endif() + +set_target_properties( MDL_sdf PROPERTIES FOLDER "apps") + diff --git a/apps/MDL_sdf/dp/math/Config.h b/apps/MDL_sdf/dp/math/Config.h new file mode 100644 index 00000000..f111f297 --- /dev/null +++ b/apps/MDL_sdf/dp/math/Config.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once + +// #include + +//#ifdef DP_OS_WINDOWS +//// microsoft specific storage-class defines +//# ifdef DP_MATH_EXPORTS +//# define DP_MATH_API __declspec(dllexport) +//# else +//# define DP_MATH_API __declspec(dllimport) +//# endif +//#else +// No need for a library, just use the dp::math functions as inline code. +# define DP_MATH_API +//#endif diff --git a/apps/MDL_sdf/dp/math/Matmnt.h b/apps/MDL_sdf/dp/math/Matmnt.h new file mode 100644 index 00000000..6ecde911 --- /dev/null +++ b/apps/MDL_sdf/dp/math/Matmnt.h @@ -0,0 +1,1434 @@ +// Copyright (c) 2009-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include + +namespace dp +{ + namespace math + { + + template class Quatt; + + /*! \brief Matrix class of fixed size and type. + * \remarks This class is templated by size and type. It holds \a m rows times \a n columns values of type \a + * T. There are typedefs for the most common usage with 3x3 and 4x4 values of type \c float and \c + * + * The layout in memory is is row-major. + * Vectors have to be multiplied from the left (result = v*M). + * The last row [12-14] contains the translation. + * + * double: Mat33f, Mat33d, Mat44f, Mat44d. */ + template class Matmnt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons, no initialization is performed. */ + Matmnt(); + + /*! \brief Copy constructor from a matrix of possibly different size and type. + * \param rhs A matrix with \a m times \a m values of type \a S. + * \remarks The minimum \a x of \a m and \a k, and the minimum \a y of \a n and \a l is determined. The + * first \a y values of type \a S in the first \a x rows from \a rhs are converted to type \a T and + * assigned as the first \a y values in the first \a x rows of \c this. If \a x is less than \a m, the + * last rows of \a this are not initialized. If \a y is less than \a n, the last values of the first \a + * x rows are not initialized. */ + template + explicit Matmnt( const Matmnt & rhs ); + + /*! \brief Constructor for a matrix by an array of m rows of type Vecnt. + * \param rows An array of m rows of type Vecnt + * \remarks This constructor can easily be called, using an initializer-list + * \code + * Mat33f m33f( { xAxis, yAxis, zAxis } ); + * \endcode */ + explicit Matmnt( const std::array,m> & rows ); + + /*! \brief Constructor for a matrix by an array of 2 rows of type Vecnt. + * \param v1,v2 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2 ); + + /*! \brief Constructor for a matrix by an array of 3 rows of type Vecnt. + * \param v1,v2,v3 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3 ); + + /*! \brief Constructor for a matrix by an array of 4 rows of type Vecnt. + * \param v1,v2,v3,v4 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3, Vecnt const& v4 ); + + + /*! \brief Constructor for a matrix by an array of m*n scalars of type T. + * \param scalars An array of m*n scalars of type T + * \remarks This constructor can easily be called, using an initializer-list + * \code + * Mat33f m33f( { m00, m01, m02, m10, m11, m12, m20, m21, m22 } ); + * \endcode */ + explicit Matmnt( const std::array & scalars ); + + /*! \brief Constructor for a 3 by 3 rotation matrix out of an axis and an angle. + * \param axis A reference to the constant axis to rotate about. + * \param angle The angle, in radians, to rotate. + * \remarks The resulting 3 by 3 matrix is a pure rotation. + * \note The behavior is undefined, if \a axis is not normalized. + * \par Example: + * \code + * Mat33f rotZAxisBy45Degrees( Vec3f( 0.0f, 0.0f, 1.0f ), PI/4 ); + * \endcode */ + Matmnt( const Vecnt<3,T> & axis, T angle ); + + /*! \brief Constructor for a 3 by 3 rotation matrix out of a normalized quaternion. + * \param ori A reference to the normalized quaternion representing the rotation. + * \remarks The resulting 3 by 3 matrix is a pure rotation. + * \note The behavior is undefined, if \a ori is not normalized. */ + explicit Matmnt( const Quatt & ori ); + + /*! \brief Constructor for a 4 by 4 transformation matrix out of a quaternion and a translation. + * \param ori A reference to the normalized quaternion representing the rotational part. + * \param trans A reference to the vector representing the translational part. + * \note The behavior is undefined, if \ ori is not normalized. */ + Matmnt( const Quatt & ori, const Vecnt<3,T> & trans ); + + /*! \brief Constructor for a 4 by 4 transformation matrix out of a quaternion, a translation, + * and a scaling. + * \param ori A reference to the normalized quaternion representing the rotational part. + * \param trans A reference to the vector representing the translational part. + * \param scale A reference to the vector representing the scaling along the three axis directions. + * \note The behavior is undefined, if \ ori is not normalized. */ + Matmnt( const Quatt & ori, const Vecnt<3,T> & trans, const Vecnt<3,T> & scale ); + + public: + /*! \brief Get a constant pointer to the n times n values of the matrix. + * \return A constant pointer to the matrix elements. + * \remarks The matrix elements are stored in row-major order. This function returns the + * address of the first element of the first row. It is assured, that the other elements of + * the matrix follow linearly. + * \par Example: + * If \c m is a 3 by 3 matrix, m.getPtr() gives a pointer to the 9 elements m00, m01, m02, m10, + * m11, m12, m20, m21, m22, in that order. */ + const T * getPtr() const; + + /*! \brief Invert the matrix. + * \return \c true, if the matrix was successfully inverted, otherwise \c false. */ + bool invert(); + + /*! \brief Non-constant subscript operator. + * \param i Index of the row to address. + * \return A reference to the \a i th row of the matrix. */ + Vecnt & operator[]( unsigned int i ); + + /*! \brief Constant subscript operator. + * \param i Index of the row to address. + * \return A constant reference to the \a i th row of the matrix. */ + const Vecnt & operator[]( unsigned int i ) const; + + /*! \brief Matrix addition and assignment operator. + * \param mat A constant reference to the matrix to add. + * \return A reference to \c this. + * \remarks The matrix \a mat has to be of the same size as \c this, but may hold values of a + * different type. The matrix elements of type \a S of \a mat are converted to type \a T and + * added to the corresponding matrix elements of \c this. */ + template + Matmnt & operator+=( const Matmnt & mat ); + + /*! \brief Matrix subtraction and assignment operator. + * \param mat A constant reference to the matrix to subtract. + * \return A reference to \c this. + * \remarks The matrix \a mat has to be of the same size as \c this, but may hold values of a + * different type. The matrix elements of type \a S of \a mat are converted to type \a T and + * subtracted from the corresponding matrix elements of \c this. */ + template + Matmnt & operator-=( const Matmnt & mat ); + + /*! \brief Scalar multiplication and assignment operator. + * \param s A scalar value to multiply with. + * \return A reference to \c this. + * \remarks The type of \a s may be of different type as the elements of the \c this. \a s is + * converted to type \a T and each element of \c this is multiplied with it. */ + template + Matmnt & operator*=( S s ); + + /*! \brief Matrix multiplication and assignment operator. + * \param mat A constant reference to the matrix to multiply with. + * \return A reference to \c this. + * \remarks The matrix multiplication \code *this * mat \endcode is calculated and assigned to + * \c this. */ + Matmnt & operator*=( const Matmnt & mat ); + + /*! \brief Scalar division and assignment operator. + * \param s A scalar value to divide by. + * \return A reference to \c this. + * \remarks The type of \a s may be of different type as the elements of the \c this. \a s is + * converted to type \a T and each element of \c this is divided by it. + * \note The behavior is undefined if \a s is very close to zero. */ + template + Matmnt & operator/=( S s ); + + private: + Vecnt m_mat[m]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine the determinant of a matrix. + * \param mat A constant reference to the matrix to determine the determinant from. + * \return The determinant of \a mat. */ + template + T determinant( const Matmnt & mat ); + + /*! \brief Invert a matrix. + * \param mIn A constant reference to the matrix to invert. + * \param mOut A reference to the matrix to hold the inverse. + * \return \c true, if the matrix \a mIn was successfully inverted, otherwise \c false. + * \note If the mIn was not successfully inverted, the values in mOut are undefined. */ + template + bool invert( const Matmnt & mIn, Matmnt & mOut ); + + /*! \brief Test if a matrix is the identity. + * \param mat A constant reference to the matrix to test for identity. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true, if the matrix is the identity, otherwise \c false. + * \remarks A matrix is considered to be the identity, if each of the diagonal elements differ + * less than \a eps from one, and each of the other matrix elements differ less than \a eps from + * zero. + * \sa isNormalized, isNull, isOrthogonal, isSingular */ + template + bool isIdentity( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool identity = true; + for ( unsigned int i=0 ; identity && i + bool isNormalized( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool normalized = true; + for ( unsigned int i=0 ; normalized && i v; + for ( unsigned int j=0 ; j + bool isNull( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool null = true; + for ( unsigned int i=0 ; null && i + bool isOrthogonal( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool orthogonal = true; + for ( unsigned int i=0 ; orthogonal && i+1 tm = ~mat; + for ( unsigned int i=0 ; orthogonal && i+1 + bool isSingular( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + return( abs( determinant( mat ) ) <= eps ); + } + + /*! \brief Get the value of the maximal absolute element of a matrix. + * \param mat A constant reference to a matrix to get the maximal element from. + * \return The value of the maximal absolute element of \a mat. + * \sa minElement */ + template + T maxElement( const Matmnt & mat ); + + /*! \brief Get the value of the minimal absolute element of a matrix. + * \param mat A constant reference to a matrix to get the minimal element from. + * \return The value of the minimal absolute element of \a mat. + * \sa maxElement */ + template + T minElement( const Matmnt & mat ); + + /*! \brief Matrix equality operator. + * \param m0 A constant reference to the first matrix to compare. + * \param m1 A constant reference to the second matrix to compare. + * \return \c true, if \a m0 and \a m1 are equal, otherwise \c false. + * \remarks Two matrices are considered to be equal, if each element of \a m0 differs less than + * the type dependent epsilon from the corresponding element of \a m1. */ + template + bool operator==( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix inequality operator. + * \param m0 A constant reference to the first matrix to compare. + * \param m1 A constant reference to the second matrix to compare. + * \return \c true, if \a m0 and \a m1 are not equal, otherwise \c false. + * \remarks Two matrices are considered to be not equal, if at least one element of \a m0 differs + * more than the type dependent epsilon from the the corresponding element of \a m1. */ + template + bool operator!=( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix transpose operator. + * \param mat A constant reference to the matrix to transpose. + * \return The transposed version of \a m. */ + template + Matmnt operator~( const Matmnt & mat ); + + /*! \brief Matrix addition operator. + * \param m0 A constant reference to the first matrix to add. + * \param m1 A constant reference to the second matrix to add. + * \return A matrix representing the sum of \code m0 + m1 \endcode */ + template + Matmnt operator+( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix negation operator. + * \param mat A constant reference to the matrix to negate. + * \return A matrix representing the negation of \a mat. */ + template + Matmnt operator-( const Matmnt & mat ); + + /*! \brief Matrix subtraction operator. + * \param m0 A constant reference to the first argument of the subtraction. + * \param m1 A constant reference to the second argument of the subtraction. + * \return A matrix representing the difference \code m0 - m1 \endcode */ + template + Matmnt operator-( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Scalar multiplication operator. + * \param mat A constant reference to the matrix to multiply. + * \param s The scalar value to multiply with. + * \return A matrix representing the product \code mat * s \endcode */ + template + Matmnt operator*( const Matmnt & mat, T s ); + + /*! \brief Scalar multiplication operator. + * \param s The scalar value to multiply with. + * \param mat A constant reference to the matrix to multiply. + * \return A matrix representing the product \code s * mat \endcode */ + template + Matmnt operator*( T s, const Matmnt & mat ); + + /*! \brief Vector multiplication operator. + * \param mat A constant reference to the matrix to multiply. + * \param v A constant reference to the vector to multiply with. + * \return A vector representing the product \code mat * v \endcode */ + template + Vecnt operator*( const Matmnt & mat, const Vecnt & v ); + + /*! \brief Vector multiplication operator. + * \param v A constant reference to the vector to multiply with. + * \param mat A constant reference to the matrix to multiply. + * \return A vector representing the product \code v * mat \endcode */ + template + Vecnt operator*( const Vecnt & v, const Matmnt & mat ); + + /*! \brief Matrix multiplication operator. + * \param m0 A constant reference to the first operand of the multiplication. + * \param m1 A constant reference to the second operand of the multiplication. + * \return A matrix representing the product \code m0 * m1 \endcode */ + template + Matmnt operator*( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Scalar division operator. + * \param mat A constant reference to the matrix to divide. + * \param s The scalar value to divide by. + * \return A matrix representing the matrix \a mat divided by \a s. */ + template + Matmnt operator/( const Matmnt & mat, T s ); + + /*! \brief Set a matrix to be the identity. + * \param mat The matrix to set to identity. + * \remarks Each diagonal element of \a mat is set to one, each other element is set to zero. */ + template + void setIdentity( Matmnt & mat ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Test if a 3 by 3 matrix represents a rotation. + * \param mat A constant reference to the matrix to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true, if the matrix represents a rotation, otherwise \c false. + * \remarks A 3 by 3 matrix is considered to be a rotation, if it is normalized, orthogonal, and its + * determinant is one. + * \sa isIdentity, isNull, isNormalized, isOrthogonal */ + template + bool isRotation( const Matmnt<3,3,T> & mat, T eps = 9 * std::numeric_limits::epsilon() ) + { + return( isNormalized( mat, eps ) + && isOrthogonal( mat, eps ) + && ( std::abs( determinant( mat ) - 1 ) <= eps ) ); + } + + /*! \brief Set the values of a 3 by 3 matrix using a normalized quaternion. + * \param mat A reference to the matrix to set. + * \param q A constant reference to the normalized quaternion to use. + * \return A reference to \a mat. + * \remarks The matrix is set to represent the same rotation as the normalized quaternion \a q. + * \note The behavior is undefined if \a q is not normalized. */ + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Quatt & q ); + + /*! \brief Set the values of a 3 by 3 matrix using a normalized rotation axis and an angle. + * \param mat A reference to the matrix to set. + * \param axis A constant reference to the normalized rotation axis. + * \param angle The angle in radians to rotate around \a axis. + * \return A reference to \a mat. + * \remarks The matrix is set to represent the rotation by \a angle around \a axis. + * \note The behavior is undefined if \a axis is not normalized. */ + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Vecnt<3,T> & axis, T angle ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 3, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a 3 by 3 matrix. + * \param mat A constant reference to the matrix to decompose. + * \param orientation A reference to the quaternion getting the rotational part of the matrix. + * \param scaling A reference to the vector getting the scaling part of the matrix. + * \param scaleOrientation A reference to the quaternion getting the orientation of the scaling. + * \note The behavior is undefined, if the determinant of \a mat is too small, or the rank of \a mat + * is less than three. */ + DP_MATH_API void decompose( const Matmnt<3,3,float> &mat, Quatt &orientation + , Vecnt<3,float> &scaling, Quatt &scaleOrientation ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*!\brief Test if a 4 by 4 matrix represents a mirror transform + * \param mat A const reference to the matrix to test. + * \return \c true if the given matrix is a mirror transform, otherwise \c false. */ + template + bool isMirrorMatrix( const Matmnt<4,4,T>& mat ) + { + const T* ptr = mat.getPtr(); + + const Vecnt<3,T> &v0 = reinterpret_cast&>(ptr[0]); + const Vecnt<3,T> &v1 = reinterpret_cast&>(ptr[4]); + const Vecnt<3,T> &v2 = reinterpret_cast&>(ptr[8]); + + return (scalarTripleProduct(v0, v1, v2)) < 0; + } + + /*! \brief Set the values of a 4 by 4 matrix by the constituents of a transformation. + * \param mat A reference to the matrix to set. + * \param ori A constant reference of the rotation part of the transformation. + * \param trans An optional constant reference to the translational part of the transformation. The + * default is a null vector. + * \param scale An optional constant reference to the scaling part of the transformation. The default + * is the identity scaling. + * \return A reference to \a mat. */ + template + Matmnt<4,4,T> & setMat( Matmnt<4,4,T> & mat, const Quatt & ori + , const Vecnt<3,T> & trans = Vecnt<3,T>(0,0,0) + , const Vecnt<3,T> & scale = Vecnt<3,T>(1,1,1) ) + { + Matmnt<3,3,T> m3( ori ); + mat[0] = Vecnt<4,T>( scale[0] * m3[0], 0 ); + mat[1] = Vecnt<4,T>( scale[1] * m3[1], 0 ); + mat[2] = Vecnt<4,T>( scale[2] * m3[2], 0 ); + mat[3] = Vecnt<4,T>( trans, 1 ); + return( mat ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 4, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a 4 by 4 matrix of floats. + * \param mat A constant reference to the matrix to decompose. + * \param translation A reference to the vector getting the translational part of the matrix. + * \param orientation A reference to the quaternion getting the rotational part of the matrix. + * \param scaling A reference to the vector getting the scaling part of the matrix. + * \param scaleOrientation A reference to the quaternion getting the orientation of the scaling. + * \note The behavior is undefined, if the determinant of \a mat is too small. + * \note Currently, the behavior is undefined, if the rank of \a mat is less than three. */ + DP_MATH_API void decompose( const Matmnt<4,4,float> &mat, Vecnt<3,float> &translation + , Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Matmnt<3,3,float> Mat33f; + typedef Matmnt<3,3,double> Mat33d; + typedef Matmnt<4,4,float> Mat44f; + typedef Matmnt<4,4,double> Mat44d; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Matmnt::Matmnt() + { + } + + template + template + inline Matmnt::Matmnt( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i(rhs[i]); + } + } + + template + inline Matmnt::Matmnt( const std::array,m> & rows ) + { + for ( unsigned int i=0 ; i + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2 ) + { + MY_STATIC_ASSERT( m == 2 ); + m_mat[0] = v1; + m_mat[1] = v2; + } + + template + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3 ) + { + MY_STATIC_ASSERT( m == 3 ); + m_mat[0] = v1; + m_mat[1] = v2; + m_mat[2] = v3; + } + + template + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3, Vecnt const& v4 ) + { + MY_STATIC_ASSERT( m == 4 ); + m_mat[0] = v1; + m_mat[1] = v2; + m_mat[2] = v3; + m_mat[3] = v4; + } + + template + inline Matmnt::Matmnt( const std::array & scalars ) + { + for ( unsigned int i=0, idx=0 ; i + inline Matmnt::Matmnt( const Vecnt<3,T> & axis, T angle ) + { + MY_STATIC_ASSERT( ( m == 3 ) && ( n == 3 ) ); + setMat( *this, axis, angle ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori ) + { + MY_STATIC_ASSERT( ( m == 3 ) && ( n == 3 ) ); + setMat( *this, ori ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori, const Vecnt<3,T> & trans ) + { + MY_STATIC_ASSERT( ( m == 4 ) && ( n == 4 ) ); + setMat( *this, ori, trans ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori, const Vecnt<3,T> & trans, const Vecnt<3,T> & scale ) + { + MY_STATIC_ASSERT( ( m == 4 ) && ( n == 4 ) ); + setMat( *this, ori, trans, scale ); + } + + template + inline const T * Matmnt::getPtr() const + { + return( m_mat[0].getPtr() ); + } + + template + inline bool Matmnt::invert() + { + MY_STATIC_ASSERT( m == n ); + Matmnt tmp; + bool ok = dp::math::invert( *this, tmp ); + if ( ok ) + { + *this = tmp; + } + return( ok ); + } + + template + inline Vecnt & Matmnt::operator[]( unsigned int i ) + { + MY_ASSERT( i < m ); + return( m_mat[i] ); + } + + template + inline const Vecnt & Matmnt::operator[]( unsigned int i ) const + { + MY_ASSERT( i < m ); + return( m_mat[i] ); + } + + template + template + inline Matmnt & Matmnt::operator+=( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Matmnt & Matmnt::operator-=( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Matmnt & Matmnt::operator*=( S s ) + { + for ( unsigned int i=0 ; i + inline Matmnt & Matmnt::operator*=( const Matmnt & rhs ) + { + *this = *this * rhs; + return( *this ); + } + + template + template + inline Matmnt & Matmnt::operator/=( S s ) + { + for ( unsigned int i=0 ; i + inline T calculateDeterminant( const Matmnt & mat, const Vecnt & first, const Vecnt & second ) + { + Vecnt subFirst, subSecond; + for ( unsigned int i=1 ; i + inline T calculateDeterminant( const Matmnt & mat, const Vecnt<1,unsigned int> & first, const Vecnt<1,unsigned int> & second ) + { + return( mat[first[0]][second[0]] ); + } + + template + inline T determinant( const Matmnt & mat ) + { + Vecnt first, second; + for ( unsigned int i=0 ; i + inline bool invert( const Matmnt & mIn, Matmnt & mOut ) + { + mOut = mIn; + + unsigned int p[n]; + + bool ok = true; + for ( unsigned int k=0 ; ok && k::epsilon() < std::abs(s) ); + if ( ok ) + { + T q = std::abs( mOut[i][k] ) / s; + if ( q > max ) + { + max = q; + p[k] = i; + } + } + } + + ok = ( std::numeric_limits::epsilon() < max ); + if ( ok ) + { + if ( p[k] != k ) + { + for ( unsigned int j=0 ; j::epsilon() < std::abs( pivot ) ); + if ( ok ) + { + for ( unsigned int j=0 ; j ( int k >= 0 ) + { + if ( p[k] != k ) + { + for ( unsigned int i=0 ; i + inline T maxElement( const Matmnt & mat ) + { + T me = maxElement( mat[0] ); + for ( unsigned int i=1 ; i + inline T minElement( const Matmnt & mat ) + { + T me = minElement( mat[0] ); + for ( unsigned int i=1 ; i + inline bool operator==( const Matmnt & m0, const Matmnt & m1 ) + { + bool eq = true; + for ( unsigned int i=0 ; i + inline bool operator!=( const Matmnt & m0, const Matmnt & m1 ) + { + return( ! ( m0 == m1 ) ); + } + + template + inline Matmnt operator~( const Matmnt & mat ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator+( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret(m0); + ret += m1; + return( ret ); + } + + template + inline Matmnt operator-( const Matmnt & mat ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator-( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret(m0); + ret -= m1; + return( ret ); + } + + template + inline Matmnt operator*( const Matmnt & mat, T s ) + { + Matmnt ret(mat); + ret *= s; + return( ret ); + } + + template + inline Matmnt operator*( T s, const Matmnt & mat ) + { + return( mat * s ); + } + + template + inline Vecnt operator*( const Matmnt & mat, const Vecnt & v ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Vecnt operator*( const Vecnt & v, const Matmnt & mat ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator*( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator/( const Matmnt & mat, T s ) + { + Matmnt ret(mat); + ret /= s; + return( ret ); + } + + template + void setIdentity( Matmnt & mat ) + { + for ( unsigned int i=0 ; i + inline T determinant( const Matmnt<3,3,T> & mat ) + { + return( mat[0] * ( mat[1] ^ mat[2] ) ); + } + + template + inline bool invert( const Matmnt<3,3,T> & mIn, Matmnt<3,3,T> & mOut ) + { + double adj00 = ( mIn[1][1] * mIn[2][2] - mIn[1][2] * mIn[2][1] ); + double adj10 = - ( mIn[1][0] * mIn[2][2] - mIn[1][2] * mIn[2][0] ); + double adj20 = ( mIn[1][0] * mIn[2][1] - mIn[1][1] * mIn[2][0] ); + double det = mIn[0][0] * adj00 + mIn[0][1] * adj10 + mIn[0][2] * adj20; + bool ok = ( std::numeric_limits::epsilon() < abs( det ) ); + if ( ok ) + { + double invDet = 1.0 / det; + mOut[0][0] = T( adj00 * invDet ); + mOut[0][1] = T( - ( mIn[0][1] * mIn[2][2] - mIn[0][2] * mIn[2][1] ) * invDet ); + mOut[0][2] = T( ( mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1] ) * invDet ); + mOut[1][0] = T( adj10 * invDet ); + mOut[1][1] = T( ( mIn[0][0] * mIn[2][2] - mIn[0][2] * mIn[2][0] ) * invDet ); + mOut[1][2] = T( - ( mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0] ) * invDet ); + mOut[2][0] = T( adj20 * invDet ); + mOut[2][1] = T( - ( mIn[0][0] * mIn[2][1] - mIn[0][1] * mIn[2][0] ) * invDet ); + mOut[2][2] = T( ( mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0] ) * invDet ); + } + return( ok ); + } + + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Vecnt<3,T> & axis, T angle ) + { + T c = cos( angle ); + T s = sin( angle ); + T t = 1 - c; + T x = axis[0]; + T y = axis[1]; + T z = axis[2]; + + mat[0] = Vecnt<3,T>( t * x * x + c, t * x * y + s * z, t * x * z - s * y ); + mat[1] = Vecnt<3,T>( t * x * y - s * z, t * y * y + c, t * y * z + s * x ); + mat[2] = Vecnt<3,T>( t * x * z + s * y, t * y * z - s * x, t * z * z + c ); + + return( mat ); + } + + template + inline Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Quatt & q ) + { + T x = q[0]; + T y = q[1]; + T z = q[2]; + T w = q[3]; + + mat[0] = Vecnt<3,T>( 1 - 2 * ( y * y + z * z ), 2 * ( x * y + z * w ), 2 * ( x * z - y * w ) ); + mat[1] = Vecnt<3,T>( 2 * ( x * y - z * w ), 1 - 2 * ( x * x + z * z ), 2 * ( y * z + x * w ) ); + mat[2] = Vecnt<3,T>( 2 * ( x * z + y * w ), 2 * ( y * z - x * w ), 1 - 2 * ( x * x + y * y ) ); + + return( mat ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for m,n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline T determinant( const Matmnt<4,4,T> & mat ) + { + const T a0 = mat[0][0]*mat[1][1] - mat[0][1]*mat[1][0]; + const T a1 = mat[0][0]*mat[1][2] - mat[0][2]*mat[1][0]; + const T a2 = mat[0][0]*mat[1][3] - mat[0][3]*mat[1][0]; + const T a3 = mat[0][1]*mat[1][2] - mat[0][2]*mat[1][1]; + const T a4 = mat[0][1]*mat[1][3] - mat[0][3]*mat[1][1]; + const T a5 = mat[0][2]*mat[1][3] - mat[0][3]*mat[1][2]; + const T b0 = mat[2][0]*mat[3][1] - mat[2][1]*mat[3][0]; + const T b1 = mat[2][0]*mat[3][2] - mat[2][2]*mat[3][0]; + const T b2 = mat[2][0]*mat[3][3] - mat[2][3]*mat[3][0]; + const T b3 = mat[2][1]*mat[3][2] - mat[2][2]*mat[3][1]; + const T b4 = mat[2][1]*mat[3][3] - mat[2][3]*mat[3][1]; + const T b5 = mat[2][2]*mat[3][3] - mat[2][3]*mat[3][2]; + return( a0*b5 - a1*b4 + a2*b3 + a3*b2 - a4*b1 + a5*b0 ); + } + + template + inline bool invert( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + T s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; T c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + T s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; T c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + T s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; T c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + T s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; T c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + T s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; T c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + T s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; T c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + T det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( det != T(0) ) + { + T invDet = T(1.0) / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[0][1] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[0][2] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[0][3] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[1][0] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[1][2] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[1][3] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[2][0] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[2][1] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[2][3] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[3][0] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[3][1] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[3][2] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + template + inline bool invertTranspose( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + T s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; T c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + T s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; T c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + T s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; T c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + T s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; T c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + T s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; T c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + T s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; T c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + T det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( det != 0 ) + { + T invDet = T(1.0) / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[1][0] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[2][0] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[3][0] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[0][1] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[2][1] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[3][1] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[0][2] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[1][2] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[3][2] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[0][3] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[1][3] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[2][3] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + template + inline bool invertDouble( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + double s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; double c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + double s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; double c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + double s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; double c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + double s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; double c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + double s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; double c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + double s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; double c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + double det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( ( std::numeric_limits::epsilon() < abs( det ) ) ) + { + double invDet = 1.0 / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[0][1] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[0][2] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[0][3] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[1][0] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[1][2] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[1][3] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[2][0] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[2][1] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[2][3] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[3][0] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[3][1] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[3][2] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + /*! \brief makeLookAt defines a viewing transformation. + * \param eye The position of the eye point. + * \param center The position of the reference point. + * \param up The direction of the up vector. + * \remarks The makeLookAt function creates a viewing matrix derived from an eye point, a reference point indicating the center + * of the scene, and an up vector. The matrix maps the reference point to the negative z-axis and the eye point to the + * origin, so that when you use a typical projection matrix, the center of the scene maps to the center of the viewport. + * Similarly, the direction described by the up vector projected onto the viewing plane is mapped to the positive y-axis so that + * it points upward in the viewport. The up vector must not be parallel to the line of sight from the eye to the reference point. + * \note This documentation is adapted from gluLookAt, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeLookAt( const Vecnt<3,T> & eye, const Vecnt<3,T> & center, const Vecnt<3,T> & up ) + { + Vecnt<3,T> f = center - eye; + normalize( f ); + + #ifndef NDEBUG + // assure up is not parallel to vector from eye to center + Vecnt<3,T> nup = up; + normalize( nup ); + T dot = f * nup; + MY_ASSERT( dot != T(1) && dot != T(-1) ); + #endif + + Vecnt<3,T> s = f ^ up; + normalize( s ); + Vecnt<3,T> u = s ^ f; + + Matmnt<4,4,T> transmat; + transmat[0] = Vec4f( T(1), T(0), T(0), T(0) ); + transmat[1] = Vec4f( T(0), T(1), T(0), T(0) ); + transmat[2] = Vec4f( T(0), T(0), T(1), T(0) ); + transmat[3] = Vec4f( -eye[0], -eye[1], -eye[2], T(1) ); + + Matmnt<4,4,T> orimat; + orimat[0] = Vec4f( s[0], u[0], -f[0], T(0) ); + orimat[1] = Vec4f( s[1], u[1], -f[1], T(0) ); + orimat[2] = Vec4f( s[2], u[2], -f[2], T(0) ); + orimat[3] = Vec4f( T(0), T(0), T(0), T(1) ); + + // must premultiply translation + return transmat * orimat; + } + + /*! \brief makeOrtho defines an orthographic projection matrix. + * \param left Coordinate for the left vertical clipping plane. + * \param right Coordinate for the right vertical clipping plane. + * \param bottom Coordinate for the bottom horizontal clipping plane. + * \param top Coordinate for the top horizontal clipping plane. + * \param znear The distance to the near clipping plane. This distance is negative if the plane is behind the viewer. + * \param zfar The distance to the far clipping plane. This distance is negative if the plane is behind the viewer. + * \remarks The makeOrtho function describes a perspective matrix that produces a parallel projection. Assuming this function + * will be used to build a camera's Projection matrix, the (left, bottom, znear) and (right, top, znear) parameters specify the + * points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, + * assuming that the eye is located at (0, 0, 0). The far parameter specifies the location of the far clipping plane. Both znear + * and zfar can be either positive or negative. + * \note This documentation is adapted from glOrtho, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeOrtho( T left, T right, + T bottom, T top, + T znear, T zfar ) + { + MY_ASSERT( (left != right) && (bottom != top) && (znear != zfar) && (zfar > znear) ); + + return Matmnt<4,4,T>( { T(2)/(right-left), T(0), T(0), T(0) + , T(0), T(2)/(top-bottom), T(0), T(0) + , T(0), T(0), T(-2)/(zfar-znear), T(0) + , -(right+left)/(right-left), -(top+bottom)/(top-bottom), -(zfar+znear)/(zfar-znear), T(1) } ); + } + + /*! \brief makeFrustum defines a perspective projection matrix. + * \param left Coordinate for the left vertical clipping plane. + * \param right Coordinate for the right vertical clipping plane. + * \param bottom Coordinate for the bottom horizontal clipping plane. + * \param top Coordinate for the top horizontal clipping plane. + * \param znear The distance to the near clipping plane. The value must be greater than zero. + * \param zfar The distance to the far clipping plane. The value must be greater than znear. + * \remarks The makeFrustum function describes a perspective matrix that produces a perspective projection. Assuming this function + * will be used to build a camera's Projection matrix, the (left, bottom, znear) and (right, top, znear) parameters specify the + * points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, + * assuming that the eye is located at (0,0,0). The zfar parameter specifies the location of the far clipping plane. Both znear + * and zfar must be positive. + * \note This documentation is adapted from glFrustum, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeFrustum( T left, T right, + T bottom, T top, + T znear, T zfar ) + { + // near and far must be greater than zero + MY_ASSERT( (znear > T(0)) && (zfar > T(0)) && (zfar > znear) ); + MY_ASSERT( (left != right) && (bottom != top) && (znear != zfar) ); + + T v0 = (right+left)/(right-left); + T v1 = (top+bottom)/(top-bottom); + T v2 = -(zfar+znear)/(zfar-znear); + T v3 = T(-2)*zfar*znear/(zfar-znear); + T v4 = T(2)*znear/(right-left); + T v5 = T(2)*znear/(top-bottom); + + return Matmnt<4,4,T>( { v4, T(0), T(0), T(0) + , T(0), v5, T(0), T(0) + , v0, v1, v2, T(-1) + , T(0), T(0), v3, T(0) } ); + } + + /*! \brief makePerspective builds a perspective projection matrix. + * \param fovy The vertical field of view, in degrees. + * \param aspect The ratio of the viewport width / height. + * \param znear The distance to the near clipping plane. The value must be greater than zero. + * \param zfar The distance to the far clipping plane. The value must be greater than znear. + * \remarks Assuming makePerspective will be used to build a camera's Projection matrix, it specifies a viewing frustum into the + * world coordinate system. In general, the aspect ratio in makePerspective should match the aspect ratio of the associated + * viewport. For example, aspect = 2.0 means the viewer's angle of view is twice as wide in x as it is in y. If the viewport + * is twice as wide as it is tall, it displays the image without distortion. + * \note This documentation is adapted from gluPerspective, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makePerspective( T fovy, T aspect, T znear, T zfar ) + { + MY_ASSERT( (znear > (T)0) && (zfar > (T)0) ); + + T tanfov = tan( degToRad( fovy ) * (T)0.5 ); + T r = tanfov * aspect * znear; + T l = -r; + T t = tanfov * znear; + T b = -t; + + return makeFrustum( l, r, b, t, znear, zfar ); + } + + template + inline Vecnt<4,T> operator*( const Vecnt<4,T>& v, const Matmnt<4,4,T>& mat ) + { + return Vecnt<4,T> ( + v[0] * mat[0][0] + v[1]*mat[1][0] + v[2]*mat[2][0] + v[3]*mat[3][0], + v[0] * mat[0][1] + v[1]*mat[1][1] + v[2]*mat[2][1] + v[3]*mat[3][1], + v[0] * mat[0][2] + v[1]*mat[1][2] + v[2]*mat[2][2] + v[3]*mat[3][2], + v[0] * mat[0][3] + v[1]*mat[1][3] + v[2]*mat[2][3] + v[3]*mat[3][3] ); + } + + template + inline Matmnt<4,4,T> operator*( const Matmnt<4,4,T> & m0, const Matmnt<4,4,T> & m1 ) + { + Matmnt<4,4,T> result; + + result[0] = Vecnt<4,T>( + m0[0][0]*m1[0][0] + m0[0][1]*m1[1][0] + m0[0][2]*m1[2][0] + m0[0][3]*m1[3][0], + m0[0][0]*m1[0][1] + m0[0][1]*m1[1][1] + m0[0][2]*m1[2][1] + m0[0][3]*m1[3][1], + m0[0][0]*m1[0][2] + m0[0][1]*m1[1][2] + m0[0][2]*m1[2][2] + m0[0][3]*m1[3][2], + m0[0][0]*m1[0][3] + m0[0][1]*m1[1][3] + m0[0][2]*m1[2][3] + m0[0][3]*m1[3][3] + ); + + result[1] = Vecnt<4,T>( + m0[1][0]*m1[0][0] + m0[1][1]*m1[1][0] + m0[1][2]*m1[2][0] + m0[1][3]*m1[3][0], + m0[1][0]*m1[0][1] + m0[1][1]*m1[1][1] + m0[1][2]*m1[2][1] + m0[1][3]*m1[3][1], + m0[1][0]*m1[0][2] + m0[1][1]*m1[1][2] + m0[1][2]*m1[2][2] + m0[1][3]*m1[3][2], + m0[1][0]*m1[0][3] + m0[1][1]*m1[1][3] + m0[1][2]*m1[2][3] + m0[1][3]*m1[3][3] + ); + + result[2] = Vecnt<4,T>( + m0[2][0]*m1[0][0] + m0[2][1]*m1[1][0] + m0[2][2]*m1[2][0] + m0[2][3]*m1[3][0], + m0[2][0]*m1[0][1] + m0[2][1]*m1[1][1] + m0[2][2]*m1[2][1] + m0[2][3]*m1[3][1], + m0[2][0]*m1[0][2] + m0[2][1]*m1[1][2] + m0[2][2]*m1[2][2] + m0[2][3]*m1[3][2], + m0[2][0]*m1[0][3] + m0[2][1]*m1[1][3] + m0[2][2]*m1[2][3] + m0[2][3]*m1[3][3] + ); + + result[3] = Vecnt<4,T>( + m0[3][0]*m1[0][0] + m0[3][1]*m1[1][0] + m0[3][2]*m1[2][0] + m0[3][3]*m1[3][0], + m0[3][0]*m1[0][1] + m0[3][1]*m1[1][1] + m0[3][2]*m1[2][1] + m0[3][3]*m1[3][1], + m0[3][0]*m1[0][2] + m0[3][1]*m1[1][2] + m0[3][2]*m1[2][2] + m0[3][3]*m1[3][2], + m0[3][0]*m1[0][3] + m0[3][1]*m1[1][3] + m0[3][2]*m1[2][3] + m0[3][3]*m1[3][3] + ); + + return result; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for m,n == 4, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline void decompose( const Matmnt<4,4,float> &mat, Vecnt<3,float> &translation + , Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ) + { + translation = Vecnt<3,float>( mat[3] ); + Matmnt<3,3,float> m33( mat ); + decompose( m33, orientation, scaling, scaleOrientation ); + } + + + + //! global identity matrix. + extern DP_MATH_API const Mat44f cIdentity44f; + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/Quatt.h b/apps/MDL_sdf/dp/math/Quatt.h new file mode 100644 index 00000000..8ace2d8f --- /dev/null +++ b/apps/MDL_sdf/dp/math/Quatt.h @@ -0,0 +1,551 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include + +namespace dp +{ + namespace math + { + + template class Matmnt; + + /*! \brief Quaternion class. + * \remarks Quaternions are an alternative to the 3x3 matrices that are typically used for 3-D + * rotations. A unit quaternion represents an axis in 3-D space and a rotation around that axis. + * Every rotation can be expressed that way. There are typedefs for the most common usage with \c + * float and \c double: Quatf, Quatd. + * \note Only unit quaternions represent a rotation. */ + template class Quatt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons no initialization is performed. */ + Quatt(); + + /*! \brief Constructor using four scalar values. + * \param x X-component of the quaternion. + * \param y Y-component of the quaternion. + * \param z Z-component of the quaternion. + * \param w W-component of the quaternion. + * \note \c x, \c y, and \c z are \b not the x,y,z-component of the rotation axis, and \c w + * is \b not the rotation angle. If you have such values handy, use the constructor that + * takes the axis as a Vecnt<3,T> and an angle. */ + Quatt( T x, T y, T z, T w ); + + /*! \brief Constructor using a Vecnt<4,T>. + * \param v Vector to construct the quaternion from. + * \remarks The four values of \c v are just copied over to the quaternion. It is assumed + * that this operation gives a normalized quaternion. */ + explicit Quatt( const Vecnt<4,T> & v ); + + /*! \brief Copy constructor using a Quaternion of possibly different type. + * \param q The quaternion to copy. */ + template + explicit Quatt( const Quatt & q ); + + /*! \brief Constructor using an axis and an angle. + * \param axis Axis to rotate around. + * \param angle Angle in radians to rotate. + * \remarks The resulting quaternion represents a rotation by \c angle (in radians) around + * \c axis. */ + Quatt( const Vecnt<3,T> & axis, T angle ); + + /*! \brief Constructor by two vectors. + * \param v0 Start vector. + * \param v1 End vector. + * \remarks The resulting quaternion represents the rotation that maps \a vec0 to \a vec1. + * \note The quaternion out of two anti-parallel vectors is not uniquely defined. We select just + * one out of the possible candidates, which might not be the one you would expect. For better + * control on the quaternion in such a case, we recommend to use the constructor out of an axis + * and an angle. */ + Quatt( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ); + + /*! \brief Constructor by a rotation matrix. + * \param rot The rotation matrix to convert to a unit quaternion. + * \remarks The resulting quaternion represents the same rotation as the matrix. */ + explicit Quatt( const Matmnt<3,3,T> & rot ); + + /*! \brief Normalize the quaternion. + * \return A reference to \c this, as the normalized quaternion. + * \remarks It is always assumed, that a quaternion is normalized. But when getting data from + * outside or due to numerically instabilities, a quaternion might become unnormalized. You + * can use this function then to normalize it again. */ + Quatt & normalize(); + + /*! \brief Non-constant subscript operator. + * \param i Index to the element to use (i=0,1,2,3). + * \return A reference to the \a i th element of the quaternion. */ + template + T & operator[]( S i ); + + /*! \brief Constant subscript operator. + * \param i Index to the element to use (i=0,1,2,3). + * \return A reference to the constant \a i th element of the quaternion. */ + template + const T & operator[]( S i ) const; + + /*! \brief Quaternion assignment operator from a Quaternion of possibly different type. + * \param q The quaternion to assign. + * \return A reference to \c this, as the assigned Quaternion from q. */ + template + Quatt & operator=( const Quatt & q ); + + /*! \brief Quaternion multiplication with a quaternion and assignment operator. + * \param q A Quaternion to multiply with. + * \return A reference to \c this, as the product (or concatenation) of \c this and \a q. + * \remarks Multiplying two quaternions give a quaternion that represents the concatenation + * of the rotations represented by the two quaternions. */ + Quatt & operator*=( const Quatt & q ); + + /*! \brief Quaternion division by a quaternion and assignment operator. + * \param q A Quaternion to divide by. + * \return A reference to \c this, as the division of \c this by \a q. + * \remarks Dividing a quaternion by an other gives a quaternion that represents the + * concatenation of the rotation represented by the numerator quaternion and the rotation + * represented by the conjugated denumerator quaternion. */ + Quatt & operator/=( const Quatt & q ); + + private: + T m_quat[4]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a quaternion into an axis and an angle. + * \param q A reference to the constant quaternion to decompose. + * \param axis A reference to the resulting axis. + * \param angle A reference to the resulting angle. */ + template + void decompose( const Quatt & q, Vecnt<3,T> & axis, T & angle ); + + /*! \brief Determine the distance between two quaternions. + * \param q0 A reference to the left constant quaternion. + * \param q1 A reference to the right constant quaternion. + * \return The euclidean distance between \a q0 and \c q1, interpreted as Vecnt<4,T>. */ + template + T distance( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Test if a quaternion is normalized. + * \param q A reference to the constant quaternion to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true if the quaternion is normalized, otherwise \c false. + * \remarks A quaternion \a q is considered to be normalized, when it's magnitude differs less + * than some small value \a eps from one. */ + template + bool isNormalized( const Quatt & q, T eps = std::numeric_limits::epsilon() * 8 ) + { + return( std::abs( magnitude( q ) - 1 ) <= eps ); + } + + /*! \brief Test if a quaternion is null. + * \param q A reference to the constant quaternion to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true if the quaternion is null, otherwise \c false. + * \remarks A quaternion \a q is considered to be null, if it's magnitude is less than some small + * value \a eps. */ + template + bool isNull( const Quatt & q, T eps = std::numeric_limits::epsilon() ) + { + return( magnitude( q ) <= eps ); + } + + /*! \brief Determine the magnitude of a quaternion. + * \param q A reference to the quaternion to determine the magnitude of. + * \return The magnitude of the quaternion \a q. + * \remarks The magnitude of a normalized quaternion is one. */ + template + T magnitude( const Quatt & q ); + + /*! \brief Quaternion equality operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return \c true if the quaternions \a q0 and \a q are equal, otherwise \c false. + * \remarks Two quaternions are considered to be equal, if each component of the one quaternion + * deviates less than epsilon from the corresponding element of the other quaternion. */ + template + bool operator==( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion inequality operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return \c true if \a q0 is not equal to \a q1, otherwise \c false + * \remarks Two quaternions are considered to be equal, if each component of the one quaternion + * deviates less than epsilon from the corresponding element of the other quaternion. */ + template + bool operator!=( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion conjugation operator. + * \param q A reference to the constant quaternion to conjugate. + * \return The conjugation of \a q. + * \remarks The conjugation of a quaternion is a rotation of the same angle around the negated + * axis. */ + template + Quatt operator~( const Quatt & q ); + + /*! \brief Negation operator. + * \param q A reference to the constant quaternion to get the negation from. + * \return The negation of \a q. + * \remarks The negation of a quaternion is a rotation around the same axis by the negated angle. */ + template + Quatt operator-( const Quatt & q ); + + /*! \brief Quaternion multiplication operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return The product of \a q0 with \a a1. + * \remarks Multiplying two quaternions gives a quaternion that represents the concatenation of + * the rotation represented by the two quaternions. Besides rounding errors, the following + * equation holds: + * \code + * Matmnt<3,3,T>( q0 * q1 ) == Matmnt<3,3,T>( q0 ) * Matmnt<3,3,T>( q1 ) + * \endcode */ + template + Quatt operator*( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion multiplication operator with a vector. + * \param q A reference to the constant left operand. + * \param v A reference to the constant right operand. + * \return The vector \a v rotated by the inverse of \a q. + * \remarks Multiplying a quaternion \a q with a vector \a v applies the inverse rotation + * represented by \a q to \a v. */ + template + Vecnt<3,T> operator*( const Quatt & q, const Vecnt<3,T> & v ); + + /*! \brief Vector multiplication operator with a quaternion. + * \param v A reference to the constant left operand. + * \param q A reference to the constant right operand. + * \return The vector \a v rotated by \a q. + * \remarks Multiplying a vector \a v by a quaternion \a q applies the rotation represented by + * \a q to \a v. */ + template + Vecnt<3,T> operator*( const Vecnt<3,T> & v, const Quatt & q ); + + /*! \brief Quaternion division operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return A quaternion representing the quotient of \a q0 and \a q1. + * \remarks Dividing a quaternion by an other gives a quaternion that represents the + * concatenation of the rotation represented by the numerator quaternion and the rotation + * represented by the conjugated denumerator quaternion. */ + template + Quatt operator/( const Quatt & q0, const Quatt & q1 ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Spherical linear interpolation between two quaternions \a q0 and \a q1. + * \param alpha The interpolation parameter. + * \param q0 The starting value. + * \param q1 The ending value. + * \return The quaternion that represents the spherical linear interpolation between \a q0 and \a + * q1. */ + DP_MATH_API Quatt lerp( float alpha, const Quatt & q0, const Quatt & q1 ); + + /*! \brief Spherical linear interpolation between two quaternions \a q0 and \a q1. + * \param alpha The interpolation parameter. + * \param q0 The starting value. + * \param q1 The ending value. + * \param qr The quaternion that represents the spherical linear interpolation between \a q0 and \a + * q1. */ + DP_MATH_API void lerp( float alpha, const Quatt & q0, const Quatt & q1, Quatt &qr ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Quatt Quatf; + typedef Quatt Quatd; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline Quatt::Quatt() + { + } + + template + inline Quatt::Quatt( T x, T y, T z, T w ) + { + m_quat[0] = x; + m_quat[1] = y; + m_quat[2] = z; + m_quat[3] = w; + } + + template + inline Quatt::Quatt( const Vecnt<4,T> & v ) + { + m_quat[0] = v[0]; + m_quat[1] = v[1]; + m_quat[2] = v[2]; + m_quat[3] = v[3]; + } + + template + template + inline Quatt::Quatt( const Quatt & q ) + { + *this = q; + } + + template + inline Quatt::Quatt( const Vecnt<3,T> & axis, T angle ) + { + T dummy = sin( T(0.5) * angle ); + m_quat[0] = axis[0] * dummy; + m_quat[1] = axis[1] * dummy; + m_quat[2] = axis[2] * dummy; + m_quat[3] = cos( T(0.5) * angle ); + } + + template + inline Quatt::Quatt( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ) + { + Vecnt<3,T> axis = v0 ^ v1; + axis.normalize(); + T cosAngle = clamp( v0 * v1, -1.0f, 1.0f ); // make sure, cosine is in [-1.0,1.0]! + if ( cosAngle + 1.0f < std::numeric_limits::epsilon() ) + { + // In case v0 and v1 are (closed to) anti-parallel, the standard + // procedure would not create a valid quaternion. + // As the rotation axis isn't uniquely defined in that case, we + // just pick one. + axis = orthonormal( v0 ); + } + T s = sqrt( T(0.5) * ( 1 - cosAngle ) ); + m_quat[0] = axis[0] * s; + m_quat[1] = axis[1] * s; + m_quat[2] = axis[2] * s; + m_quat[3] = sqrt( T(0.5) * ( 1 + cosAngle ) ); + } + + template + inline Quatt::Quatt( const Matmnt<3,3,T> & rot ) + { + T tr = rot[0][0] + rot[1][1] + rot[2][2] + 1; + if ( std::numeric_limits::epsilon() < tr ) + { + T s = sqrt( tr ); + m_quat[3] = T(0.5) * s; + s = T(0.5) / s; + m_quat[0] = ( rot[1][2] - rot[2][1] ) * s; + m_quat[1] = ( rot[2][0] - rot[0][2] ) * s; + m_quat[2] = ( rot[0][1] - rot[1][0] ) * s; + } + else + { + unsigned int i = 0; + if ( rot[i][i] < rot[1][1] ) + { + i = 1; + } + if ( rot[i][i] < rot[2][2] ) + { + i = 2; + } + unsigned int j = ( i + 1 ) % 3; + unsigned int k = ( j + 1 ) % 3; + T s = sqrt( rot[i][i] - rot[j][j] - rot[k][k] + 1 ); + m_quat[i] = T(0.5) * s; + s = T(0.5) / s; + m_quat[j] = ( rot[i][j] + rot[j][i] ) * s; + m_quat[k] = ( rot[i][k] + rot[k][i] ) * s; + m_quat[3] = ( rot[k][j] - rot[j][k] ) * s; + } + normalize(); + } + + template + inline Quatt & Quatt::normalize() + { + T mag = sqrt( square(m_quat[0]) + square(m_quat[1]) + square(m_quat[2]) + square(m_quat[3]) ); + T invMag = T(1) / mag; + for ( int i=0 ; i<4 ; i++ ) + { + m_quat[i] = m_quat[i] * invMag; + } + return( *this ); + } + + template<> + inline Quatt & Quatt::normalize() + { + *this = (Quatt(*this)).normalize(); + return( *this ); + } + + template + template + inline T & Quatt::operator[]( S i ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i && i <= 3 ); + return( m_quat[i] ); + } + + template + template + inline const T & Quatt::operator[]( S i ) const + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i && i <= 3 ); + return( m_quat[i] ); + } + + template + template + inline Quatt & Quatt::operator=( const Quatt & q ) + { + m_quat[0] = T(q[0]); + m_quat[1] = T(q[1]); + m_quat[2] = T(q[2]); + m_quat[3] = T(q[3]); + return( *this ); + } + + template + inline Quatt & Quatt::operator*=( const Quatt & q ) + { + *this = *this * q; + return( *this ); + } + + template + inline Quatt & Quatt::operator/=( const Quatt & q ) + { + *this = *this / q; + return( *this ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline void decompose( const Quatt & q, Vecnt<3,T> & axis, T & angle ) + { + angle = 2 * acos( q[3] ); + if ( angle < std::numeric_limits::epsilon() ) + { + // no angle to rotate about => take just any one + axis[0] = 0.0f; + axis[1] = 0.0f; + axis[2] = 1.0f; + } + else + { + T dummy = 1 / sin( T(0.5) * angle ); + axis[0] = q[0] * dummy; + axis[1] = q[1] * dummy; + axis[2] = q[2] * dummy; + axis.normalize(); + } + } + + template + inline T distance( const Quatt & q0, const Quatt & q1 ) + { + return( sqrt( square( q0[0] - q1[0] ) + + square( q0[1] - q1[1] ) + + square( q0[2] - q1[2] ) + + square( q0[3] - q1[3] ) ) ); + } + + template + inline T magnitude( const Quatt & q ) + { + return( sqrt( q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3] ) ); + } + + template + inline bool operator==( const Quatt & q0, const Quatt & q1 ) + { + return( ( std::abs( q0[0] - q1[0] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[1] - q1[1] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[2] - q1[2] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[3] - q1[3] ) < std::numeric_limits::epsilon() ) ); + } + + template + inline bool operator!=( const Quatt & q0, const Quatt & q1 ) + { + return( ! ( q0 == q1 ) ); + } + + template + inline Quatt operator~( const Quatt & q ) + { + return( Quatt( -q[0], -q[1], -q[2], q[3] ) ); + } + + template + inline Quatt operator-( const Quatt & q ) + { + return( Quatt( q[0], q[1], q[2], -q[3] ) ); + } + + template + inline Quatt operator*( const Quatt & q0, const Quatt & q1 ) + { + Quatt q( q0[3]*q1[0] + q0[0]*q1[3] - q0[1]*q1[2] + q0[2]*q1[1] + , q0[3]*q1[1] + q0[0]*q1[2] + q0[1]*q1[3] - q0[2]*q1[0] + , q0[3]*q1[2] - q0[0]*q1[1] + q0[1]*q1[0] + q0[2]*q1[3] + , q0[3]*q1[3] - q0[0]*q1[0] - q0[1]*q1[1] - q0[2]*q1[2] ); + q.normalize(); + return( q ); + } + + template + inline Vecnt<3,T> operator*( const Quatt & q, const Vecnt<3,T> & v ) + { + return( Matmnt<3,3,T>(q) * v ); + } + + template + inline Vecnt<3,T> operator*( const Vecnt<3,T> & v, const Quatt & q ) + { + return( v * Matmnt<3,3,T>(q) ); + } + + template + inline Quatt operator/( const Quatt & q0, const Quatt & q1 ) + { + return( q0 * ~q1 /*/ magnitude( q1 )*/ ); + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/Trafo.h b/apps/MDL_sdf/dp/math/Trafo.h new file mode 100644 index 00000000..a4eb1d33 --- /dev/null +++ b/apps/MDL_sdf/dp/math/Trafo.h @@ -0,0 +1,264 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include + +namespace dp +{ + namespace math + { + + //! Transformation class. + /** This class is used to ease transformation handling. It has an interface to rotate, scale, + * and translate and can produce a \c Mat44f that combines them. */ + class Trafo + { + public: + //! Constructor: initialized to identity + DP_MATH_API Trafo( void ); + + //! Copy Constructor + DP_MATH_API Trafo( const Trafo &rhs ); //!< Trafo to copy + + //! Get the center of rotation of this transformation. + /** \returns \c Vec3f that describes the center or rotation. */ + DP_MATH_API const Vec3f & getCenter( void ) const; + + //! Get the rotational part of this transformation. + /** \returns \c Quatf that describes the rotational part */ + DP_MATH_API const Quatf & getOrientation( void ) const; + + //! Get the scale orientation part of this transform. + /** \return \c Quatf that describes the scale orientational part */ + DP_MATH_API const Quatf & getScaleOrientation( void ) const; + + //! Get the scaling part of this transformation. + /** \returns \c Vec3f that describes the scaling part */ + DP_MATH_API const Vec3f & getScaling( void ) const; + + //! Get the translational part of this transformation. + /** \returns \c Vec3f that describes the translational part */ + DP_MATH_API const Vec3f & getTranslation( void ) const; + + /*! \brief Get the current transformation. + * \return The \c Mat44f that describes the transformation. + * \remarks The transformation is the concatenation of the center translation C, scale + * orientation SO, scaling S, rotation R, and translation T, by the following formula: + * \code + * M = -C * SO^-1 * S * SO * R * C * T + * \endcode + * \sa getInverse */ + DP_MATH_API const Mat44f& getMatrix( void ) const; + + /*! \brief Get the current inverse transformation. + * \return The \c Mat44f that describes the inverse transformation. + * \remarks The inverse transformation is the concatenation of the center translation C, + * scale orientation SO, scaling S, rotation R, and translation T, by the following + * formula: + * \code + * M = T^-1 * C^-1 * R^-1 * SO^-1 * S^-1 * SO * -C^-1 + * \endcode + * \sa getMatrix */ + DP_MATH_API Mat44f getInverse( void ) const; + + //! Set the center of ration of the transformation. + DP_MATH_API void setCenter( const Vec3f ¢er //!< center of rotation + ); + + //! Set the \c Trafo to identity. + DP_MATH_API void setIdentity( void ); + + //! Set the rotational part of the transformation, using a quaternion. + DP_MATH_API void setOrientation( const Quatf &orientation //!< rotational part of transformation + ); + + //! Set the scale orientational part of the transformation. + DP_MATH_API void setScaleOrientation( const Quatf &scaleOrientation //!< scale orientational part of transform + ); + + //! Set the scaling part of the transformation. + DP_MATH_API void setScaling( const Vec3f &scaling //!< scaling part of transformation + ); + + //! Set the translational part of the transformation. + DP_MATH_API void setTranslation( const Vec3f &translation //!< translational part of transformation + ); + + //! Set the complete transformation by a Mat44f. + /** The matrix is internally decomposed into a translation, rotation, scaleOrientation, and scaling. */ + DP_MATH_API void setMatrix( const Mat44f &matrix //!< complete transformation + ); + + //! Copy operator. + DP_MATH_API Trafo & operator=( const Trafo &rhs //!< Trafo to copy + ); + + //! Equality operator. + /** \returns \c true if \c this is equal to \a t, otherwise \c false */ + DP_MATH_API bool operator==( const Trafo &t //!< \c Trafo to compare with + ) const; + + DP_MATH_API bool operator!=( const Trafo &t ) const; + + private: + DP_MATH_API void decompose() const; + + mutable Mat44f m_matrix; + mutable Vec3f m_center; //!< center of rotation + mutable Quatf m_orientation; //!< orientational part of the transformation + mutable Quatf m_scaleOrientation; //!< scale orientation + mutable Vec3f m_scaling; //!< scaling part of the transformation + mutable Vec3f m_translation; //!< translational part of the transformation + + mutable bool m_matrixValid; + mutable bool m_decompositionValid; + }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*! \relates dp::math::Trafo + * This calculates the linear interpolation \code ( 1 - alpha ) * t0 + alpha * t1 \endcode */ + DP_MATH_API Trafo lerp( float alpha //!< interpolation parameter + , const Trafo &t0 //!< starting value + , const Trafo &t1 //!< ending value + ); + + DP_MATH_API void lerp( float alpha, const Trafo & t0, const Trafo & t1, Trafo & tr ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlines + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline const Vec3f & Trafo::getCenter( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_center ); + } + + inline const Quatf & Trafo::getOrientation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_orientation ); + } + + inline const Quatf & Trafo::getScaleOrientation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_scaleOrientation ); + } + + inline const Vec3f & Trafo::getScaling( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_scaling ); + } + + inline const Vec3f & Trafo::getTranslation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_translation ); + } + + inline void Trafo::setCenter( const Vec3f ¢er ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_center = center; + } + + inline void Trafo::setOrientation( const Quatf &orientation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_orientation = orientation; + } + + inline void Trafo::setScaleOrientation( const Quatf &scaleOrientation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_scaleOrientation = scaleOrientation; + } + + inline void Trafo::setScaling( const Vec3f &scaling ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_scaling = scaling; + } + + inline void Trafo::setTranslation( const Vec3f &translation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_translation = translation; + } + + inline bool Trafo::operator!=( const Trafo & t ) const + { + return( ! ( *this == t ) ); + } + + inline void lerp( float alpha, const Trafo & t0, const Trafo & t1, Trafo & tr ) + { + tr = lerp( alpha, t0, t1 ); + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/Vecnt.h b/apps/MDL_sdf/dp/math/Vecnt.h new file mode 100644 index 00000000..8c1061dc --- /dev/null +++ b/apps/MDL_sdf/dp/math/Vecnt.h @@ -0,0 +1,1223 @@ +// Copyright (c) 2002-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include +#include +#include + +//#include +#include "inc/MyAssert.h" + +namespace dp +{ + namespace math + { + + template class Spherent; + + /*! \brief Vector class of fixed size and type. + * \remarks This class is templated by size and type. It holds \a n values of type \a T. There + * are typedefs for the most common usage with 2, 3, and 4 values of type \c float and \c double: + * Vec2f, Vec2d, Vec3f, Vec3d, Vec4f, Vec4d. */ + template class Vecnt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons, no initialization is performed. */ + Vecnt(); + + /*! \brief Copy constructor from a vector of possibly different size and type. + * \param rhs A vector with \a m values of type \a S. + * \remarks The minimum \a k of \a n and \a m is determined. The first \a k values of type \a + * S from \a rhs are converted to type \a T and assigned as the first \a k values of \c this. + * If \a k is less than \a n, the \a n - \a k last values of \c this are not initialized. */ + template + explicit Vecnt( const Vecnt & rhs ); + + /*! \brief Copy constructor from a vector with one less value than \c this, and an explicit last value. + * \param rhs A vector with \a m values of type \a S, where \a m has to be one less than \a n. + * \param last A single value of type \a R, that will be set as the last value of \c this. + * \remarks This constructor contains a compile-time assertion, to make sure that \a m is one + * less than \a n. The values of \a rhs of type \a S are converted to type \a T and assigned + * as the first values of \c this. The value \a last of type \a R also is converted to type + * \a T and assigned as the last value of \c this. + * \par Example: + * \code + * Vec3f v3f(0.0f,0.0f,0.0f); + * Vec4f v4f(v3f,1.0f); + * \endcode */ + template + Vecnt( const Vecnt & rhs, R last ); + + /*! \brief Constructor for a one-element vector. + * \param x First element of the vector. + * \remarks This constructor can only be used with one-element vectors. + * \par Example: + * \code + * Vec1f v1f( 1.0f ); + * Vecnt<1,int> v1i( 0 ); + * \endcode */ + Vecnt( T x); + + /*! \brief Constructor for a two-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \remarks This constructor can only be used with two-element vectors. + * \par Example: + * \code + * Vec2f v2f( 1.0f, 2.0f ); + * Vecnt<2,int> v2i( 0, 1 ); + * \endcode */ + Vecnt( T x, T y ); + + /*! \brief Constructor for a three-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \param z Third element of the vector. + * \remarks This constructor contains a compile-time assertion, to make sure it is used for + * three-element vectors, like Vec3f, only. + * \par Example: + * \code + * Vec3f v3f( 1.0f, 2.0f, 3.0f ); + * Vecnt<3,int> v3i( 0, 1, 2 ); + * \endcode */ + Vecnt( T x, T y, T z ); + + /*! \brief Constructor for a four-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \param z Third element of the vector. + * \param w Fourth element of the vector. + * \remarks This constructor contains a compile-time assertion, to make sure it is used for + * four-element vectors, like Vec4f, only. + * \par Example: + * \code + * Vec4f v4f( 1.0f, 2.0f, 3.0f, 4.0f ); + * Vecnt<4,int> v4i( 0, 1, 2, 3 ); + * \endcode */ + Vecnt( T x, T y, T z, T w ); + + public: + /*! \brief Get a pointer to the constant values of this vector. + * \return A pointer to the constant values of this vector. + * \remarks It is assured, that the values of a vector are contiguous. + * \par Example: + * \code + * GLColor3fv( p->getDiffuseColor().getPtr() ); + * \endcode */ + const T * getPtr() const; + + /*! \brief Normalize this vector and get it's previous length. + * \return The length of the vector before the normalization. */ + T normalize(); + + /*! \brief Access operator to the values of a vector. + * \param i The index of the value to access. + * \return A reference to the value at position \a i in this vector. + * \remarks The index \a i has to be less than the size of the vector, given by the template + * argument \a n. + * \note The behavior is undefined if \ i is greater or equal to \a n. */ + T & operator[]( size_t i ); + + /*! \brief Constant access operator to the values of a vector. + * \param i The index of the value to access. + * \return A constant reference to the value at position \a i in this vector. + * \remarks The index \a i has to be less than the size of the vector, given by the template + * argument \a n. + * \note The behavior is undefined if \ i is greater or equal to \a n. */ + const T & operator[]( size_t i ) const; + + /*! \brief Vector assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to assign to \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise assigned to the values of \c this. */ + template + Vecnt & operator=( const Vecnt & rhs ); + + /*! \brief Vector addition and assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to add to \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise added to the values of \c this. */ + template + Vecnt & operator+=( const Vecnt & rhs ); + + /*! \brief Vector subtraction and assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to subtract from \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise subtracted from the values of \c this. */ + template + Vecnt & operator-=( const Vecnt & rhs ); + + /*! \brief Scalar multiplication and assignment operator with a scalar of possibly different type. + * \param s A scalar to multiply \c this with. + * \return A reference to \c this. + * \remarks The values of \c this are component-wise multiplied with \a s. */ + template + Vecnt & operator*=( S s ); + + /*! \brief Scalar division and assignment operator with a scalar of possibly different type. + * \param s A scalar to divide \c this by. + * \return A reference to \c this. + * \remarks The values of \c this are component-wise divided by \a s. + * \note The behavior is undefined if \a s is less than the type-dependent epsilon. */ + template + Vecnt & operator/=( S s ); + + /*! \brief Orthonormalize \c this with respect to the vector \a v. + * \param v A constant reference to the vector to orthonormalize \c this to. + * \remarks Subtracts the orthogonal projection of \c this on \a v from \c this and + * normalizes the it, resulting in a normalized vector that is orthogonal to \a v. */ + void orthonormalize( const Vecnt & v ); + + private: + T m_vec[n]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine the bounding box of a number of points. + * \param points A vector to the points. + * \param numberOfPoints The number of points in \a points. + * \param min Reference to the calculated lower left front vertex of the bounding box. + * \param max Reference to the calculated upper right back vertex of the bounding box. */ + template + void boundingBox( const Vecnt * points, size_t numberOfPoints, Vecnt & min, Vecnt & max ); + + /*! \brief Determine the bounding box of a number of points. + * \param points A random access iterator to the points. + * \param numberOfPoints The number of points in \a points. + * \param min Reference to the calculated lower left front vertex of the bounding box. + * \param max Reference to the calculated upper right back vertex of the bounding box. */ + template + inline void boundingBox( RandomAccessIterator points, size_t numberOfPoints, Vecnt & min, Vecnt & max ); + + /*! \brief Determine if two vectors point in opposite directions. + * \param v0 A constant reference to the first normalized vector to use. + * \param v1 A constant reference to the second normalized vector to use. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v0 and \a v1 are anti-parallel, otherwise \c false. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \sa areCollinear, isNormalized */ + template + bool areOpposite( const Vecnt & v0, const Vecnt & v1 + , T eps = std::numeric_limits::epsilon() ) + { + return( ( 1 + v0 * v1 ) <= eps ); + } + + /*! \brief Determine if two vectors are orthogonal. + * \param v0 A constant reference to the first vector to use. + * \param v1 A constant reference to the second vector to use. + * \param eps An optional deviation from orthonormality. The default is the type dependent + * epsilon. + * \return \c true, if \a v0 and \a v1 are orthogonal, otherwise \c false. + * \sa areOrthonormal */ + template + bool areOrthogonal( const Vecnt & v0, const Vecnt & v1 + , T eps = 2 * std::numeric_limits::epsilon() ) + { + return( std::abs(v0*v1) <= std::max(T(1),length(v0)) * std::max(T(1),length(v1)) * eps ); + } + + /*! \brief Determine if two vectors are orthonormal. + * \param v0 A constant reference to the first normalized vector to use. + * \param v1 A constant reference to the second normalized vector to use. + * \param eps An optional deviation from orthonormality. The default is the type dependent + * epsilon. + * \return \c true, if \a v0 and \a v1 are orthonormal, otherwise \c false. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \sa areOrthogonal */ + template + bool areOrthonormal( const Vecnt & v0, const Vecnt & v1 + , T eps = 2 * std::numeric_limits::epsilon() ) + { + return( isNormalized(v0) && isNormalized(v1) && ( std::abs(v0*v1) <= eps ) ); + } + + /*! \brief Determine if two vectors differ less than a given epsilon in each component. + * \param v0 A constant reference to the first vector to use. + * \param v1 A constant reference to the second vector to use. + * \param eps The acceptable deviation for each component. + * \return \c true, if \ v0 and \a v1 differ less than or equal to \a eps in each component, otherwise \c + * false. + * \sa distance, operator==() */ + template + bool areSimilar( const Vecnt & v0, const Vecnt & v1, T eps ); + + /*! \brief Determine the distance between vectors. + * \param v0 A constant reference to the first vector. + * \param v1 A constant reference to the second vector. + * \return The euclidean distance between \a v0 and \a v1. + * \sa length, lengthSquared */ + template + T distance( const Vecnt & v0, const Vecnt & v1 ); + + template + T intensity( const Vecnt & v ); + + /*! \brief Determine if a vector is normalized. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v is normalized, otherwise \c false. + * \sa isNull, length, lengthSquared, normalize */ + template + bool isNormalized( const Vecnt & v, T eps = 2 * std::numeric_limits::epsilon() ) + { + return( std::abs( length( v ) - 1 ) <= eps ); + } + + /*! \brief Determine if a vector is the null vector. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v is the null vector, otherwise \c false. + * \sa isNormalized, length, lengthSquared */ + template + bool isNull( const Vecnt & v, T eps = std::numeric_limits::epsilon() ) + { + return( length( v ) <= eps ); + } + + /*! \brief Determine if a vector is uniform, that is, all its components are equal. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if all components of \a v are equal, otherwise \c false. */ + template + bool isUniform( const Vecnt & v, T eps = std::numeric_limits::epsilon() ) + { + bool uniform = true; + for ( unsigned int i=1 ; i + T length( const Vecnt & v ); + + /*! \brief Determine the squared length of a vector. + * \param v A constant reference to the vector to use. + * \return The squared length of the vector \a v. + * \sa length */ + template + T lengthSquared( const Vecnt & v ); + + /*! \brief Determine the maximal element of a vector. + * \param v A constant reference to the vector to use. + * \return The largest absolute value of \a v. + * \sa minElement */ + template + T maxElement( const Vecnt & v ); + + /*! \brief Determine the minimal element of a vector. + * \param v A constant reference to the vector to use. + * \return The smallest absolute value of \a v. + * \sa maxElement */ + template + T minElement( const Vecnt & v ); + + /*! \brief Normalize a vector. + * \param v A reference to the vector to normalize. + * \return The length of the unnormalized vector. + * \sa isNormalized, length */ + template + T normalize( Vecnt & v ); + + /*! \brief Test for equality of two vectors. + * \param v0 A constant reference to the first vector to test. + * \param v1 A constant reference to the second vector to test. + * \return \c true, if the two vectors component-wise differ less than the type dependent + * epsilon, otherwise \c false. + * \sa operator!=() */ + template + bool operator==( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Test for inequality of two vectors. + * \param v0 A constant reference to the first vector to test. + * \param v1 A constant reference to the second vector to test. + * \return \c true, if the two vectors component-wise differ more than the type dependent + * epsilon in at least one component, otherwise \c false. + * \sa operator==() */ + template + bool operator!=( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Vector addition operator + * \param v0 A constant reference to the left operand. + * \param v1 A second reference to the right operand. + * \return A vector holding the component-wise sum of \a v0 and \a v1. + * \sa operator-() */ + template + Vecnt operator+( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Unary vector negation operator. + * \param v A constant reference to a vector. + * \return A vector holding the component-wise negation of \a v. + * \sa operator-() */ + template + Vecnt operator-( const Vecnt & v ); + + /*! \brief Vector subtraction operator. + * \param v0 A constant reference to the left operand. + * \param v1 A second reference to the right operand. + * \return A vector holding the component-wise difference of \a v0 and \a v1. + * \sa operator+() */ + template + Vecnt operator-( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Scalar multiplication of a vector. + * \param v A constant reference to the left operand. + * \param s A scalar as the right operand. + * \return A vector holding the component-wise product with s. + * \sa operator/() */ + template + Vecnt operator*( const Vecnt & v, S s ); + + /*! \brief Scalar multiplication of a vector. + * \param s A scalar as the left operand. + * \param v A constant reference to the right operand. + * \return A vector holding the component-wise product with \a s. + * \sa operator/() */ + template + Vecnt operator*( S s, const Vecnt & v ); + + /*! \brief Vector multiplication. + * \param v0 A constant reference to the left operand. + * \param v1 A constant reference to the right operand. + * \return The dot product of \a v0 and \a v1. + * \sa operator^() */ + template + T operator*( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Scalar division of a vector. + * \param v A constant reference to the left operand. + * \param s A scalar as the right operand. + * \return A vector holding the component-wise division by \a s. + * \sa operator*() */ + template + Vecnt operator/( const Vecnt & v, S s ); + + /*! \brief Determine a vector that's orthonormal to \a v. + * \param v A vector to determine an orthonormal vector to. + * \return A vector that's orthonormal to \a v. + * \note The result of this function is not uniquely defined. In two dimensions, the orthonormal + * to a vector can be one of two anti-parallel vectors. In higher dimensions, there are infinitely + * many possible results. This function just select one of them. + * \sa orthonormalize */ + template + Vecnt orthonormal( const Vecnt & v ); + + /*! \brief Determine the orthonormal vector of \a v1 with respect to \a v0. + * \param v0 A constant reference to the normalized vector to orthonormalize against. + * \param v1 A constant reference to the normalized vector to orthonormalize. + * \return A normalized vector representing the orthonormalized version of \a v1 with respect to + * \a v0. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \par Example: + * \code + * Vec3f newYAxis = orthonormalize( newZAxis, oldYAxis ); + * \endcode + * \sa normalize */ + template + Vecnt orthonormalize( const Vecnt & v0, const Vecnt & v1 ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 1 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a two-component vector. + * \param v A reference to the vector to set with \a x. + * \param x The first component to set. */ + template + Vecnt<1,T> & setVec( Vecnt<1,T> & v, T x ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 2 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a two-component vector. + * \param v A reference to the vector to set with \a x and \a y. + * \param x The first component to set. + * \param y The second component to set. */ + template + Vecnt<2,T> & setVec( Vecnt<2,T> & v, T x, T y ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine if two vectors are collinear. + * \param v0 A constant reference to the first vector. + * \param v1 A constant reference to the second vector. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v0 and \a v1 are collinear, otherwise \c false. + * \sa areOpposite */ + template + bool areCollinear( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 + , T eps = std::numeric_limits::epsilon() ) + { + return( length( v0 ^ v1 ) < eps ); + } + + /*! \brief Cross product operator. + * \param v0 A constant reference to the left operand. + * \param v1 A constant reference to the right operand. + * \return A vector that is the cross product of v0 and v1. + * \sa operator*() */ + template + Vecnt<3,T> operator^( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 ); + + /*! \brief Set the values of a three-component vector. + * \param v A reference to the vector to set with \a x, \a y and \a z. + * \param x The first component to set. + * \param y The second component to set. + * \param z The third component to set. */ + template + Vecnt<3,T> & setVec( Vecnt<3,T> & v, T x, T y, T z ); + + /*! \brief Determine smoothed normals for a set of vertices. + * \param vertices A constant reference to a vector of vertices. + * \param sphere A constant reference to the bounding sphere of the vertices. + * \param creaseAngle The angle in radians to crease at. + * \param normals A reference to a vector of normals. + * \remarks Given \a vertices with \a normals and their bounding \a sphere, this function + * calculates smoothed normals in \a normals for the vertices. For all vertices, that are similar + * within a tolerance that depends on the radius of \a sphere, and whose normals differ less than + * \a creaseAngle (in radians), their normals are set to the average of their normals. */ + template + void smoothNormals( const std::vector > &vertices, const Spherent<3,T> &sphere + , T creaseAngle, std::vector > &normals ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a four-component vector. + * \param v A reference to the vector to set with \a x, \a y, \a z, and \a w. + * \param x The first component to set. + * \param y The second component to set. + * \param z The third component to set. + * \param w The fourth component to set. */ + template + Vecnt<4,T> & setVec( Vecnt<4,T> & v, T x, T y, T z, T w ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Vecnt<1,float> Vec1f; + typedef Vecnt<1,double> Vec1d; + typedef Vecnt<2,float> Vec2f; + typedef Vecnt<2,double> Vec2d; + typedef Vecnt<3,float> Vec3f; + typedef Vecnt<3,double> Vec3d; + typedef Vecnt<4,float> Vec4f; + typedef Vecnt<4,double> Vec4d; + typedef Vecnt<1,int> Vec1i; + typedef Vecnt<2,int> Vec2i; + typedef Vecnt<3,int> Vec3i; + typedef Vecnt<4,int> Vec4i; + typedef Vecnt<1,unsigned int> Vec1ui; + typedef Vecnt<2,unsigned int> Vec2ui; + typedef Vecnt<3,unsigned int> Vec3ui; + typedef Vecnt<4,unsigned int> Vec4ui; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt::Vecnt() + { + } + + template + template + inline Vecnt::Vecnt( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt::Vecnt( const Vecnt & rhs, R last ) + { + for ( unsigned int i=0 ; i + inline Vecnt::Vecnt( T x ) + { + setVec( *this, x ); + } + + template + inline Vecnt::Vecnt( T x, T y ) + { + setVec( *this, x, y ); + } + + template + inline Vecnt::Vecnt( T x, T y, T z ) + { + setVec( *this, x, y, z ); + } + + template + inline Vecnt::Vecnt( T x, T y, T z, T w ) + { + setVec( *this, x, y, z, w ); + } + + template + inline const T * Vecnt::getPtr() const + { + return( &m_vec[0] ); + } + + template + inline T Vecnt::normalize() + { + return( dp::math::normalize( *this ) ); + } + + template + inline T & Vecnt::operator[]( size_t i ) + { + MY_ASSERT( i < n ); + return( m_vec[i] ); + } + + template + inline const T & Vecnt::operator[]( size_t i ) const + { + MY_ASSERT( i < n ); + return( m_vec[i] ); + } + + template + template + inline Vecnt & Vecnt::operator=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator+=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator-=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator*=( S s ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator/=( S s ) + { + for ( unsigned int i=0 ; i + inline void Vecnt::orthonormalize( const Vecnt & v ) + { + *this = dp::math::orthonormalize( v, *this ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline void boundingBox( const Vecnt * points, size_t numberOfPoints, Vecnt & min, Vecnt & max ) + { + MY_ASSERT( 0 < n ); + min = max = points[0]; + for ( size_t i=1 ; i + inline void boundingBox( RandomAccessIterator points, size_t numberOfPoints, Vecnt & min, Vecnt & max ) + { + MY_ASSERT( 0 < n ); + min = max = points[0]; + for ( size_t i=1 ; i + inline bool areSimilar( const Vecnt & v0, const Vecnt & v1, T eps ) + { + for ( unsigned int i=0 ; i + inline T distance( const Vecnt & v0, const Vecnt & v1 ) + { + return( length( v0 - v1 ) ); + } + + template + inline T intensity( const Vecnt & v ) + { + T intens(0); + for ( unsigned int i=0 ; i + inline T length( const Vecnt & v ) + { + return( sqrt( lengthSquared( v ) ) ); + } + + template + inline T lengthSquared( const Vecnt & v ) + { + return( v * v ); + } + + template + inline T maxElement( const Vecnt & v ) + { + T me = std::abs( v[0] ); + for ( unsigned int i=1 ; i + inline T minElement( const Vecnt & v ) + { + T me = std::abs( v[0] ); + for ( unsigned int i=1 ; i + inline T normalize( Vecnt & v ) + { + T norm = length( v ); + if ( std::numeric_limits::epsilon() < norm ) + { + v /= norm; + } + return( norm ); + } + + template + inline float normalize( Vecnt & v ) + { + Vecnt vd(v); + double norm = normalize( vd ); + v = vd; + return( (float)norm ); + } + + template + inline bool operator==( const Vecnt & v0, const Vecnt & v1 ) + { + for (unsigned int i = 0; i < n; i++) + { + if (v0[i] != v1[i]) + { + return(false); + } + } + return(true); + } + + template + inline bool operator==(const Vecnt<1, T> & v0, const Vecnt<1, T> & v1) + { + return (v0[0] == v1[0]); + } + + template + inline bool operator==(const Vecnt<2, T> & v0, const Vecnt<2, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]); + } + + + template + inline bool operator==(const Vecnt<3, T> & v0, const Vecnt<3, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]) && (v0[2] == v1[2]); + } + + template + inline bool operator==(const Vecnt<4, T> & v0, const Vecnt<3, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]) && (v0[2] == v1[2]) && (v0[3] == v1[3]); + } + + template + inline bool operator!=( const Vecnt & v0, const Vecnt & v1 ) + { + return( ! ( v0 == v1 ) ); + } + + template + inline Vecnt operator+( const Vecnt & v0, const Vecnt & v1 ) + { + Vecnt ret(v0); + ret += v1; + return( ret ); + } + + template + inline Vecnt operator-( const Vecnt & v ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Vecnt operator-( const Vecnt & v0, const Vecnt & v1 ) + { + Vecnt ret(v0); + ret -= v1; + return( ret ); + } + + template + inline Vecnt operator*( const Vecnt & v, S s ) + { + Vecnt ret(v); + ret *= s; + return( ret ); + } + + template + inline Vecnt operator*( S s, const Vecnt & v ) + { + return( v * s ); + } + + template + inline T operator*( const Vecnt & v0, const Vecnt & v1 ) + { + T ret(0); + for ( unsigned int i=0 ; i + inline Vecnt operator/( const Vecnt & v, S s ) + { + Vecnt ret(v); + ret /= s; + return( ret ); + } + + template + inline bool operator<(const Vecnt& lhs, const Vecnt& rhs) + { + for ( unsigned int i=0 ; i + inline bool operator>(const Vecnt& lhs, const Vecnt& rhs) + { + for ( unsigned int i=0 ; i + inline Vecnt orthonormal( const Vecnt & v ) + { + MY_STATIC_ASSERT( 1 < n ); + MY_STATIC_ASSERT( ! std::numeric_limits::is_integer ); + + T firstV(-1), secondV(-1); + unsigned int firstI(0), secondI(0); + Vecnt result; + for ( unsigned int i=0 ; i + inline Vecnt orthonormalize( const Vecnt & v0, const Vecnt & v1 ) + { + // determine the orthogonal projection of v1 on v0 : ( v0 * v1 ) * v0 + // and subtract it from v1 resulting in the orthogonalized version of v1 + Vecnt vr = v1 - ( v0 * v1 ) * v0; + vr.normalize(); + // don't assert the general case, because this orthonormalization is far from exact + return( vr ); + } + + template + inline Vecnt<3,T> orthonormalize( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ) + { + Vecnt<3,T> vr = v0 ^ ( v1 ^ v0 ); + vr.normalize(); + return( vr ); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 1 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<1,T> & setVec( Vecnt<1,T> & v, T x ) + { + v[0] = x; + return( v ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 2 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<2,T> & setVec( Vecnt<2,T> & v, T x, T y ) + { + v[0] = x; + v[1] = y; + return( v ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<3,T> operator^( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 ) + { + Vecnt<3,T> ret; + ret[0] = v0[1] * v1[2] - v0[2] * v1[1]; + ret[1] = v0[2] * v1[0] - v0[0] * v1[2]; + ret[2] = v0[0] * v1[1] - v0[1] * v1[0]; + return( ret ); + } + + template + inline Vecnt<3,T> & setVec( Vecnt<3,T> & v, T x, T y, T z ) + { + v[0] = x; + v[1] = y; + v[2] = z; + return( v ); + } + + template + size_t _hash( const Vecnt<3,T> &vertex, const Vecnt<3,T> &base, const Vecnt<3,T> &scale + , size_t numVertices ) + { + #if 1 + size_t value = static_cast(floor( ( vertex - base ) * scale )); + MY_ASSERT( value < numVertices ); + return( value ); + #else + int value = (int) floor( ( vertex - base ) * scale ); + if ( value < 0 ) + { + value = 0; + } + else if ( numVertices <= value ) + { + value = (int) numVertices - 1; + } + return( value ); + #endif + } + + template + inline void smoothNormals( const std::vector > &vertices, const Spherent<3,T> &sphere + , T creaseAngle, std::vector > &normals ) + { + if ( creaseAngle < T(0.01) ) + { + for_each( normals.begin(), normals.end(), std::mem_fun_ref(&Vecnt<3,T>::normalize) ); + } + else + { + // the face normals are un-normalized (for weighting them in the smoothing process) + // and we need the normalized too + std::vector > normalizedNormals(normals); + for_each( normalizedNormals.begin(), normalizedNormals.end() + , std::mem_fun_ref(&Vecnt<3,T>::normalize) ); + + // the tolerance to distinguish different points is a function of the given radius + T tolerance = sphere.getRadius() / 10000; + + // the toleranceVector is used to determine all slices of the bounding box (_hash entries) + // within the given tolerance from the current point + Vecnt<3,T> toleranceVector( tolerance, tolerance, tolerance ); + + // we create a _hash by using diagonal slices through the bounding box (or some approximation + // to it) + T halfWidth = sphere.getRadius() + tolerance; + Vecnt<3,T> hashBase = sphere.getCenter() - Vecnt<3,T>( halfWidth, halfWidth, halfWidth ); + + // The _hash function is a linear function of x, y, and z such that one corner of the + // bounding box (hashBase) maps to the key "0" and the opposite corner maps to the key + // "numVertices()". + T scale = vertices.size() / ( 3 * 2 * halfWidth ) ; + Vecnt<3,T> hashScale( scale, scale, scale ); + + // The "indirect" table is a circularly linked list of indices that are within tolerance of + // each other. + std::vector indirect( vertices.size() ); + + // create a _hash table as a vector of lists of indices into the vertex array + std::vector > hashTable( vertices.size() ); + for ( size_t i=0 ; i::const_iterator hlit=hashTable[hv].begin() + ; ! found && hlit!=hashTable[hv].end() + ; ++hlit ) + { + if ( areSimilar( vertices[i], vertices[*hlit], tolerance ) ) + { + indirect[i] = indirect[*hlit]; + indirect[*hlit] = i; + found = true; + } + } + } + + if ( ! found ) + { + // there is no similar (previous) point, so add it to the hashTable + size_t hashValue = _hash( vertices[i], hashBase, hashScale, vertices.size() ); + hashTable[hashValue].push_back( i ); + indirect[i] = i; + } + } + + // now the indirect vector maps all vertices i to the (similar) vertex j + std::vector > vertexNormals( vertices.size() ); + T cosCreaseAngle = cos( creaseAngle ); + for ( size_t i=0 ; i sum = normals[i]; + for ( size_t j=indirect[i] ; j!=i ; j=indirect[j] ) + { + if ( normalizedNormals[i] * normalizedNormals[j] > cosCreaseAngle ) + { + sum += normals[j]; + } + } + sum.normalize(); + vertexNormals[i] = sum; + } + + // finally, set the vertex normals + normals.swap( vertexNormals ); + } + } + + /*! \brief Compute the scalar triple product (v0 ^ v1) * v2. + **/ + template + inline T scalarTripleProduct( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1, const Vecnt<3,T> &v2 ) + { + return (v0 ^ v1) * v2; + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<4,T> & setVec( Vecnt<4,T> & v, T x, T y, T z, T w ) + { + v[0] = x; + v[1] = y; + v[2] = z; + v[3] = w; + return( v ); + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/math.h b/apps/MDL_sdf/dp/math/math.h new file mode 100644 index 00000000..4177e822 --- /dev/null +++ b/apps/MDL_sdf/dp/math/math.h @@ -0,0 +1,401 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include +#include + +//#include +#include "inc/MyAssert.h" +#include + + +namespace dp +{ + namespace math + { + + // define the stand trig values + #ifdef PI + # undef PI + # undef PI_HALF + # undef PI_QUARTER + #endif + //! constant PI + const float PI = 4 * atan( 1.0f ); + //! constant PI half + const float PI_HALF = 0.5f * PI; + //! constant PI quarter + const float PI_QUARTER = 0.25f * PI; + //! constant square root two + const float SQRT_TWO = sqrt( 2.0f ); + //! constant square root two half + const float SQRT_TWO_HALF = 0.5f * SQRT_TWO; + //! constant square root three + const float SQRT_THREE = sqrt( 3.0f ); + //! constant square root three + const float SQRT_THREE_HALF = 0.5f * SQRT_THREE; + + const double LOG_TWO = log( 2.0f ); + const double ONE_OVER_LOG_TWO = 1.0f / LOG_TWO; + + //! Template to clamp an object of type T to a lower and an upper limit. + /** \returns clamped value of \a v between \a l and \a u */ + template T clamp( T v //!< value to clamp + , T l //!< lower limit + , T u //!< upper limit + ) + { + return std::min(u, std::max(l,v)); + } + + //! Template to cube an object of Type T. + /** \param t value to cube + * \returns triple product of \a t with itself */ + template + inline T cube( const T& t ) + { + return( t * t * t ); + } + + //! Transform an angle in degrees to radians. + /** \returns angle in radians */ + template + inline T degToRad( T angle ) + { + return( angle*(PI/180) ); + } + + template + inline T exp2( T x ) + { + return( (T)pow( T(2), x ) ); + } + + /*! \brief Returns the highest bit set for the specified positive input value */ + template + inline int highestBit( T i ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i ); + int hb = -1; + for ( T h = i ; h ; h>>=1, hb++ ) + ; + return hb; + } + + /*! \brief Returns the highest bit value for the specified positive input value */ + template + inline T highestBitValue(T i) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i ); + T h = 0; + while (i) + { + h = (i & (~i+1)); // grab lowest bit + i &= ~h; // clear lowest bit + } + return h; + } + + /*! \brief Calculate the vertical field of view out of the horizontal field of view */ + inline float horizontalToVerticalFieldOfView( float hFoV, float aspectRatio ) + { + return( 2.0f * atanf( tanf( 0.5f * hFoV ) / aspectRatio ) ); + } + + //! Determine if an integer is a power of two. + /** \returns \c true if \a n is a power of two, otherwise \c false */ + template + inline bool isPowerOfTwo( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + return (n && !(n & (n-1))); + } + + //! Linear interpolation between two values \a v0 and \a v1. + /** v = ( 1 - alpha ) * v0 + alpha * v1 */ + template + inline T lerp( float alpha //!< interpolation parameter + , const T &v0 //!< starting value + , const T &v1 //!< ending value + ) + { + return( (T)( ( 1.0f - alpha ) * v0 + alpha * v1 ) ); + } + + template + inline void lerp( float alpha, const T & v0, const T & v1, T & vr ) + { + vr = (T)( ( 1.0f - alpha ) * v0 + alpha * v1 ); + } + + template + inline double log2( T x ) + { + return( ONE_OVER_LOG_TWO * log( x ) ); + } + + /*! \brief Determine the maximal value out of three. + * \param a A constant reference of the first value to consider. + * \param b A constant reference of the second value to consider. + * \param c A constant reference of the third value to consider. + * \return A constant reference to the maximal value out of \a a, \a b, and \a c. + * \sa min */ + template + inline const T & max( const T &a, const T &b, const T &c ) + { + return( std::max( a, std::max( b, c ) ) ); + } + + /*! \brief Determine the minimal value out of three. + * \param a A constant reference of the first value to consider. + * \param b A constant reference of the second value to consider. + * \param c A constant reference of the third value to consider. + * \return A constant reference to the minimal value out of \a a, \a b, and \a c. + * \sa max */ + template + inline const T & min( const T &a, const T &b, const T &c ) + { + return( std::min( a, std::min( b, c ) ) ); + } + + /*! \brief Determine the smallest power of two equal to or larger than \a n. + * \param n The value to determine the nearest power of two above. + * \return \a n, if \a n is a power of two, otherwise the smallest power of two above \a n. + * \sa powerOfTowBelow */ + template + inline T powerOfTwoAbove( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return( n ); + } + else + { + return highestBitValue(n) << 1; + } + } + + /*! \brief Determine the largest power of two equal to or smaller than \a n. + * \param n The value to determine the nearest power of two below. + * \return \a n, if \a n is a power of two, otherwise the largest power of two below \a n. + * \sa powerOfTowAbove */ + template + inline T powerOfTwoBelow( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return( n ); + } + else + { + return highestBitValue(n); + } + } + + /*! \brief Calculates the nearest power of two for the specified integer. + * \param n Integer for which to calculate the nearest power of two. + * \returns nearest power of two for integer \a n. */ + template + inline T powerOfTwoNearest( T n) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return n; + } + + T prev = highestBitValue(n); // POT below + T next = prev<<1; // POT above + return (next-n) < (n-prev) ? next : prev; // return nearest + } + + //! Transform an angle in radian to degree. + /** \param angle angle in radians + * \returns angle in degrees */ + inline float radToDeg( float angle ) + { + return( angle*180.0f/PI ); + } + + //! Determine the sign of a scalar. + /** \param t scalar value + * \returns sign of \a t */ + template + inline int sign( const T &t ) + { + return( ( t < 0 ) ? -1 : ( ( t > 0 ) ? 1 : 0 ) ); + } + + //! Solve the quadratic equation a*x^2 + b*x + c = 0 + /** \param a Coefficient of the quadratic term. + * \param b Coefficient of the linear term. + * \param c Coefficient of the constant term. + * \param x0 Reference to hold the first of up to two solutions. + * \param x1 Reference to hold the second of up to two solutions. + * \returns The number of real solutions (0, 1, or 2) + * \remarks If 2 is returned, x0 and x1 hold those two solutions. If 1 is returned, it is a double solution + * (turning point), returned in x0. If 0 is returned, there are only two complex conjugated solution, that + * are not calculated here. */ + template + inline unsigned int solveQuadraticEquation( T a, T b, T c, T & x0, T & x1 ) + { + if ( std::numeric_limits::epsilon() < fabs( a ) ) + { + T D = b * b - 4 * a * c; + if ( 0 < D ) + { + D = sqrt( D ); + x0 = 0.5f * ( - b + D ) / a; + x1 = 0.5f * ( - b - D ) / a; + return( 2 ); + } + if ( D < 0 ) + { + return( 0 ); + } + x0 = - 0.5f * b / a; + return( 1 ); + } + if ( std::numeric_limits::epsilon() < fabs( b ) ) + { + x0 = - c / b; + return( 1 ); + } + return( 0 ); + } + + //! Solve the cubic equation a*x^3 + b*x^2 + c*x + d = 0 + /** \param a Coefficient of the cubic term. + * \param b Coefficient of the quadratic term. + * \param c Coefficient of the linear term. + * \param d Coefficient of the constant term. + * \param x0 Reference to hold the first of up to three solutions. + * \param x1 Reference to hold the second of up to three solutions. + * \param x2 Reference to hold the second of up to three solutions. + * \returns The number of real solutions (0, 1, 2, or 3) + * \remarks If the absolute value of \a a is less than the type specific epsilon, the cubic equation is handled + * as a quadratic only. + * If 3 is returned, x0, x1, and x2 hold those three solutions. If 2 is returned, the cubic equation + * in fact was a quadratic one, and x0 and x1 hold those two solutions. If 1 is returned, the cubic equation + * has two conjugate complex, and one real solution; as complex numbers are not treated by this function, it is + * just the real solution returned in x0. If 0 is returned, the cubic equationn again war in fact a quadratic one, + * and there are only two complex conjugated solution, that are not calculated here. */ + template + inline unsigned int solveCubicEquation( T a, T b, T c, T d, T & x0, T & x1, T & x2 ) + { + if ( std::numeric_limits::epsilon() < abs( a ) ) + { + T bOverThreeA = b / ( 3 * a ); + T p = - square( bOverThreeA ) + c / ( 3 * a ); + T q = cube( bOverThreeA ) - T(0.5) * bOverThreeA * c / a + T(0.5) * d / a; + T discriminant = square( q ) + cube( p ); + if ( discriminant < - std::numeric_limits::epsilon() ) // < 0 ? + { + MY_ASSERT( p <= 0 ); + T f = 2 * sqrt( -p ); + T phi = acos( -q / sqrt( - cube( p ) ) ); + x0 = f * cos( phi / 3 ) - bOverThreeA; + x1 = - f * cos( ( phi + PI ) / 3 ) - bOverThreeA; + x2 = - f * cos( ( phi - PI ) / 3 ) - bOverThreeA; + return( 3 ); + } + else if ( std::numeric_limits::epsilon() < discriminant ) // > 0 ? + { + T t = sqrt( discriminant ); + T u = sign( - q + t ) * pow( abs( - q + t ), 1 / T(3) ); + T v = sign( - q - t ) * pow( abs( - q - t ), 1 / T(3) ); + x0 = u + v - bOverThreeA; + return( 1 ); // plus two complex solutions + } + else + { + T t = sign( - q ) * pow( abs( - q ), 1 / T(3) ); + x0 = 2 * t - bOverThreeA; + x1 = -t - bOverThreeA; + x2 = -t - bOverThreeA; + return( 3 ); + } + } + else + { + return( solveQuadraticEquation( b, c, d, x0, x1 ) ); + } + } + + //! Template to square an object of Type T. + /** \param t value to square + * \returns product of \a t with itself */ + template + inline T square( const T& t ) + { + return( t * t ); + } + + /*! \brief Calculate the horizontal field of view out of the vertical field of view */ + inline float verticalToHorizontalFieldOfView( float vFoV, float aspectRatio ) + { + return( 2.0f * atanf( tanf( 0.5f * vFoV ) * aspectRatio ) ); + } + + //! Compares two numerical values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template + inline int compare(T lhs, T rhs) + { + return (lhs == rhs) ? 0 : (lhs < rhs) ? -1 : 1; + } + + // specializations for float and double below + + //! Compares two float values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template<> + inline int compare(float lhs, float rhs) + { + return ((fabs(lhs-rhs) < FLT_EPSILON) ? 0 : (lhs < rhs) ? -1 : 1); + } + + //! Compares two double values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template<> + inline int compare(double lhs, double rhs) + { + return ((fabs(lhs-rhs) < DBL_EPSILON) ? 0 : (lhs < rhs) ? -1 : 1); + } + + DP_MATH_API float _atof( const std::string &str ); + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/src/Math.cpp b/apps/MDL_sdf/dp/math/src/Math.cpp new file mode 100644 index 00000000..0bc5799b --- /dev/null +++ b/apps/MDL_sdf/dp/math/src/Math.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +#include + + +namespace dp +{ + namespace math + { + + DP_MATH_API float _atof( const std::string &str ) + { + int pre = 0; + int post = 0; + float divisor = 1.0f; + bool negative = false; + size_t i = 0; + while ( ( i < str.length() ) && ( ( str[i] == ' ' ) || ( str[i] == '\t' ) ) ) + { + i++; + } + if ( ( i < str.length() ) && ( ( str[i] == '+' ) || ( str[i] == '-' ) ) ) + { + negative = ( str[i] == '-' ); + i++; + } + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + pre = pre * 10 + ( str[i] - 0x30 ); + i++; + } + if ( ( i < str.length() ) && ( str[i] == '.' ) ) + { + i++; + while ( ( i < str.length() ) && isdigit( str[i] ) && ( post <= INT_MAX/10-9 ) ) + { + post = post * 10 + ( str[i] - 0x30 ); + divisor *= 10; + i++; + } + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + i++; + } + } + if ( negative ) + { + pre = - pre; + post = - post; + } + float f = post ? pre + post / divisor : (float)pre; + if ( ( i < str.length() ) && ( ( str[i] == 'd' ) || ( str[i] == 'D' ) || ( str[i] == 'e' ) || ( str[i] == 'E' ) ) ) + { + i++; + negative = false; + if ( ( i < str.length() ) && ( ( str[i] == '+' ) || ( str[i] == '-' ) ) ) + { + negative = ( str[i] == '-' ); + i++; + } + int exponent = 0; + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + exponent = exponent * 10 + ( str[i] - 0x30 ); + i++; + } + if ( negative ) + { + exponent = - exponent; + } + f *= powf( 10.0f, float(exponent) ); + } + #if !defined( NDEBUG ) + float z = (float)atof( str.c_str() ); + MY_ASSERT( fabsf( f - z ) < FLT_EPSILON * std::max( 1.0f, fabsf( z ) ) ); + #endif + return( f ); + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/src/Matmnt.cpp b/apps/MDL_sdf/dp/math/src/Matmnt.cpp new file mode 100644 index 00000000..920c6ba0 --- /dev/null +++ b/apps/MDL_sdf/dp/math/src/Matmnt.cpp @@ -0,0 +1,411 @@ +// Copyright (c) 2012-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +using std::abs; + +namespace dp +{ + namespace math + { + const Mat44f cIdentity44f( { 1.0f, 0.0f, 0.0f, 0.0f + , 0.0f, 1.0f, 0.0f, 0.0f + , 0.0f, 0.0f, 1.0f, 0.0f + , 0.0f, 0.0f, 0.0f, 1.0f } ); + + template + static T _colNorm( const Matmnt &mat ) + { + T max(0); + for ( unsigned int i=0 ; i + static T _rowNorm( const Matmnt &mat ) + { + T max(0); + for ( unsigned int i=0 ; i + static int _maxColumn( const Matmnt &mat ) + { + T max(0); + int col(-1); + for ( unsigned int i=0 ; i + static void _makeReflector( Vecnt<3,T> &v ) + { + T s = sqrt( v * v ); + v[2] += ( v[2] < 0 ) ? -s : s; + s = sqrt( T(2) / ( v * v ) ); + v *= s; + } + + // Apply Householder reflection represented by u to column vectors of M + template + static void _reflectCols( Matmnt &M, const Vecnt &u ) + { + Vecnt v = u * M; + for ( unsigned int i=0 ; i + static void _reflectRows( Matmnt &M, const Vecnt &u ) + { + Vecnt v = M * u; + for ( unsigned int i=0 ; i write to mk + template + static void _decomposeRank1( Matmnt<3,3,T> & mk ) + { + // if rank(mk) is 1 we should find a non-zero column in M + int col = _maxColumn( mk ); + if ( col < 0 ) + { + // rank 0 + setIdentity( mk ); + } + else + { + Vecnt<3,T> v1( mk[0][col], mk[1][col], mk[2][col] ); + _makeReflector( v1 ); + _reflectCols( mk, v1 ); + Vecnt<3,T> v2( mk[2][0], mk[2][1], mk[2][2] ); + _makeReflector( v2 ); + _reflectRows( mk, v2 ); + T s = mk[2][2]; + setIdentity( mk ); + if ( s < 0 ) + { + mk[2][2] = T(-1); + } + _reflectCols( mk, v1 ); + _reflectRows( mk, v2 ); + } + } + + // Find orthogonal factor Q or rank 2 (or less) of mk using adjoint transpose -> write to mk + template + static void _decomposeRank2( Matmnt<3,3,T> & mk, const Matmnt<3,3,T> mAdjTk ) + { + // if rank(mk) is 2, we should find a non-zero column in mAdjTk + int col = _maxColumn( mAdjTk ); + if ( col < 0 ) + { + // rank 1 + _decomposeRank1( mk ); + } + else + { + Vecnt<3,T> v1( mAdjTk[0][col], mAdjTk[1][col], mAdjTk[2][col] ); + _makeReflector( v1 ); + _reflectCols( mk, v1 ); + Vecnt<3,T> v2 = mk[0] ^ mk[1]; + _makeReflector( v2 ); + _reflectRows( mk, v2 ); + if ( mk[0][1] * mk[1][0] < mk[0][0] * mk[1][1] ) + { + T c = mk[1][1] + mk[0][0]; + T s = mk[1][0] - mk[0][1]; + T d = sqrt( c * c + s * s ); + c /= d; + s /= d; + mk[0][0] = c; + mk[0][1] = -s; + mk[1][0] = s; + mk[1][1] = c; + } + else + { + T c = mk[1][1] - mk[0][0]; + T s = mk[1][0] + mk[0][1]; + T d = sqrt( c * c + s * s ); + c /= d; + s /= d; + mk[0][0] = -c; + mk[0][1] = s; + mk[1][0] = s; + mk[1][1] = c; + } + mk[0][2] = T(0); + mk[1][2] = T(0); + mk[2][0] = T(0); + mk[2][1] = T(0); + mk[2][2] = T(1); + _reflectCols( mk, v1 ); + _reflectRows( mk, v2 ); + } + } + + template + static T _polarDecomposition( const Matmnt<3,3,T> &mat, Matmnt<3,3,T> &rot, Matmnt<3,3,T> &sca ) + { + Matmnt<3,3,T> mk = ~mat; + T det; + T eCol; + T mCol = _colNorm( mk ); + T mRow = _rowNorm( mk ); + do + { + Matmnt<3, 3, T> mAdjTk{ mk[1] ^ mk[2], mk[2] ^ mk[0], mk[0] ^ mk[1] }; + det = mk[0] * mAdjTk[0]; + T absDet = abs( det ); + if ( std::numeric_limits::epsilon() < absDet ) + { + T mAdjTCol = _colNorm( mAdjTk ); + T mAdjTRow = _rowNorm( mAdjTk ); + T gamma = sqrt( sqrt( ( mAdjTCol * mAdjTRow ) / ( mCol * mRow ) ) / absDet ); + Matmnt<3,3,T> ek = mk; + mk = T(0.5) * ( gamma * mk + mAdjTk / ( gamma * det ) ); + ek -= mk; + eCol = _colNorm( ek ); + mCol = _colNorm( mk ); + mRow = _rowNorm( mk ); + } + else + { + // rank 2 + _decomposeRank2( mk, mAdjTk ); + break; + } + } while( ( 2 * mCol * std::numeric_limits::epsilon() ) < eCol ); + if ( det < T(0) ) + { + mk = - mk; + } + rot = ~mk; + + sca = mat * mk; + for ( unsigned int i=0 ; i<3 ; ++i ) + { + for ( unsigned int j=i+1 ; j<3 ; ++j ) + { + sca[i][j] = sca[j][i] = T(0.5) * ( sca[i][j] + sca[j][i] ); + } + } + return( det ); + } + + template + static Vecnt<3,T> _spectralDecomposition( const Matmnt<3,3,T> &mat, Matmnt<3,3,T> &u ) + { + setIdentity( u ); + Vecnt<3,T> diag( mat[0][0], mat[1][1], mat[2][2] ); + Vecnt<3,T> offd( mat[2][1], mat[0][2], mat[1][0] ); + bool done = false; + for ( int sweep=20 ; 0::epsilon() ); + if ( !done ) + { + done = true; + for ( int i=2 ; 0<=i ; i-- ) + { + int p = (i+1)%3; + int q = (p+1)%3; + T absOffDi = abs(offd[i]); + if ( std::numeric_limits::epsilon() < absOffDi ) + { + done = false; + T g = 100 * absOffDi; + T h = diag[q] - diag[p]; + T absh = abs( h ); + T t; + if ( absh + g == absh ) + { + t = offd[i] / h; + } + else + { + T theta = T(0.5) * h / offd[i]; + t = 1 / ( abs(theta) + sqrt(theta*theta+1) ); + if ( theta < 0 ) + { + t = -t; + } + } + T c = 1 / sqrt( t*t+1); + T s = t * c; + T tau = s / (c + 1); + T ta = t * offd[i]; + offd[i] = 0; + diag[p] -= ta; + diag[q] += ta; + T offdq = offd[q]; + offd[q] -= s * ( offd[p] + tau * offd[q] ); + offd[p] += s * ( offdq - tau * offd[p] ); + for ( int j=2 ; 0<=j ; j-- ) + { + T a = u[p][j]; + T b = u[q][j]; + u[p][j] -= s * ( b + tau * a ); + u[q][j] += s * ( a - tau * b ); + } + } + } + } + } + return( diag ); + } + + void decompose( const Matmnt<3,3,float> &mat, Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ) + { +#if 0 + + Matmnt<3,3,float> rot, sca; + float det = _polarDecomposition( mat, rot, sca ); +#if !defined(NDEBUG) + { + Matmnt<3,3,float> diff = mat - (float)sign(det) * sca * rot; + float eps = std::max( 1.0f, maxElement( sca ) ) * std::numeric_limits::epsilon(); + } +#endif + + orientation = Quatt(rot); + + // get the scaling out of sca + Matmnt<3,3,float> so; + scaling = (float)sign(det) * _spectralDecomposition( sca, so ); +#if !defined( NDEBUG ) + { + Matmnt<3,3,float> k( scaling[0], 0, 0, 0, scaling[1], 0, 0, 0, scaling[2] ); + Matmnt<3,3,float> diff = sca - ~so * k * so; + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + } +#endif + + scaleOrientation = Quatt( so ); + +#if !defined( NDEBUG ) + { + Matmnt<3,3,float> ms( scaling[0], 0, 0, 0, scaling[1], 0, 0, 0, scaling[2] ); + Matmnt<3,3,float> mso( so ); + Matmnt<3,3,float> diff = mat - ~mso * ms * mso * rot; + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + } +#endif + +#else + + Matmnt<3,3,double> rot, sca; + double det = _polarDecomposition( Matmnt<3,3,double>(mat), rot, sca ); +#if !defined(NDEBUG) + { + Matmnt<3,3,double> diff = Matmnt<3,3,double>(mat) - sca * rot; + double eps = std::max( 1.0, maxElement( sca ) ) * std::numeric_limits::epsilon(); + } +#endif + + orientation = Quatt(Quatt(rot)); + + // get the scaling out of sca + Matmnt<3,3,double> so; + scaling = _spectralDecomposition( sca, so ); +#if !defined( NDEBUG ) + { + Matmnt<3, 3, double> k( { (double)scaling[0], 0.0, 0.0, 0.0, (double)scaling[1], 0.0, 0.0, 0.0, (double)scaling[2] } ); + Matmnt<3,3,double> diff = sca - ~so * k * so; + double eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + int a = 0; + } +#endif + + scaleOrientation = Quatt(Quatt( so )); + +#if !defined( NDEBUG ) + Matmnt<3, 3, double> ms( { (double)scaling[0], 0.0, 0.0, 0.0, (double)scaling[1], 0.0, 0.0, 0.0, (double)scaling[2] } ); + Matmnt<3,3,double> mso( so ); + Matmnt<3,3,float> diff = mat - Matmnt<3,3,float>(~mso * ms * mso * rot); + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); +#endif + +#endif + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/src/Quatt.cpp b/apps/MDL_sdf/dp/math/src/Quatt.cpp new file mode 100644 index 00000000..6b79158a --- /dev/null +++ b/apps/MDL_sdf/dp/math/src/Quatt.cpp @@ -0,0 +1,83 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +namespace dp +{ + namespace math + { + template + Quatt _lerp( T alpha, const Quatt & q0, const Quatt & q1 ) + { + // cosine theta = dot product of q0 and q1 + T cosTheta = q0[0] * q1[0] + q0[1] * q1[1] + q0[2] * q1[2] + q0[3] * q1[3]; + + // if q1 is on opposite hemisphere from q0, use -q1 instead + bool flip = ( cosTheta < 0 ); + if ( flip ) + { + cosTheta = - cosTheta; + } + + T beta; + if ( 1 - cosTheta < std::numeric_limits::epsilon() ) + { + // if q1 is (within precision limits) the same as q0, just linear interpolate between q0 and q1. + beta = 1 - alpha; + } + else + { + // normal case + T theta = acos( cosTheta ); + T oneOverSinTheta = 1 / sin( theta ); + beta = sin( theta * ( 1 - alpha ) ) * oneOverSinTheta; + alpha = sin( theta * alpha ) * oneOverSinTheta; + } + + if ( flip ) + { + alpha = - alpha; + } + + return( Quatt( beta * q0[0] + alpha * q1[0] + , beta * q0[1] + alpha * q1[1] + , beta * q0[2] + alpha * q1[2] + , beta * q0[3] + alpha * q1[3] ) ); + } + + Quatt lerp( float alpha, const Quatt & q0, const Quatt & q1 ) + { + return( _lerp( alpha, q0, q1 ) ); + } + + void lerp( float alpha, const Quatt & q0, const Quatt & q1, Quatt &qr ) + { + qr = _lerp( alpha, q0, q1 ); + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/dp/math/src/Trafo.cpp b/apps/MDL_sdf/dp/math/src/Trafo.cpp new file mode 100644 index 00000000..844f1200 --- /dev/null +++ b/apps/MDL_sdf/dp/math/src/Trafo.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2002-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + + +#include + + +namespace dp +{ + namespace math + { + + Trafo::Trafo( void ) + : m_center( Vec3f( 0.0f, 0.0f, 0.0f ) ) + , m_orientation( Quatf( 0.0f, 0.0f, 0.0f, 1.0f ) ) + , m_scaleOrientation( Quatf( 0.0f, 0.0f, 0.0f, 1.0f ) ) + , m_scaling( Vec3f( 1.0f, 1.0f, 1.0f ) ) + , m_translation( Vec3f( 0.0f, 0.0f, 0.0f ) ) + , m_matrixValid( false ) + , m_decompositionValid( true ) + { + }; + + Trafo::Trafo( const Trafo &rhs ) + : m_matrixValid( rhs.m_matrixValid ) + , m_decompositionValid( rhs.m_decompositionValid ) + { + if ( m_matrixValid ) + { + m_matrix = rhs.m_matrix; + } + if ( m_decompositionValid ) + { + m_center = rhs.m_center; + m_orientation = rhs.m_orientation; + m_scaleOrientation = rhs.m_scaleOrientation; + m_scaling = rhs.m_scaling; + m_translation = rhs.m_translation; + } + } + + Trafo & Trafo::operator=( const Trafo & rhs ) + { + MY_ASSERT( rhs.m_matrixValid || rhs.m_decompositionValid ); + if (&rhs != this) + { + if ( rhs.m_decompositionValid ) + { + m_center = rhs.m_center; + m_orientation = rhs.m_orientation; + m_scaleOrientation = rhs.m_scaleOrientation; + m_scaling = rhs.m_scaling; + m_translation = rhs.m_translation; + } + if ( rhs.m_matrixValid ) + { + m_matrix = rhs.m_matrix; + } + + m_matrixValid = rhs.m_matrixValid; + m_decompositionValid = rhs.m_decompositionValid; + } + return *this; + } + + const Mat44f& Trafo::getMatrix( void ) const + { + MY_ASSERT( m_matrixValid || m_decompositionValid ); + + if ( !m_matrixValid ) + { + // Calculates -C * SO^-1 * S * SO * R * C * T, with + // C being the center translation + // SO being the scale orientation + // S being the scale + // R being the rotation + // T being the translation + Mat33f soInv( -m_scaleOrientation ); + Mat44f m0( Vec4f( soInv[0], 0.0f ) + , Vec4f( soInv[1], 0.0f ) + , Vec4f( soInv[2], 0.0f ) + , Vec4f( -m_center*soInv, 1.0f ) ); + Mat33f rot( m_scaleOrientation * m_orientation ); + Mat44f m1( Vec4f( m_scaling[0] * rot[0], 0.0f ) + , Vec4f( m_scaling[1] * rot[1], 0.0f ) + , Vec4f( m_scaling[2] * rot[2], 0.0f ) + , Vec4f( m_center + m_translation, 1.0f ) ); + m_matrix = m0 * m1; + m_matrixValid = true; + } + return( m_matrix ); + } + + Mat44f Trafo::getInverse( void ) const + { + dp::math::Mat44f inverse = getMatrix(); // Automatically makes the matrix valid. + if ( !inverse.invert() ) + { + // Inverting the matrix directly failed. + // Try the more robust but also more expensive decomposition. + // (Note that this still barely handles a uniform scaling of 2.2723e-6, + // while the direct matrix inversion already dropped below the + // std::numeric_limits::epsilon() of 2.2204460492503131e-016 for the determinant.) + if ( !m_decompositionValid ) + { + decompose(); + } + // Calculates T^-1 * C^-1 * R^-1 * SO^-1 * S^-1 * SO * -C^-1, with + // C being the center translation + // SO being the scale orientation + // S being the scale + // R being the rotation + // T being the translation + Mat33f rot( -m_orientation * -m_scaleOrientation ); + Mat44f m0; + m0[0] = Vec4f( rot[0], 0.0f ); + m0[1] = Vec4f( rot[1], 0.0f ); + m0[2] = Vec4f( rot[2], 0.0f ); + m0[3] = Vec4f( ( -m_center - m_translation ) * rot, 1.0f ); + + Mat33f so( m_scaleOrientation ); + + Mat44f m1; + m1[0] = Vec4f( so[0], 0.0f ) / m_scaling[0]; + m1[1] = Vec4f( so[1], 0.0f ) / m_scaling[1]; + m1[2] = Vec4f( so[2], 0.0f ) / m_scaling[2]; + m1[3] = Vec4f( m_center, 1.0f ); + + inverse = m0 * m1; + } + return inverse; + } + + void Trafo::setIdentity( void ) + { + m_center = Vec3f( 0.0f, 0.0f, 0.0f ); + m_orientation = Quatf( 0.0f, 0.0f, 0.0f, 1.0f ); + m_scaling = Vec3f( 1.0f, 1.0f, 1.0f ); + m_scaleOrientation = Quatf( 0.0f, 0.0f, 0.0f, 1.0f ); + m_translation = Vec3f( 0.0f, 0.0f, 0.0f ); + m_matrixValid = false; + m_decompositionValid = true; + } + + void Trafo::setMatrix( const Mat44f &matrix ) + { + m_matrix = matrix; + m_matrixValid = true; + m_decompositionValid = false; + } + + void Trafo::decompose() const + { + MY_ASSERT( m_matrixValid ); + MY_ASSERT( m_decompositionValid == false ); + + dp::math::decompose( m_matrix, m_translation, m_orientation, m_scaling, m_scaleOrientation); + + m_decompositionValid = true; + } + + + bool Trafo::operator==( const Trafo &t ) const + { + if ( m_decompositionValid && t.m_decompositionValid ) + { + return( + ( getCenter() == t.getCenter() ) + && ( getOrientation() == t.getOrientation() ) + && ( getScaling() == t.getScaling() ) + && ( getScaleOrientation() == t.getScaleOrientation() ) + && ( getTranslation() == t.getTranslation() ) ); + } + else + { + return getMatrix() == t.getMatrix(); + } + + } + + Trafo lerp( float alpha, const Trafo &t0, const Trafo &t1 ) + { + Trafo t; + t.setCenter( lerp( alpha, t0.getCenter(), t1.getCenter() ) ); + t.setOrientation( lerp( alpha, t0.getOrientation(), t1.getOrientation() ) ); + t.setScaling( lerp( alpha, t0.getScaling(), t1.getScaling() ) ); + t.setScaleOrientation( lerp( alpha, t0.getScaleOrientation(), t1.getScaleOrientation() ) ); + t.setTranslation( lerp( alpha, t0.getTranslation(), t1.getTranslation() ) ); + return( t ); + } + + } // namespace math +} // namespace dp diff --git a/apps/MDL_sdf/imgui/LICENSE b/apps/MDL_sdf/imgui/LICENSE new file mode 100644 index 00000000..b28ef225 --- /dev/null +++ b/apps/MDL_sdf/imgui/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 Omar Cornut and ImGui contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/MDL_sdf/imgui/imconfig.h b/apps/MDL_sdf/imgui/imconfig.h new file mode 100644 index 00000000..8abf26f3 --- /dev/null +++ b/apps/MDL_sdf/imgui/imconfig.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// Most options (memory allocation, clipboard callbacks, etc.) can be set at runtime via the ImGuiIO structure - ImGui::GetIO(). +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h) +// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" +// Note that options such as IMGUI_API, IM_VEC2_CLASS_EXTRA or ImDrawIdx needs to be defined consistently everywhere you include imgui.h, not only for the imgui*.cpp compilation units. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. +// DAR HACK Compiling directly into the application, not using DLLs. Not doing this will lead to "incosistent DLL definitions". +//#ifndef IMGUI_API +//# if imgui_EXPORTS /* Set by CMake */ +//# if defined( _WIN32 ) || defined( _WIN64 ) +//# define IMGUI_API __declspec( dllexport ) +//# endif +//# else /* imgui_EXPORTS */ +//# if defined( _WIN32 ) || defined( _WIN64 ) +//# define IMGUI_API __declspec( dllimport ) +//# endif +//# endif +//#endif + +//---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce likelihood of using already obsolete function/names +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Don't implement default handlers for Windows (so as not to link with certain functions) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // Don't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // Don't use and link with ImmGetContext/ImmSetCompositionWindow. + +//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) +//---- It is very strongly recommended to NOT disable the demo windows. Please read the comment at the top of imgui_demo.cpp. +//#define IMGUI_DISABLE_DEMO_WINDOWS + +//---- Don't implement ImFormatString(), ImFormatStringV() so you can reimplement them yourself. +//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +//---- Include imgui_user.h at the end of imgui.h as a convenience +//#define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Pack colors to BGRA8 instead of RGBA8 (if you needed to convert from one to another anyway) +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Implement STB libraries in a namespace to avoid linkage conflicts (defaults to global namespace) +//#define IMGUI_STB_NAMESPACE ImGuiStb + +//---- Define constructor and implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ + +//---- Use 32-bit vertex indices (instead of default 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it. +//#define ImDrawIdx unsigned int + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +/* +namespace ImGui +{ + void MyFunction(const char* name, const MyMatrix44& v); +} +*/ diff --git a/apps/MDL_sdf/imgui/imgui.cpp b/apps/MDL_sdf/imgui/imgui.cpp new file mode 100644 index 00000000..d6d9f40f --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui.cpp @@ -0,0 +1,13277 @@ +// dear imgui, v1.60 WIP +// (main code and documentation) + +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui +// Releases change-log at https://github.com/ocornut/imgui/releases +// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// This library is free but I need your support to sustain development and maintenance. +// If you work for a company, please consider financial support, see Readme. For individuals: https://www.patreon.com/imgui + +/* + + Index + - MISSION STATEMENT + - END-USER GUIDE + - PROGRAMMER GUIDE (read me!) + - Read first + - How to update to a newer version of Dear ImGui + - Getting started with integrating Dear ImGui in your code/engine + - Using gamepad/keyboard navigation [BETA] + - API BREAKING CHANGES (read me when you update!) + - ISSUES & TODO LIST + - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + - How can I help? + - How can I display an image? What is ImTextureID, how does it works? + - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels and the ID stack. + - How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + - How can I load a different font than the default? + - How can I easily use icons in my application? + - How can I load multiple fonts? + - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? + - How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + - I integrated Dear ImGui in my engine and the text or lines are blurry.. + - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + - ISSUES & TODO-LIST + - CODE + + + MISSION STATEMENT + ================= + + - Easy to use to create code-driven and data-driven tools + - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools + - Easy to hack and improve + - Minimize screen real-estate usage + - Minimize setup and maintenance + - Minimize state storage on user side + - Portable, minimize dependencies, run on target (consoles, phones, etc.) + - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, opening a tree node + for the first time, etc. but a typical frame won't allocate anything) + + Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: + - Doesn't look fancy, doesn't animate + - Limited layout features, intricate layouts are typically crafted in code + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) + - Controls are automatically adjusted for OSX to match standard OSX text editing operations. + - Gamepad navigation: see suggested mappings in imgui.h ImGuiNavInput_ + + + PROGRAMMER GUIDE + ================ + + READ FIRST + + - Read the FAQ below this section! + - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention + on your side, no state duplication, less sync, less bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + + - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed from the public API. + If you have a problem with a missing function/symbols, search for its name in the code, there will likely be a comment about it. + Please report any issue to the GitHub page! + - Try to keep your copy of dear imgui reasonably up to date. + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + + - Add the Dear ImGui source files to your projects, using your preferred build system. + It is recommended you build the .cpp files as part of your project and not as a library. + - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types. + - See examples/ folder for standalone sample applications. + - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + + - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize + (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic + integration you don't need to worry about it all. + - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory. + - Every frame: + - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.) + - Call ImGui::NewFrame() to begin the frame + - You can use any ImGui function you want between NewFrame() and Render() + - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler. + (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break) + - All rendering information are stored into command-lists until ImGui::Render() is called. + - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. + - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases + of your own application. + - Refer to the examples applications in the examples/ folder for instruction on how to setup your code. + - A minimal application skeleton may be: + + // Application init + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = 1920.0f; + io.DisplaySize.y = 1280.0f; + // TODO: Fill others settings of the io structure later. + + // Load texture atlas (there is a default font so you don't need to care about choosing a font yet) + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height); + // TODO: At this points you've got the texture data and you need to upload that your your graphic system: + MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA) + // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer. + io.Fonts->TexID = (void*)texture; + + // Application main loop + while (true) + { + // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.) + ImGuiIO& io = ImGui::GetIO(); + io.DeltaTime = 1.0f/60.0f; + io.MousePos = mouse_pos; + io.MouseDown[0] = mouse_button_0; + io.MouseDown[1] = mouse_button_1; + + // Call NewFrame(), after this point you can use ImGui::* functions anytime + ImGui::NewFrame(); + + // Most of your application code here + MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); + MyGameRender(); // may use any ImGui functions as well! + + // Render & swap video buffers + ImGui::Render(); + MyImGuiRenderFunction(ImGui::GetDrawData()); + SwapBuffers(); + } + + // Shutdown + ImGui::DestroyContext(); + + + - A minimal render function skeleton may be: + + void void MyRenderFunction(ImDrawData* draw_data) + { + // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + // TODO: Setup viewport, orthographic projection matrix + // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // The texture for the draw call is specified by pcmd->TextureId. + // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization. + MyEngineBindTexture(pcmd->TextureId); + + // We are using scissoring to clip some objects. All low-level graphics API supports it. + // If your engine doesn't support scissoring yet, you will get some small glitches (some elements outside their bounds) which you can fix later. + MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices. + MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + } + + - The examples/ folders contains many functional implementation of the pseudo-code above. + - When calling NewFrame(), the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'io.WantTextInput' flags are updated. + They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide + mouse inputs from the rest of your application. Read the FAQ below for more information about those flags. + + USING GAMEPAD/KEYBOARD NAVIGATION [BETA] + + - Ask questions and report issues at https://github.com/ocornut/imgui/issues/787 + - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable. + - Keyboard: + - Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays. + - When keyboard navigation is active (io.NavActive + NavFlags_EnableKeyboard), the io.WantCaptureKeyboard flag will be set. + For more advanced uses, you may want to read from: + - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. + - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used). + - or query focus information with e.g. IsWindowFocused(), IsItemFocused() etc. functions. + Please reach out if you think the game vs navigation input sharing could be improved. + - Gamepad: + - Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). + - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: + 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. + - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. + Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, maybe a power curve, etc.). + - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. + Please reach out if you think the game vs navigation input sharing could be improved. + - Mouse: + - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback. + - Consoles/Tablet/Phone users: Consider using Synergy host (on your computer) + uSynergy.c (in your console/tablet/phone app) to use your PC mouse/keyboard. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiNavFlags_MoveMouse flag in io.NavFlags. + Enabling ImGuiNavFlags_MoveMouse instructs dear imgui to move your mouse cursor along with navigation movements. + When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantMoveMouse' to notify you that it wants the mouse cursor to be moved. + When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that. + (If you set the ImGuiNavFlags_MoveMouse flag but don't honor 'io.WantMoveMouse' properly, imgui will misbehave as it will see your mouse as moving back and forth.) + (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want + to set a boolean to ignore your other external mouse positions until the external source is moved again.) + + + API BREAKING CHANGES + ==================== + + Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. + Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. + Also read releases logs https://github.com/ocornut/imgui/releases for more details. + + - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display. + - 2018/02/07 (1.60) - reorganized context handling to be more explicit, + - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. + - removed Shutdown() function, as DestroyContext() serve this purpose. + - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwhise CreateContext() will create its own font atlas instance. + - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. + - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. + - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. + - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. + - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side. + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. + Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). + - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). + - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame. + - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. + - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. + However if your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color. + ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) + { + float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; + return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); + } + If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDraw::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. + you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. + this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + became: + ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + argument 'cmd_lists' -> 'draw_data->CmdLists' + argument 'cmd_lists_count' -> 'draw_data->CmdListsCount' + ImDrawList 'commands' -> 'CmdBuffer' + ImDrawList 'vtx_buffer' -> 'VtxBuffer' + ImDrawList n/a -> 'IdxBuffer' (new) + ImDrawCmd 'vtx_count' -> 'ElemCount' + ImDrawCmd 'clip_rect' -> 'ClipRect' + ImDrawCmd 'user_callback' -> 'UserCallback' + ImDrawCmd 'texture_id' -> 'TextureId' + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + this sequence: + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + // + became: + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + // + io.Fonts->TexID = (your_texture_identifier); + you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + it is now recommended that you sample the font texture with bilinear interpolation. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. + (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + + ISSUES & TODO-LIST + ================== + See TODO.txt + + + FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + ====================================== + + Q: How can I help? + A: - If you are experienced with Dear ImGui and C++, look at the github issues, or TODO.txt and see how you want/can help! + - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README. + - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. + You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers. + But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. + - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately). + + Q: How can I display an image? What is ImTextureID, how does it works? + A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function. + Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry! + It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc. + At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render. + Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing. + (c++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!) + To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions. + Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. + You may call ImGui::ShowMetricsWindow() to explore active draw lists and visualize/understand how the draw data is generated. + It is your responsibility to get textures uploaded to your GPU. + + Q: Can I have multiple widgets with the same label? Can I have widget without a label? + A: Yes. A primer on labels and the ID stack... + + - Elements that are typically not clickable, such as Text() items don't need an ID. + + - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui often needs to remember what is + the "active" widget). to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. + + Button("OK"); // Label = "OK", ID = hash of "OK" + Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" + + - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" + in two different windows or in two different locations of a tree. + + - If you have a same ID twice in the same location, you'll have a conflict: + + Button("OK"); + Button("OK"); // ID collision! Both buttons will be treated as the same. + + Fear not! this is easy to solve and there are many ways to solve it! + + - When passing a label you can optionally specify extra unique ID information within string itself. + Use "##" to pass a complement to the ID that won't be visible to the end-user. + This helps solving the simple collision cases when you know which items are going to be created. + + Button("Play"); // Label = "Play", ID = hash of "Play" + Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above) + Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above) + + - If you want to completely hide the label, but still need an ID: + + Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!) + + - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels. + For example you may want to include varying information in a window title bar, but windows are uniquely identified by their ID.. + Use "###" to pass a label that isn't part of ID: + + Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" + Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) + + sprintf(buf, "My game (%f FPS)###MyGame", fps); + Begin(buf); // Variable label, ID = hash of "MyGame" + + - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window. + This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements. + You can push a pointer, a string or an integer value. Remember that ID are formed from the concatenation of _everything_ in the ID stack! + + for (int i = 0; i < 100; i++) + { + PushID(i); + Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj); + Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj->Name); + Button("Click"); // Label = "Click", ID = hash of string + "label" (unique) + PopID(); + } + + - More example showing that you can stack multiple prefixes into the ID stack: + + Button("Click"); // Label = "Click", ID = hash of "Click" + PushID("node"); + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + PushID(my_ptr); + Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click" + PopID(); + PopID(); + + - Tree nodes implicitly creates a scope for you by calling PushID(). + + Button("Click"); // Label = "Click", ID = hash of "Click" + if (TreeNode("node")) + { + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + TreePop(); + } + + - When working with trees, ID are used to preserve the open/close state of each tree node. + Depending on your use cases you may want to use strings, indices or pointers as ID. + e.g. when displaying a single object that may change over time (dynamic 1-1 relationship), using a static string as ID will preserve your + node open/closed state when the targeted object change. + e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. + experiment and see what makes more sense! + + Q: How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure. + - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application. + - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is + also generally ok, as the bool toggles fairly rarely and you don't generally expect to interact with either Dear ImGui or your application during + the same frame when that transition occurs. Dear ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, + so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered. + (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically + have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs + were for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) + + Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13) + A: Use the font atlas to load the TTF/OTF file you want: + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + + New programmers: remember that in C/C++ and most programming languages if you want to use a backslash \ in a string literal you need to write a double backslash "\\": + io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG + io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT + io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT + + Q: How can I easily use icons in my application? + A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your + strings. Read 'How can I load multiple fonts?' and the file 'misc/fonts/README.txt' for instructions and useful header files. + + Q: How can I load multiple fonts? + A: Use the font atlas to pack them into a single texture: + (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.) + + ImGuiIO& io = ImGui::GetIO(); + ImFont* font0 = io.Fonts->AddFontDefault(); + ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + // the first loaded font gets used by default + // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime + + // Options + ImFontConfig config; + config.OversampleH = 3; + config.OversampleV = 1; + config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up + config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); + + // Combine multiple fonts into one (e.g. for icon fonts) + ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontDefault(); + io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs + + Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? + A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. + + // Add default Japanese ranges + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); + + // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need) + ImVector ranges; + ImFontAtlas::GlyphRangesBuilder builder; + builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters) + builder.AddChar(0x7262); // Add a specific character + builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges + builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); + + All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax. + Specifying literal in your source code using a local code page (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work! + Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. + + Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that. + For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. + The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly. + + Q: How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' + so you don't rely on the default globals. + + Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + A: - You can create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, + push a ImGuiCol_WindowBg with zero alpha, then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. + - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows. + - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData. + + Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. + A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). + Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. + + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + A: You are probably mishandling the clipping rectangles in your render function. + Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). + + + - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. + this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". + - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle + of a deep nested inner loop in your code. + - tip: you can call Render() multiple times (e.g for VR renders). + - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui! + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // toupper, isprint +#include // NULL, malloc, free, qsort, atoi +#include // vsnprintf, sscanf, printf +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#define IMGUI_DEBUG_NAV_SCORING 0 +#define IMGUI_DEBUG_NAV_RECTS 0 + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great! +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' // +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#endif + +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif + +//------------------------------------------------------------------------- +// Forward Declarations +//------------------------------------------------------------------------- + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); + +static ImFont* GetDefaultFont(); +static void SetCurrentWindow(ImGuiWindow* window); +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); +static ImGuiWindow* FindHoveredWindow(); +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); +static void CheckStacksSize(ImGuiWindow* window, bool write); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); +static void AddWindowToDrawData(ImVector* out_list, ImGuiWindow* window); +static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); + +static ImGuiWindowSettings* AddWindowSettings(const char* name); + +static void LoadIniSettingsFromDisk(const char* ini_filename); +static void LoadIniSettingsFromMemory(const char* buf); +static void SaveIniSettingsToDisk(const char* ini_filename); +static void SaveIniSettingsToMemory(ImVector& out_buf); +static void MarkIniSettingsDirty(ImGuiWindow* window); + +static ImRect GetViewportRect(); + +static void ClosePopupToLevel(int remaining); +static ImGuiWindow* GetFrontMostModalRootWindow(); + +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size); +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size); +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2); +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format); + +namespace ImGui +{ +static void NavUpdate(); +static void NavUpdateWindowing(); +static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id); + +static void UpdateMovingWindow(); +static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]); +static void FocusFrontMostActiveWindow(ImGuiWindow* ignore_window); +} + +//----------------------------------------------------------------------------- +// Platform dependent default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(void* user_data); +static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +// Current context pointer. Implicitely used by all ImGui functions. Always assumed to be != NULL. +// CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext(). +// If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file. +// ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can: +// - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) +#ifndef GImGui +ImGuiContext* GImGui = NULL; +#endif + +// Memory Allocator functions. Use SetAllocatorFunctions() to change them. +// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file. +// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); } +#else +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); } +#endif + +static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper; +static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper; +static void* GImAllocatorUserData = NULL; +static size_t GImAllocatorActiveAllocationsCount = 0; + +//----------------------------------------------------------------------------- +// User facing structures +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + Alpha = 1.0f; // Global alpha applies to everything in ImGui + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns + ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + DisplaySafeAreaPadding = ImVec2(4,4); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. + AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + ImGui::StyleColorsClassic(this); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); + MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); +} + +ImGuiIO::ImGuiIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + + // Settings + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f/60.0f; + NavFlags = 0x00; + IniSavingRate = 5.0f; + IniFilename = "imgui.ini"; + LogFilename = "imgui_log.txt"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; + KeyRepeatDelay = 0.250f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); + + // Advanced/subtle behaviors +#ifdef __APPLE__ + OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + OptMacOSXBehaviors = false; +#endif + OptCursorBlink = true; + + // Settings (User Functions) + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; + ImeWindowHandle = NULL; + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + RenderDrawListsFn = NULL; +#endif + + // Input (NB: we already have memset zero the entire structure) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; +} + +// Pass in translated ASCII characters for text input. +// - with glfw you can get those from the callback set in glfwSetCharCallback() +// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message +void ImGuiIO::AddInputCharacter(ImWchar c) +{ + const int n = ImStrlenW(InputCharacters); + if (n + 1 < IM_ARRAYSIZE(InputCharacters)) + { + InputCharacters[n] = c; + InputCharacters[n+1] = '\0'; + } +} + +void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +{ + // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more + const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); + ImWchar wchars[wchars_buf_len]; + ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); + for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) + AddInputCharacter(wchars[i]); +} + +//----------------------------------------------------------------------------- +// HELPERS +//----------------------------------------------------------------------------- + +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 + +// Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) +{ + ImVec2 ap = p - a; + ImVec2 ab_dir = b - a; + float ab_len = sqrtf(ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y); + ab_dir *= 1.0f / ab_len; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + if (dot > ab_len) + return b; + return a + ab_dir * dot; +} + +bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) +{ + ImVec2 v0 = b - a; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + out_w = (v0.x * v2.y - v2.x * v0.y) / denom; + out_u = 1.0f - out_v - out_w; +} + +ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + ImVec2 proj_ab = ImLineClosestPoint(a, b, p); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + return proj_bc; + return proj_ca; +} + +int ImStricmp(const char* str1, const char* str2) +{ + int d; + while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + return d; +} + +int ImStrnicmp(const char* str1, const char* str2, size_t count) +{ + int d = 0; + while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + return d; +} + +void ImStrncpy(char* dst, const char* src, size_t count) +{ + if (count < 1) return; + strncpy(dst, src, count); + dst[count-1] = 0; +} + +char* ImStrdup(const char *str) +{ + size_t len = strlen(str) + 1; + void* buf = ImGui::MemAlloc(len); + return (char*)memcpy(buf, (const void*)str, len); +} + +char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + for ( ; str < str_end; str++) + if (*str == c) + return (char*)str; + return NULL; +} + +int ImStrlenW(const ImWchar* str) +{ + int n = 0; + while (*str++) n++; + return n; +} + +const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) +{ + if (!needle_end) + needle_end = needle + strlen(needle); + + const char un0 = (char)toupper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) + { + if (toupper(*haystack) == un0) + { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + return NULL; +} + +static const char* ImAtoi(const char* src, int* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + int v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS +int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + int w = vsnprintf(buf, buf_size, fmt, args); + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) +{ + int w = vsnprintf(buf, buf_size, fmt, args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +// Pass data_size==0 for zero-terminated strings +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImU32 ImHash(const void* data, int data_size, ImU32 seed) +{ + static ImU32 crc32_lut[256] = { 0 }; + if (!crc32_lut[1]) + { + const ImU32 polynomial = 0xEDB88320; + for (ImU32 i = 0; i < 256; i++) + { + ImU32 crc = i; + for (ImU32 j = 0; j < 8; j++) + crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); + crc32_lut[i] = crc; + } + } + + seed = ~seed; + ImU32 crc = seed; + const unsigned char* current = (const unsigned char*)data; + + if (data_size > 0) + { + // Known size + while (data_size--) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; + } + else + { + // Zero-terminated string + while (unsigned char c = *current++) + { + // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. + // Because this syntax is rarely used we are optimizing for the common case. + // - If we reach ### in the string we discard the hash so far and reset to the seed. + // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. + if (c == '#' && current[0] == '#' && current[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + return ~crc; +} + +//----------------------------------------------------------------------------- +// ImText* helpers +//----------------------------------------------------------------------------- + +// Convert UTF-8 to 32-bits character, process single character input. +// Based on stb_from_utf8() from github.com/nothings/stb/ +// We handle UTF-8 decoding error by skipping forward. +int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) +{ + unsigned int c = (unsigned int)-1; + const unsigned char* str = (const unsigned char*)in_text; + if (!(*str & 0x80)) + { + c = (unsigned int)(*str++); + *out_char = c; + return 1; + } + if ((*str & 0xe0) == 0xc0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 2) return 1; + if (*str < 0xc2) return 2; + c = (unsigned int)((*str++ & 0x1f) << 6); + if ((*str & 0xc0) != 0x80) return 2; + c += (*str++ & 0x3f); + *out_char = c; + return 2; + } + if ((*str & 0xf0) == 0xe0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 3) return 1; + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; + if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x0f) << 12); + if ((*str & 0xc0) != 0x80) return 3; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 3; + c += (*str++ & 0x3f); + *out_char = c; + return 3; + } + if ((*str & 0xf8) == 0xf0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 4) return 1; + if (*str > 0xf4) return 4; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; + if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x07) << 18); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 12); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 4; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return 4; + *out_char = c; + return 4; + } + *out_char = 0; + return 0; +} + +int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + ImWchar* buf_out = buf; + ImWchar* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) + char_count++; + } + return char_count; +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c >= 0xdc00 && c < 0xe000) return 0; + if (c >= 0xd800 && c < 0xdc00) return 4; + return 3; +} + +int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +{ + char* buf_out = buf; + const char* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); + } + *buf_out = 0; + return (int)(buf_out - buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_count; +} + +ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) +{ + float s = 1.0f/255.0f; + return ImVec4( + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; + return out; +} + +ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4& col) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = col; + c.w *= style.Alpha; + return ColorConvertFloat4ToU32(c); +} + +const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) +{ + ImGuiStyle& style = GImGui->Style; + return style.Colors[idx]; +} + +ImU32 ImGui::GetColorU32(ImU32 col) +{ + float style_alpha = GImGui->Style.Alpha; + if (style_alpha >= 1.0f) + return col; + int a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (int)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 +// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) +{ + float K = 0.f; + if (g < b) + { + ImSwap(g, b); + K = -1.f; + } + if (r < g) + { + ImSwap(r, g); + K = -2.f / 6.f - K; + } + + const float chroma = r - (g < b ? g : b); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); + out_s = chroma / (r + 1e-20f); + out_v = r; +} + +// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 +// also http://en.wikipedia.org/wiki/HSL_and_HSV +void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) +{ + if (s == 0.0f) + { + // gray + out_r = out_g = out_b = v; + return; + } + + h = fmodf(h, 1.0f) / (60.0f/360.0f); + int i = (int)h; + float f = h - (float)i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (i) + { + case 0: out_r = v; out_g = t; out_b = p; break; + case 1: out_r = q; out_g = v; out_b = p; break; + case 2: out_r = p; out_g = v; out_b = t; break; + case 3: out_r = p; out_g = q; out_b = v; break; + case 4: out_r = t; out_g = p; out_b = v; break; + case 5: default: out_r = v; out_g = p; out_b = q; break; + } +} + +FILE* ImFileOpen(const char* filename, const char* mode) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can) + const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; + const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); + ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); + return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); +#else + return fopen(filename, mode); +#endif +} + +// Load file content into memory +// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() +void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) +{ + IM_ASSERT(filename && file_open_mode); + if (out_file_size) + *out_file_size = 0; + + FILE* f; + if ((f = ImFileOpen(filename, file_open_mode)) == NULL) + return NULL; + + long file_size_signed; + if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) + { + fclose(f); + return NULL; + } + + int file_size = (int)file_size_signed; + void* file_data = ImGui::MemAlloc(file_size + padding_bytes); + if (file_data == NULL) + { + fclose(f); + return NULL; + } + if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) + { + fclose(f); + ImGui::MemFree(file_data); + return NULL; + } + if (padding_bytes > 0) + memset((void *)(((char*)file_data) + file_size), 0, padding_bytes); + + fclose(f); + if (out_file_size) + *out_file_size = file_size; + + return file_data; +} + +//----------------------------------------------------------------------------- +// ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +// std::lower_bound but without the bullshit +static ImVector::iterator LowerBound(ImVector& data, ImGuiID key) +{ + ImVector::iterator first = data.begin(); + ImVector::iterator last = data.end(); + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + ImVector::iterator mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. +void ImGuiStorage::BuildSortByKey() +{ + struct StaticFunc + { + static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1; + if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. +int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_i; +} + +bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_f; +} + +void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) +void ImGuiStorage::SetInt(ImGuiID key, int val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_i = val; +} + +void ImGuiStorage::SetBool(ImGuiID key, bool val) +{ + SetInt(key, val ? 1 : 0); +} + +void ImGuiStorage::SetFloat(ImGuiID key, float val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) +{ + if (default_filter) + { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } + else + { + InputBuf[0] = 0; + CountGrep = 0; + } +} + +bool ImGuiTextFilter::Draw(const char* label, float width) +{ + if (width != 0.0f) + ImGui::PushItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (width != 0.0f) + ImGui::PopItemWidth(); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::TextRange::split(char separator, ImVector& out) +{ + out.resize(0); + const char* wb = b; + const char* we = wb; + while (we < e) + { + if (*we == separator) + { + out.push_back(TextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out.push_back(TextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); + input_range.split(',', Filters); + + CountGrep = 0; + for (int i = 0; i != Filters.Size; i++) + { + Filters[i].trim_blanks(); + if (Filters[i].empty()) + continue; + if (Filters[i].front() != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (int i = 0; i != Filters.Size; i++) + { + const TextRange& f = Filters[i]; + if (f.empty()) + continue; + if (f.front() == '-') + { + // Subtract + if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) + return false; + } + else + { + // Grep + if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) + return true; + } + } + + // Implicit * grep + if (CountGrep == 0) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiTextBuffer +//----------------------------------------------------------------------------- + +// On some platform vsnprintf() takes va_list by reference and modifies it. +// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. +#ifndef va_copy +#define va_copy(dest, src) (dest = src) +#endif + +// Helper: Text buffer for logging/accumulating text +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + if (len <= 0) + return; + + const int write_off = Buf.Size; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int double_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); + } + + Buf.resize(needed_sz); + ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy); +} + +void ImGuiTextBuffer::appendf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + appendfv(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// ImGuiSimpleColumns (internal use only) +//----------------------------------------------------------------------------- + +ImGuiMenuColumns::ImGuiMenuColumns() +{ + Count = 0; + Spacing = Width = NextWidth = 0.0f; + memset(Pos, 0, sizeof(Pos)); + memset(NextWidths, 0, sizeof(NextWidths)); +} + +void ImGuiMenuColumns::Update(int count, float spacing, bool clear) +{ + IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); + Count = count; + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < Count; i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = (float)(int)Width; + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } +} + +float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +{ + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < 3; i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); +} + +float ImGuiMenuColumns::CalcExtraSpace(float avail_w) +{ + return ImMax(0.0f, avail_w - Width); +} + +//----------------------------------------------------------------------------- +// ImGuiListClipper +//----------------------------------------------------------------------------- + +static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) +{ + // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. Consider moving within SetCursorXXX functions? + ImGui::SetCursorPosY(pos_y); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage. + window->DC.PrevLineHeight = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. + if (window->DC.ColumnsSet) + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly +} + +// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 +// Use case B: Begin() called from constructor with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int count, float items_height) +{ + StartPosY = ImGui::GetCursorPosY(); + ItemsHeight = items_height; + ItemsCount = count; + StepNo = 0; + DisplayEnd = DisplayStart = -1; + if (ItemsHeight > 0.0f) + { + ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display + if (DisplayStart > 0) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor + StepNo = 2; + } +} + +void ImGuiListClipper::End() +{ + if (ItemsCount < 0) + return; + // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; + StepNo = 3; +} + +bool ImGuiListClipper::Step() +{ + if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) + { + ItemsCount = -1; + return false; + } + if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height. + { + DisplayStart = 0; + DisplayEnd = 1; + StartPosY = ImGui::GetCursorPosY(); + StepNo = 1; + return true; + } + if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. + { + if (ItemsCount == 1) { ItemsCount = -1; return false; } + float items_height = ImGui::GetCursorPosY() - StartPosY; + IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically + Begin(ItemsCount-1, items_height); + DisplayStart++; + DisplayEnd++; + StepNo = 3; + return true; + } + if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3. + { + IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); + StepNo = 3; + return true; + } + if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. + End(); + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiWindow +//----------------------------------------------------------------------------- + +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) +{ + Name = ImStrdup(name); + ID = ImHash(name, 0); + IDStack.push_back(ID); + Flags = 0; + PosFloat = Pos = ImVec2(0.0f, 0.0f); + Size = SizeFull = ImVec2(0.0f, 0.0f); + SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); + WindowPadding = ImVec2(0.0f, 0.0f); + WindowRounding = 0.0f; + WindowBorderSize = 0.0f; + MoveId = GetID("#MOVE"); + ChildId = 0; + Scroll = ImVec2(0.0f, 0.0f); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + ScrollbarX = ScrollbarY = false; + ScrollbarSizes = ImVec2(0.0f, 0.0f); + Active = WasActive = false; + WriteAccessed = false; + Collapsed = false; + CollapseToggleWanted = false; + SkipItems = false; + Appearing = false; + CloseButton = false; + BeginOrderWithinParent = -1; + BeginOrderWithinContext = -1; + BeginCount = 0; + PopupId = 0; + AutoFitFramesX = AutoFitFramesY = -1; + AutoFitOnlyGrows = false; + AutoFitChildAxises = 0x00; + AutoPosLastDirection = ImGuiDir_None; + HiddenFrames = 0; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + + LastFrameActive = -1; + ItemWidthDefault = 0.0f; + FontWindowScale = 1.0f; + + DrawList = IM_NEW(ImDrawList)(&context->DrawListSharedData); + DrawList->_OwnerName = Name; + ParentWindow = NULL; + RootWindow = NULL; + RootWindowForTitleBarHighlight = NULL; + RootWindowForTabbing = NULL; + RootWindowForNav = NULL; + + NavLastIds[0] = NavLastIds[1] = 0; + NavRectRel[0] = NavRectRel[1] = ImRect(); + NavLastChildNavWindow = NULL; + + FocusIdxAllCounter = FocusIdxTabCounter = -1; + FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; + FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_DELETE(DrawList); + IM_DELETE(Name); + for (int i = 0; i != ColumnsStorage.Size; i++) + ColumnsStorage[i].~ImGuiColumnsSet(); +} + +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(&ptr, sizeof(void*), seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; + ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed); + ImGui::KeepAliveID(id); + return id; +} + +//----------------------------------------------------------------------------- +// Internal API exposed in imgui_internal.h +//----------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow = window; + if (window) + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +static void SetNavID(ImGuiID id, int nav_layer) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindow); + IM_ASSERT(nav_layer == 0 || nav_layer == 1); + g.NavId = id; + g.NavWindow->NavLastIds[nav_layer] = id; +} + +static void SetNavIDAndMoveMouse(ImGuiID id, int nav_layer, const ImRect& rect_rel) +{ + ImGuiContext& g = *GImGui; + SetNavID(id, nav_layer); + g.NavWindow->NavRectRel[nav_layer] = rect_rel; + g.NavMousePosDirty = true; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; +} + +void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) + g.ActiveIdTimer = 0.0f; + g.ActiveId = id; + g.ActiveIdAllowNavDirFlags = 0; + g.ActiveIdAllowOverlap = false; + g.ActiveIdWindow = window; + if (id) + { + g.ActiveIdIsAlive = true; + g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; + } +} + +ImGuiID ImGui::GetActiveID() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId; +} + +void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0); + + // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it. + const int nav_layer = window->DC.NavLayerCurrent; + if (g.NavWindow != window) + g.NavInitRequest = false; + g.NavId = id; + g.NavWindow = window; + g.NavLayer = nav_layer; + window->NavLastIds[nav_layer] = id; + if (window->DC.LastItemId == id) + window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + + if (g.ActiveIdSource == ImGuiInputSource_Nav) + g.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f; +} + +ImGuiID ImGui::GetHoveredID() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; +} + +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = true; +} + +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + + return true; +} + +// Advance cursor given item size for layout. +void ImGui::ItemSize(const ImVec2& size, float text_offset_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Always align ourselves on pixel boundaries + const float line_height = ImMax(window->DC.CurrentLineHeight, size.y); + const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); + window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineHeight = line_height; + window->DC.PrevLineTextBaseOffset = text_base_offset; + window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + +void ImGui::ItemSize(const ImRect& bb, float text_offset_y) +{ + ItemSize(bb.GetSize(), text_offset_y); +} + +static ImGuiDir NavScoreItemGetQuadrant(float dx, float dy) +{ + if (fabsf(dx) > fabsf(dy)) + return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; + return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; +} + +static float NavScoreItemDistInterval(float a0, float a1, float b0, float b1) +{ + if (a1 < b0) + return a1 - b0; + if (b1 < a0) + return a0 - b1; + return 0.0f; +} + +// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavLayer != window->DC.NavLayerCurrent) + return false; + + const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringCount++; + + // We perform scoring on items bounding box clipped by their parent window on the other axis (clipping on our movement axis would give us equal scores for all clipped items) + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + { + cand.Min.y = ImClamp(cand.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y); + cand.Max.y = ImClamp(cand.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y); + } + else + { + cand.Min.x = ImClamp(cand.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + cand.Max.x = ImClamp(cand.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + } + + // Compute distance between boxes + // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. + float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + if (dby != 0.0f && dbx != 0.0f) + dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); + float dist_box = fabsf(dbx) + fabsf(dby); + + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) + float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); + float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); + float dist_center = fabsf(dcx) + fabsf(dcy); // L1 metric (need this for our connectedness guarantee) + + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance + ImGuiDir quadrant; + float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; + if (dbx != 0.0f || dby != 0.0f) + { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = NavScoreItemGetQuadrant(dbx, dby); + } + else if (dcx != 0.0f || dcy != 0.0f) + { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = NavScoreItemGetQuadrant(dcx, dcy); + } + else + { + // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) + quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + g.OverlayDrawList.AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100)); + g.OverlayDrawList.AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); + g.OverlayDrawList.AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); + g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + } + else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + { + if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } + if (quadrant == g.NavMoveDir) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + g.OverlayDrawList.AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); + g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } + #endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + if (quadrant == g.NavMoveDir) + { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) + { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) + { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) + { + result->DistCenter = dist_center; + new_best = true; + } + else if (dist_center == result->DistCenter) + { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items + // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), + // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. + if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches + // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) + // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. + // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. + // Disabling it may however lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +static void NavSaveLastChildNavWindow(ImGuiWindow* child_window) +{ + ImGuiWindow* parent_window = child_window; + while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent_window = parent_window->ParentWindow; + if (parent_window && parent_window != child_window) + parent_window->NavLastChildNavWindow = child_window; +} + +// Call when we are expected to land on Layer 0 after FocusWindow() +static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window) +{ + return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; +} + +static void NavRestoreLayer(int layer) +{ + ImGuiContext& g = *GImGui; + g.NavLayer = layer; + if (layer == 0) + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); + if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) + SetNavIDAndMoveMouse(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); + else + ImGui::NavInitWindow(g.NavWindow, true); +} + +static inline void NavUpdateAnyRequestFlag() +{ + ImGuiContext& g = *GImGui; + g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || IMGUI_DEBUG_NAV_SCORING; +} + +static bool NavMoveRequestButNoResultYet() +{ + ImGuiContext& g = *GImGui; + return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +static void NavMoveRequestCancel() +{ + ImGuiContext& g = *GImGui; + g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); +} + +// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) +static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +{ + ImGuiContext& g = *GImGui; + //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. + // return; + + const ImGuiItemFlags item_flags = window->DC.ItemFlags; + const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + { + g.NavInitResultId = id; + g.NavInitResultRectRel = nav_bb_rel; + } + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + { + g.NavInitRequest = false; // Found a match, clear request + NavUpdateAnyRequestFlag(); + } + } + + // Scoring for navigation + if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav)) + { + ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; +#if IMGUI_DEBUG_NAV_SCORING + // [DEBUG] Score all items in NavWindow at all times + if (!g.NavMoveRequest) + g.NavMoveDir = g.NavMoveDirLast; + bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; +#else + bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); +#endif + if (new_best) + { + result->ID = id; + result->ParentID = window->IDStack.back(); + result->Window = window; + result->RectRel = nav_bb_rel; + } + } + + // Update window-relative bounding box of navigated item + if (g.NavId == id) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavIdIsAlive = true; + g.NavIdTabCounter = window->FocusIdxTabCounter; + window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + } +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (id != 0) + { + // Navigation processing runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window. + // it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick) + window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask; + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); + } + + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemStatusFlags = 0; + + // Clipping test + const bool is_clipped = IsClippedEx(bb, id, false); + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (IsMouseHoveringRect(bb.Min, bb.Max)) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavDisableMouseHover && !g.NavDisableHighlight) + return IsItemFocused(); + + // Test for bounding box overlap, as updated as ItemAdd() + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function + + // Test if we are hovering the right window (our window could be behind another window) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. + // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. + //if (g.HoveredWindow != window) + // return false; + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) + return false; + + // Test if another item is active (e.g. being dragged) + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + + // Test if interactions on this window are blocked by an active popup or modal + if (!IsWindowContentHoverable(window, flags)) + return false; + + // Test if the item is disabled + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. + if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_Default)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + SetHoveredID(id); + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || id != g.ActiveId) + if (clip_even_when_logged || !g.LogEnabled) + return true; + return false; +} + +bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) +{ + ImGuiContext& g = *GImGui; + + const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus; + window->FocusIdxAllCounter++; + if (allow_keyboard_focus) + window->FocusIdxTabCounter++; + + // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. + // Note that we can always TAB out of a widget that doesn't allow tabbing in. + if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) + window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + + if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) + return true; + if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) + { + g.NavJustTabbedId = id; + return true; + } + + return false; +} + +void ImGui::FocusableItemUnregister(ImGuiWindow* window) +{ + window->FocusIdxAllCounter--; + window->FocusIdxTabCounter--; +} + +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) +{ + ImGuiContext& g = *GImGui; + ImVec2 content_max; + if (size.x < 0.0f || size.y < 0.0f) + content_max = g.CurrentWindow->Pos + GetContentRegionMax(); + if (size.x <= 0.0f) + size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; + if (size.y <= 0.0f) + size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; + return size; +} + +float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) +{ + if (wrap_pos_x < 0.0f) + return 0.0f; + + ImGuiWindow* window = GetCurrentWindowRead(); + if (wrap_pos_x == 0.0f) + wrap_pos_x = GetContentRegionMax().x + window->Pos.x; + else if (wrap_pos_x > 0.0f) + wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space + + return ImMax(wrap_pos_x - pos.x, 1.0f); +} + +//----------------------------------------------------------------------------- + +void* ImGui::MemAlloc(size_t sz) +{ + GImAllocatorActiveAllocationsCount++; + return GImAllocatorAllocFunc(sz, GImAllocatorUserData); +} + +void ImGui::MemFree(void* ptr) +{ + if (ptr) GImAllocatorActiveAllocationsCount--; + return GImAllocatorFreeFunc(ptr, GImAllocatorUserData); +} + +const char* ImGui::GetClipboardText() +{ + return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char* text) +{ + if (GImGui->IO.SetClipboardTextFn) + GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); +} + +const char* ImGui::GetVersion() +{ + return IMGUI_VERSION; +} + +// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data) +{ + GImAllocatorAllocFunc = alloc_func; + GImAllocatorFreeFunc = free_func; + GImAllocatorUserData = user_data; +} + +ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) +{ + ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + if (GImGui == NULL) + SetCurrentContext(ctx); + Initialize(ctx); + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + if (ctx == NULL) + ctx = GImGui; + Shutdown(ctx); + if (GImGui == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +ImGuiIO& ImGui::GetIO() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->IO; +} + +ImGuiStyle& ImGui::GetStyle() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->Style; +} + +// Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame() +ImDrawData* ImGui::GetDrawData() +{ + ImGuiContext& g = *GImGui; + return g.DrawData.Valid ? &g.DrawData : NULL; +} + +float ImGui::GetTime() +{ + return GImGui->Time; +} + +int ImGui::GetFrameCount() +{ + return GImGui->FrameCount; +} + +ImDrawList* ImGui::GetOverlayDrawList() +{ + return &GImGui->OverlayDrawList; +} + +ImDrawListSharedData* ImGui::GetDrawListSharedData() +{ + return &GImGui->DrawListSharedData; +} + +// This needs to be called before we submit any widget (aka in or before Begin) +void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == g.NavWindow); + bool init_for_nav = false; + if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; + if (init_for_nav) + { + SetNavID(0, g.NavLayer); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } + else + { + g.NavId = window->NavLastIds[0]; + } +} + +static ImVec2 NavCalcPreferredMousePos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.NavWindow; + if (!window) + return g.IO.MousePos; + const ImRect& rect_rel = window->NavRectRel[g.NavLayer]; + ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImRect visible_rect = GetViewportRect(); + return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. +} + +static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size-1; i >= 0; i--) + if (g.Windows[i] == window) + return i; + return -1; +} + +static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.Windows[i])) + return g.Windows[i]; + return NULL; +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + ImGuiContext& g = *GImGui; + if (mode == ImGuiInputReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) + return 0.0f; + if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. + return (t == 0.0f) ? 1.0f : 0.0f; + if (mode == ImGuiInputReadMode_Repeat) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f); + if (mode == ImGuiInputReadMode_RepeatSlow) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f); + if (mode == ImGuiInputReadMode_RepeatFast) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f); + return 0.0f; +} + +// Equivalent of IsKeyDown() for NavInputs[] +static bool IsNavInputDown(ImGuiNavInput n) +{ + return GImGui->IO.NavInputs[n] > 0.0f; +} + +// Equivalent of IsKeyPressed() for NavInputs[] +static bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + return ImGui::GetNavInputAmount(n, mode) > 0.0f; +} + +static bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) +{ + return (ImGui::GetNavInputAmount(n1, mode) + ImGui::GetNavInputAmount(n2, mode)) > 0.0f; +} + +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +{ + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = FindWindowIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); + if (!window_target) + window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir); + g.NavWindowingTarget = window_target; + g.NavWindowingToggleLayer = false; +} + +// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) +static void ImGui::NavUpdateWindowing() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; + + bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard); + if (start_windowing_with_gamepad || start_windowing_with_keyboard) + if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1)) + { + g.NavWindowingTarget = window->RootWindowForTabbing; + g.NavWindowingHighlightTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavWindowingInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + } + + // Gamepad update + g.NavWindowingHighlightTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + { + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.20f) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); + if (focus_change_dir != 0) + { + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } + + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) + if (!IsNavInputDown(ImGuiNavInput_Menu)) + { + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.15f) / 0.04f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and Release ALT to toggle menu layer + // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) + if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) + apply_toggle_layer = true; + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + ImVec2 move_delta; + if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingTarget->PosFloat += move_delta * move_speed; + g.NavDisableMouseHover = true; + MarkIniSettingsDirty(g.NavWindowingTarget); + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowForTabbing)) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window); + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window only has a menu layer, select it directly + if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) + g.NavLayer = 1; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) + { + ImGuiWindow* new_nav_window = g.NavWindow; + while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) + { + ImGuiWindow* old_nav_window = g.NavWindow; + FocusWindow(new_nav_window); + new_nav_window->NavLastChildNavWindow = old_nav_window; + } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0); + } +} + +// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. +static void NavScrollToBringItemIntoView(ImGuiWindow* window, ImRect& item_rect_rel) +{ + // Scroll to keep newly navigated item fully into view + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); + //g.OverlayDrawList.AddRect(window->Pos + window_rect_rel.Min, window->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG] + if (window_rect_rel.Contains(item_rect_rel)) + return; + + ImGuiContext& g = *GImGui; + if (window->ScrollbarX && item_rect_rel.Min.x < window_rect_rel.Min.x) + { + window->ScrollTarget.x = item_rect_rel.Min.x + window->Scroll.x - g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + else if (window->ScrollbarX && item_rect_rel.Max.x >= window_rect_rel.Max.x) + { + window->ScrollTarget.x = item_rect_rel.Max.x + window->Scroll.x + g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 1.0f; + } + if (item_rect_rel.Min.y < window_rect_rel.Min.y) + { + window->ScrollTarget.y = item_rect_rel.Min.y + window->Scroll.y - g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + else if (item_rect_rel.Max.y >= window_rect_rel.Max.y) + { + window->ScrollTarget.y = item_rect_rel.Max.y + window->Scroll.y + g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 1.0f; + } + + // Estimate upcoming scroll so we can offset our relative mouse position so mouse position can be applied immediately (under this block) + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); + item_rect_rel.Translate(window->Scroll - next_scroll); +} + +static void ImGui::NavUpdate() +{ + ImGuiContext& g = *GImGui; + g.IO.WantMoveMouse = false; + +#if 0 + if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); +#endif + + // Update Keyboard->Nav inputs mapping + memset(g.IO.NavInputs + ImGuiNavInput_InternalStart_, 0, (ImGuiNavInput_COUNT - ImGuiNavInput_InternalStart_) * sizeof(g.IO.NavInputs[0])); + if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) + { + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (g.IO.KeyMap[_KEY] != -1 && IsKeyDown(g.IO.KeyMap[_KEY])) g.IO.NavInputs[_NAV_INPUT] = 1.0f; + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); + NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); + if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; + if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; +#undef NAV_MAP_KEY + } + + memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) + g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) + { + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + IM_ASSERT(g.NavWindow); + if (g.NavInitRequestFromMove) + SetNavIDAndMoveMouse(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); + else + SetNavID(g.NavInitResultId, g.NavLayer); + g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + } + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 0; + + // Process navigation move request + if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) + { + // Select which result to use + ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + if (g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) // Maybe entering a flattened child? In this case solve the tie using the regular scoring rules + if ((g.NavMoveResultOther.DistBox < g.NavMoveResultLocal.DistBox) || (g.NavMoveResultOther.DistBox == g.NavMoveResultLocal.DistBox && g.NavMoveResultOther.DistCenter < g.NavMoveResultLocal.DistCenter)) + result = &g.NavMoveResultOther; + + IM_ASSERT(g.NavWindow && result->Window); + + // Scroll to keep newly navigated item fully into view + if (g.NavLayer == 0) + NavScrollToBringItemIntoView(result->Window, result->RectRel); + + // Apply result from previous frame navigation directional move request + ClearActiveID(); + g.NavWindow = result->Window; + SetNavIDAndMoveMouse(result->ID, g.NavLayer, result->RectRel); + g.NavJustMovedToId = result->ID; + g.NavMoveFromClampedRefRect = false; + } + + // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame + if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + { + IM_ASSERT(g.NavMoveRequest); + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + g.NavDisableHighlight = false; + g.NavMoveRequestForward = ImGuiNavForward_None; + } + + // Apply application mouse position movement, after we had a chance to process move request result. + if (g.NavMousePosDirty && g.NavIdIsAlive) + { + // Set mouse position given our knowledge of the nav widget position from last frame + if (g.IO.NavFlags & ImGuiNavFlags_MoveMouse) + { + g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredMousePos(); + g.IO.WantMoveMouse = true; + } + g.NavMousePosDirty = false; + } + g.NavIdIsAlive = false; + g.NavJustTabbedId = 0; + IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); + + // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 + if (g.NavWindow) + NavSaveLastChildNavWindow(g.NavWindow); + if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) + g.NavWindow->NavLastChildNavWindow = NULL; + + NavUpdateWindowing(); + + // Set output flags for user application + g.IO.NavActive = (g.IO.NavFlags & (ImGuiNavFlags_EnableGamepad | ImGuiNavFlags_EnableKeyboard)) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); + g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; + + // Process NavCancel input (to close a popup, get back to parent, clear focus) + if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) + { + if (g.ActiveId != 0) + { + ClearActiveID(); + } + else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + { + // Exit child window + ImGuiWindow* child_window = g.NavWindow; + ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, 0); + g.NavIdIsAlive = false; + if (g.NavDisableMouseHover) + g.NavMousePosDirty = true; + } + else if (g.OpenPopupStack.Size > 0) + { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + } + else if (g.NavLayer != 0) + { + // Leave the "menu" layer + NavRestoreLayer(0); + } + else + { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = 0; + } + } + + // Process manual activation request + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + if (g.ActiveId == 0 && activate_pressed) + g.NavActivateId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) + g.NavActivatePressedId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) + g.NavInputId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + g.NavMoveRequest = false; + + // Process programmatic activation request + if (g.NavNextActivateId != 0) + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + g.NavNextActivateId = 0; + + // Initiate directional inputs request + const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; + if (g.NavMoveRequestForward == ImGuiNavForward_None) + { + g.NavMoveDir = ImGuiDir_None; + if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + if ((allowed_dir_flags & (1<Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) + { + // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item + ImGuiWindow* window = g.NavWindow; + const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + { + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) + SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + } + + // *Normal* Manual scroll with NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + { + SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + if (scroll_dir.y != 0.0f) + { + SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + } + + // Reset search results + g.NavMoveResultLocal.Clear(); + g.NavMoveResultOther.Clear(); + + // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items + if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1,1), window->InnerRect.Max - window->Pos + ImVec2(1,1)); + if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) + { + float pad = window->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item + window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); + g.NavId = 0; + } + g.NavMoveFromClampedRefRect = false; + } + + // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) + ImRect nav_rect_rel = (g.NavWindow && g.NavWindow->NavRectRel[g.NavLayer].IsFinite()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); + g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect(); + g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); + g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; + IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous fabsf() calls in NavScoreItem(). + //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] + g.NavScoringCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) g.OverlayDrawList.AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames <= 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredMousePos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } +#endif +} + +static void ImGui::UpdateMovingWindow() +{ + ImGuiContext& g = *GImGui; + if (g.MovingWindow && g.MovingWindow->MoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse) + { + // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). + // We track it to preserve Focus and so that ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. + KeepAliveID(g.ActiveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + ImGuiWindow* moving_window = g.MovingWindow->RootWindow; + if (g.IO.MouseDown[0]) + { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + if (moving_window->PosFloat.x != pos.x || moving_window->PosFloat.y != pos.y) + { + MarkIniSettingsDirty(moving_window); + moving_window->PosFloat = pos; + } + FocusWindow(g.MovingWindow); + } + else + { + ClearActiveID(); + g.MovingWindow = NULL; + } + } + else + { + // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. + if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) + { + KeepAliveID(g.ActiveId); + if (!g.IO.MouseDown[0]) + ClearActiveID(); + } + g.MovingWindow = NULL; + } +} + +void ImGui::NewFrame() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + ImGuiContext& g = *GImGui; + + // Check user data + // (We pass an error message in the assert expression as a trick to get it visible to programmers who are not using a debugger, as most assert handlers display their argument) + IM_ASSERT(g.Initialized); + IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)"); + IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value"); + IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting"); + IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)"); + IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); + for (int n = 0; n < ImGuiKey_COUNT; n++) + IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)"); + + // Do a simple check for required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was super recently added in 1.60 WIP) + if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) + IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); + + // Load settings on first frame + if (!g.SettingsLoaded) + { + IM_ASSERT(g.SettingsWindows.empty()); + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.SettingsLoaded = true; + } + + g.Time += g.IO.DeltaTime; + g.FrameCount += 1; + g.TooltipOverrideCount = 0; + g.WindowsActiveCount = 0; + + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + + g.OverlayDrawList.Clear(); + g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); + g.OverlayDrawList.PushClipRectFullScreen(); + g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it + g.DrawData.Clear(); + + // Clear reference to active widget if the widget isn't alive anymore + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + if (g.ActiveId) + g.ActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) + g.ScalarAsInputTextId = 0; + + // Elapse drag & drop payload + if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) + { + ClearDragDrop(); + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + } + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + + // Update keyboard input state + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Update gamepad/keyboard directional navigation + NavUpdate(); + + // Update mouse input state + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) + g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) + g.NavDisableMouseHover = false; + + g.IO.MousePosPrev = g.IO.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseDoubleClicked[i] = false; + if (g.IO.MouseClicked[i]) + { + if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime) + { + if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) + g.IO.MouseDoubleClicked[i] = true; + g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click + } + else + { + g.IO.MouseClickedTime[i] = g.Time; + } + g.IO.MouseClickedPos[i] = g.IO.MousePos; + g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (g.IO.MouseDown[i]) + { + ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i]; + g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x); + g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y); + g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta)); + } + if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + g.NavDisableMouseHover = false; + } + + // Calculate frame-rate for the user, as a purely luxurious feature + g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; + g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); + g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); + + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) + UpdateMovingWindow(); + + // Delay saving settings so we don't spam disk too much + if (g.SettingsDirtyTimer > 0.0f) + { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) + SaveIniSettingsToDisk(g.IO.IniFilename); + } + + // Find the window we are hovering + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow(); + g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + + ImGuiWindow* modal_window = GetFrontMostModalRootWindow(); + if (modal_window != NULL) + { + g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f); + if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) + g.HoveredRootWindow = g.HoveredWindow = NULL; + } + else + { + g.ModalWindowDarkeningRatio = 0.0f; + } + + // Update the WantCaptureMouse/WantCaptureKeyboard flags, so user can capture/discard the inputs away from the rest of their application. + // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership. + int mouse_earliest_button_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + if (g.IO.MouseClicked[i]) + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); + mouse_any_down |= g.IO.MouseDown[i]; + if (g.IO.MouseDown[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) + mouse_earliest_button_down = i; + } + bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); + else + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + + if (g.WantCaptureKeyboardNextFrame != -1) + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + if (g.IO.NavActive && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) && !(g.IO.NavFlags & ImGuiNavFlags_NoCaptureKeyboard)) + g.IO.WantCaptureKeyboard = true; + + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0; + g.MouseCursor = ImGuiMouseCursor_Arrow; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) + bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; + if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) + g.HoveredWindow = g.HoveredRootWindow = NULL; + + // Mouse wheel scrolling, scale + if (g.HoveredWindow && !g.HoveredWindow->Collapsed && (g.IO.MouseWheel != 0.0f || g.IO.MouseWheelH != 0.0f)) + { + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set). + ImGuiWindow* window = g.HoveredWindow; + ImGuiWindow* scroll_window = window; + while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow) + scroll_window = scroll_window->ParentWindow; + const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs); + + if (g.IO.MouseWheel != 0.0f) + { + if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) + { + // Zoom / Scale window + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + window->Pos += offset; + window->PosFloat += offset; + window->Size *= scale; + window->SizeFull *= scale; + } + else if (!g.IO.KeyCtrl && scroll_allowed) + { + // Mouse wheel vertical scrolling + float scroll_amount = 5 * scroll_window->CalcFontSize(); + scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f); + SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount); + } + } + if (g.IO.MouseWheelH != 0.0f && scroll_allowed) + { + // Mouse wheel horizontal scrolling (for hardware that supports it) + float scroll_amount = scroll_window->CalcFontSize(); + if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) + SetWindowScrollX(window, window->Scroll.x - g.IO.MouseWheelH * scroll_amount); + } + } + + // Pressing TAB activate widget focus + if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false)) + { + if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) + g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1); + else + g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0; + } + g.NavIdTabCounter = INT_MAX; + + // Mark all windows as not visible + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->Active = false; + window->WriteAccessed = false; + } + + // Closing the focused window restore focus to the first active root window in descending z-order + if (g.NavWindow && !g.NavWindow->WasActive) + FocusFrontMostActiveWindow(NULL); + + // No window should be open at the beginning of the frame. + // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. + g.CurrentWindowStack.resize(0); + g.CurrentPopupStack.resize(0); + ClosePopupsOverWindow(g.NavWindow); + + // Create implicit window - we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. + SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); +} + +static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); + if (!settings) + settings = AddWindowSettings(name); + return (void*)settings; +} + +static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; + float x, y; + int i; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); + else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); +} + +static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + ImGuiContext& g = *imgui_ctx; + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID); + if (!settings) + settings = AddWindowSettings(window->Name); + settings->Pos = window->Pos; + settings->Size = window->SizeFull; + settings->Collapsed = window->Collapsed; + } + + // Write a buffer + // If a window wasn't opened in this session we preserve its settings + buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) + { + const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; + if (settings->Pos.x == FLT_MAX) + continue; + const char* name = settings->Name; + if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + name = p; + buf->appendf("[%s][%s]\n", handler->TypeName, name); + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->appendf("\n"); + } +} + +void ImGui::Initialize(ImGuiContext* context) +{ + ImGuiContext& g = *context; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + g.LogClipboard = IM_NEW(ImGuiTextBuffer)(); + + // Add .ini handle for ImGuiWindow type + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHash("Window", 0, 0); + ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; + ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; + ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; + g.SettingsHandlers.push_front(ini_handler); + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown(ImGuiContext* context) +{ + ImGuiContext& g = *context; + + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + if (g.IO.Fonts && g.FontAtlasOwnedByContext) + IM_DELETE(g.IO.Fonts); + + // Cleanup of other data are conditional on actually having initialize ImGui. + if (!g.Initialized) + return; + + SaveIniSettingsToDisk(g.IO.IniFilename); + + // Clear everything else + for (int i = 0; i < g.Windows.Size; i++) + IM_DELETE(g.Windows[i]); + g.Windows.clear(); + g.WindowsSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = NULL; + g.HoveredRootWindow = NULL; + g.ActiveIdWindow = NULL; + g.MovingWindow = NULL; + for (int i = 0; i < g.SettingsWindows.Size; i++) + IM_DELETE(g.SettingsWindows[i].Name); + g.ColorModifiers.clear(); + g.StyleModifiers.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.CurrentPopupStack.clear(); + g.DrawDataBuilder.ClearFreeMemory(); + g.OverlayDrawList.ClearFreeMemory(); + g.PrivateClipboard.clear(); + g.InputTextState.Text.clear(); + g.InputTextState.InitialText.clear(); + g.InputTextState.TempTextBuffer.clear(); + + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile && g.LogFile != stdout) + { + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard) + IM_DELETE(g.LogClipboard); + + g.Initialized = false; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (int i = 0; i != g.SettingsWindows.Size; i++) + if (g.SettingsWindows[i].Id == id) + return &g.SettingsWindows[i]; + return NULL; +} + +static ImGuiWindowSettings* AddWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + g.SettingsWindows.push_back(ImGuiWindowSettings()); + ImGuiWindowSettings* settings = &g.SettingsWindows.back(); + settings->Name = ImStrdup(name); + settings->Id = ImHash(name, 0); + return settings; +} + +static void LoadIniSettingsFromDisk(const char* ini_filename) +{ + if (!ini_filename) + return; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1); + if (!file_data) + return; + LoadIniSettingsFromMemory(file_data); + ImGui::MemFree(file_data); +} + +ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) +{ + ImGuiContext& g = *GImGui; + const ImGuiID type_hash = ImHash(type_name, 0, 0); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +// Zero-tolerance, no error reporting, cheap .ini parsing +static void LoadIniSettingsFromMemory(const char* buf_readonly) +{ + // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy. + char* buf = ImStrdup(buf_readonly); + char* buf_end = buf + strlen(buf); + + ImGuiContext& g = *GImGui; + void* entry_data = NULL; + ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) + { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + + if (line[0] == '[' && line_end > line && line_end[-1] == ']') + { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; + char* type_end = ImStrchrRange(type_start, name_end, ']'); + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + { + name_start = type_start; // Import legacy entries that have no type + type_start = "Window"; + } + else + { + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + } + entry_handler = ImGui::FindSettingsHandler(type_start); + entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; + } + else if (entry_handler != NULL && entry_data != NULL) + { + // Let type handler parse the line + entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); + } + } + ImGui::MemFree(buf); + g.SettingsLoaded = true; +} + +static void SaveIniSettingsToDisk(const char* ini_filename) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + if (!ini_filename) + return; + + ImVector buf; + SaveIniSettingsToMemory(buf); + + FILE* f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f); + fclose(f); +} + +static void SaveIniSettingsToMemory(ImVector& out_buf) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + + ImGuiTextBuffer buf; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + { + ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; + handler->WriteAllFn(&g, handler, &buf); + } + + buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer + out_buf.swap(buf.Buf); +} + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +static void MarkIniSettingsDirty(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +// FIXME: Add a more explicit sort order in the window structure. +static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) +{ + const ImGuiWindow* a = *(const ImGuiWindow**)lhs; + const ImGuiWindow* b = *(const ImGuiWindow**)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) +{ + out_sorted_windows->push_back(window); + if (window->Active) + { + int count = window->DC.ChildWindows.Size; + if (count > 1) + qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); + for (int i = 0; i < count; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortedBuffer(out_sorted_windows, child); + } + } +} + +static void AddDrawListToDrawData(ImVector* out_render_list, ImDrawList* draw_list) +{ + if (draw_list->CmdBuffer.empty()) + return; + + // Remove trailing command if unused + ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); + if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) + { + draw_list->CmdBuffer.pop_back(); + if (draw_list->CmdBuffer.empty()) + return; + } + + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly. + IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); + IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); + IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + + // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) + // If this assert triggers because you are drawing lots of stuff manually: + // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents. + // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes. + // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API. + // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists. + if (sizeof(ImDrawIdx) == 2) + IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + + out_render_list->push_back(draw_list); +} + +static void AddWindowToDrawData(ImVector* out_render_list, ImGuiWindow* window) +{ + AddDrawListToDrawData(out_render_list, window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active && child->HiddenFrames <= 0) // clipped children may have been marked not active + AddWindowToDrawData(out_render_list, child); + } +} + +static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.IO.MetricsActiveWindows++; + if (window->Flags & ImGuiWindowFlags_Tooltip) + AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window); + else + AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window); +} + +void ImDrawDataBuilder::FlattenIntoSingleLayer() +{ + int n = Layers[0].Size; + int size = n; + for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) + size += Layers[i].Size; + Layers[0].resize(size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) + { + ImVector& layer = Layers[layer_n]; + if (layer.empty()) + continue; + memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + layer.resize(0); + } +} + +static void SetupDrawData(ImVector* draw_lists, ImDrawData* out_draw_data) +{ + out_draw_data->Valid = true; + out_draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; + out_draw_data->CmdListsCount = draw_lists->Size; + out_draw_data->TotalVtxCount = out_draw_data->TotalIdxCount = 0; + for (int n = 0; n < draw_lists->Size; n++) + { + out_draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; + out_draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size; + } +} + +// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. +void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +void ImGui::PopClipRect() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PopClipRect(); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. +void ImGui::EndFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times. + return; + + // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) + if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f) + { + g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y); + g.OsImePosSet = g.OsImePosRequest; + } + + // Hide implicit "Debug" window if it hasn't been used + IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls + if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + g.CurrentWindow->Active = false; + End(); + + if (g.ActiveId == 0 && g.HoveredId == 0) + { + if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear + { + // Click to focus window and start moving (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) + { + if (g.HoveredRootWindow != NULL) + { + // Set ActiveId even if the _NoMove flag is set, without it dragging away from a window with _NoMove would activate hover on other windows. + FocusWindow(g.HoveredWindow); + SetActiveID(g.HoveredWindow->MoveId, g.HoveredWindow); + g.NavDisableHighlight = true; + g.ActiveIdClickOffset = g.IO.MousePos - g.HoveredRootWindow->Pos; + if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove)) + g.MovingWindow = g.HoveredWindow; + } + else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) + { + // Clicking on void disable focus + FocusWindow(NULL); + } + } + + // With right mouse button we close popups without changing focus + // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger) + if (g.IO.MouseClicked[1]) + { + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostModalRootWindow(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal); + } + } + } + + // Sort the window list so that all child windows are after their parent + // We cannot do that on FocusWindow() because childs may not exist yet + g.WindowsSortBuffer.resize(0); + g.WindowsSortBuffer.reserve(g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + continue; + AddWindowToSortedBuffer(&g.WindowsSortBuffer, window); + } + + IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong + g.Windows.swap(g.WindowsSortBuffer); + + // Clear Input data for next frame + g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); + + g.FrameCountEnded = g.FrameCount; +} + +void ImGui::Render() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + + if (g.FrameCountEnded != g.FrameCount) + ImGui::EndFrame(); + g.FrameCountRendered = g.FrameCount; + + // Skip render altogether if alpha is 0.0 + // Note that vertex buffers have been created and are wasted, so it is best practice that you don't create windows in the first place, or consistently respond to Begin() returning false. + if (g.Style.Alpha > 0.0f) + { + // Gather windows to render + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0; + g.DrawDataBuilder.Clear(); + ImGuiWindow* window_to_render_front_most = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget : NULL; + for (int n = 0; n != g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0 && window != window_to_render_front_most) + AddWindowToDrawDataSelectLayer(window); + } + if (window_to_render_front_most && window_to_render_front_most->Active && window_to_render_front_most->HiddenFrames <= 0) // NavWindowingTarget is always temporarily displayed as the front-most window + AddWindowToDrawDataSelectLayer(window_to_render_front_most); + g.DrawDataBuilder.FlattenIntoSingleLayer(); + + // Draw software mouse cursor if requested + ImVec2 offset, size, uv[4]; + if (g.IO.MouseDrawCursor && g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &offset, &size, &uv[0], &uv[2])) + { + const ImVec2 pos = g.IO.MousePos - offset; + const ImTextureID tex_id = g.IO.Fonts->TexID; + const float sc = g.Style.MouseCursorScale; + g.OverlayDrawList.PushTextureID(tex_id); + g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(1,0)*sc, pos+ImVec2(1,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(2,0)*sc, pos+ImVec2(2,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[2], uv[3], IM_COL32(0,0,0,255)); // Black border + g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[0], uv[1], IM_COL32(255,255,255,255)); // White fill + g.OverlayDrawList.PopTextureID(); + } + if (!g.OverlayDrawList.VtxBuffer.empty()) + AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList); + + // Setup ImDrawData structure for end-user + SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData); + g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount; + g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount; + + // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) + g.IO.RenderDrawListsFn(&g.DrawData); +#endif + } +} + +const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) +{ + const char* text_display_end = text; + if (!text_end) + text_end = (const char*)-1; + + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) + text_display_end++; + return text_display_end; +} + +// Pass text data straight to log (without being displayed) +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + if (g.LogFile) + { + vfprintf(g.LogFile, fmt, args); + } + else + { + g.LogClipboard->appendfv(fmt, args); + } + va_end(args); +} + +// Internal version that takes a position to decide on newline placement and pad items according to their depth. +// We split text into individual lines to add current tree level padding +static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = ImGui::FindRenderedTextEnd(text, text_end); + + const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); + if (ref_pos) + window->DC.LogLinePosY = ref_pos->y; + + const char* text_remaining = text; + if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth + g.LogStartDepth = window->DC.TreeDepth; + const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); + for (;;) + { + // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. + const char* line_end = text_remaining; + while (line_end < text_end) + if (*line_end == '\n') + break; + else + line_end++; + if (line_end >= text_end) + line_end = NULL; + + const bool is_first_line = (text == text_remaining); + bool is_last_line = false; + if (line_end == NULL) + { + is_last_line = true; + line_end = text_end; + } + if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0)) + { + const int char_count = (int)(line_end - text_remaining); + if (log_new_line || !is_first_line) + ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining); + else + ImGui::LogText(" %.*s", char_count, text_remaining); + } + + if (is_last_line) + break; + text_remaining = line_end + 1; + } +} + +// Internal ImGui functions to render text +// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() +void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) + { + text_display_end = FindRenderedTextEnd(text, text_end); + } + else + { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + const int text_len = (int)(text_display_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); + } +} + +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + + const int text_len = (int)(text_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_end); + } +} + +// Default clip_rect uses (pos_min,pos_max) +// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Perform CPU side clipping for single clipped element to avoid using scissor state + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; + const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min + need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. + if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); + if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + + // Render + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); +} + +// Render a rectangle shaped with optional rounding and borders +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +// Render a triangle to denote expanded/collapsed state +void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const float h = g.FontSize * 1.00f; + float r = h * 0.40f * scale; + ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); + + ImVec2 a, b, c; + switch (dir) + { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) r = -r; + center.y -= r * 0.25f; + a = ImVec2(0,1) * r; + b = ImVec2(-0.866f,-0.5f) * r; + c = ImVec2(+0.866f,-0.5f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) r = -r; + center.x -= r * 0.25f; + a = ImVec2(1,0) * r; + b = ImVec2(-0.500f,+0.866f) * r; + c = ImVec2(-0.500f,-0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_Count_: + IM_ASSERT(0); + break; + } + + window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); +} + +void ImGui::RenderBullet(ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); +} + +void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness*0.5f; + pos += ImVec2(thickness*0.25f, thickness*0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third*0.5f; + window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); + window->DrawList->PathLineTo(ImVec2(bx, by)); + window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); + window->DrawList->PathStroke(col, false, thickness); +} + +void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +{ + ImGuiContext& g = *GImGui; + if (id != g.NavId) + return; + if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + return; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->DC.NavHideHighlightOneFrame) + return; + + float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + ImRect display_rect = bb; + display_rect.ClipWith(window->ClipRect); + if (flags & ImGuiNavHighlightFlags_TypeDefault) + { + const float THICKNESS = 2.0f; + const float DISTANCE = 3.0f + THICKNESS * 0.5f; + display_rect.Expand(ImVec2(DISTANCE,DISTANCE)); + bool fully_visible = window->ClipRect.Contains(display_rect); + if (!fully_visible) + window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS); + if (!fully_visible) + window->DrawList->PopClipRect(); + } + if (flags & ImGuiNavHighlightFlags_TypeThin) + { + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f); + } +} + +// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. +// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) +ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) +{ + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + ImFont* font = g.Font; + const float font_size = g.FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) + const float font_scale = font_size / font->FontSize; + const float character_spacing_x = 1.0f * font_scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + text_size.x = (float)(int)(text_size.x + 0.95f); + + return text_size; +} + +// Helper to calculate coarse clipping of large list of evenly sized items. +// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. +// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.LogEnabled) + { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (window->SkipItems) + { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((window->ClipRect.Min.y - pos.y) / items_height); + int end = (int)((window->ClipRect.Max.y - pos.y) / items_height); + if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Up) // When performing a navigation request, ensure we have one item extra in the direction we are moving to + start--; + if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) + end++; + + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have a lag here because WindowRectClipped is updated in Begin() so windows moved by user via SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is called, aka before the next Begin(). Moving window thankfully isn't affected. +static ImGuiWindow* FindHoveredWindow() +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (!window->Active) + continue; + if (window->Flags & ImGuiWindowFlags_NoInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding); + if (bb.Contains(g.IO.MousePos)) + return window; + } + return NULL; +} + +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(window->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + return rect_for_touch.Contains(g.IO.MousePos); +} + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat) +{ + const int key_index = GImGui->IO.KeyMap[key]; + return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false; +} + +int ImGui::GetKeyIndex(ImGuiKey imgui_key) +{ + IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); + return GImGui->IO.KeyMap[imgui_key]; +} + +// Note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your back-end/engine stored them into KeyDown[]! +bool ImGui::IsKeyDown(int user_key_index) +{ + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); + return GImGui->IO.KeysDown[user_key_index]; +} + +int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) +{ + if (t == 0.0f) + return 1; + if (t <= repeat_delay || repeat_rate <= 0.0f) + return 0; + const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); + return (count > 0) ? count : 0; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) return false; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); +} + +bool ImGui::IsKeyPressed(int user_key_index, bool repeat) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[user_key_index]; + if (t == 0.0f) + return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsKeyReleased(int user_key_index) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]; +} + +bool ImGui::IsMouseDown(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button]; +} + +bool ImGui::IsAnyMouseDown() +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + if (g.IO.MouseDown[n]) + return true; + return false; +} + +bool ImGui::IsMouseClicked(int button, bool repeat) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) + return true; + + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) + return true; + } + + return false; +} + +bool ImGui::IsMouseReleased(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + +bool ImGui::IsMouseDragging(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; +} + +ImVec2 ImGui::GetMousePos() +{ + return GImGui->IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext& g = *GImGui; + if (g.CurrentPopupStack.Size > 0) + return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos; + return g.IO.MousePos; +} + +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + if (mouse_pos == NULL) + mouse_pos = &GImGui->IO.MousePos; + const float MOUSE_INVALID = -256000.0f; + return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; +} + +// NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window. +ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr + g.IO.MouseClickedPos[button] = g.IO.MousePos; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + return GImGui->MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) +{ + GImGui->MouseCursor = cursor_type; +} + +void ImGui::CaptureKeyboardFromApp(bool capture) +{ + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; +} + +void ImGui::CaptureMouseFromApp(bool capture) +{ + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + return g.ActiveId == window->DC.LastItemId; + } + return false; +} + +bool ImGui::IsItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId; +} + +bool ImGui::IsItemClicked(int mouse_button) +{ + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default); +} + +bool ImGui::IsAnyItemHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; +} + +bool ImGui::IsAnyItemActive() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId != 0; +} + +bool ImGui::IsAnyItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId != 0 && !g.NavDisableHighlight; +} + +bool ImGui::IsItemVisible() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(window->DC.LastItemRect); +} + +// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId == g.CurrentWindow->DC.LastItemId) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == g.CurrentWindow->DC.LastItemId) + g.ActiveIdAllowOverlap = true; +} + +ImVec2 ImGui::GetItemRectMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Min; +} + +ImVec2 ImGui::GetItemRectMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Max; +} + +ImVec2 ImGui::GetItemRectSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.GetSize(); +} + +static ImRect GetViewportRect() +{ + ImGuiContext& g = *GImGui; + if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) + return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); + return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); +} + +// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) +{ + ImGuiContext& g = *GImGui; + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (override_previous_tooltip) + if (ImGuiWindow* window = FindWindowByName(window_name)) + if (window->Active) + { + // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one. + window->HiddenFrames = 1; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav; + Begin(window_name, NULL, flags | extra_flags); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) +{ + BeginTooltipEx(0, true); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +void ImGui::BeginTooltip() +{ + BeginTooltipEx(0, false); +} + +void ImGui::EndTooltip() +{ + IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls + End(); +} + +// Mark popup as open (toggle toward open state). +// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopupEx(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + int current_stack_size = g.CurrentPopupStack.Size; + ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. + popup_ref.PopupId = id; + popup_ref.Window = NULL; + popup_ref.ParentWindow = parent_window; + popup_ref.OpenFrameCount = g.FrameCount; + popup_ref.OpenParentId = parent_window->IDStack.back(); + popup_ref.OpenMousePos = g.IO.MousePos; + popup_ref.OpenPopupPos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos; + + if (g.OpenPopupStack.Size < current_stack_size + 1) + { + g.OpenPopupStack.push_back(popup_ref); + } + else + { + // Close child popups if any + g.OpenPopupStack.resize(current_stack_size + 1); + + // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui + // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing + // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. + if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) + g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; + else + g.OpenPopupStack[current_stack_size] = popup_ref; + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). + // This is equivalent to what ClosePopupToLevel() does. + //if (g.OpenPopupStack[current_stack_size].PopupId == id) + // FocusWindow(parent_window); + } +} + +void ImGui::OpenPopup(const char* str_id) +{ + ImGuiContext& g = *GImGui; + OpenPopupEx(g.CurrentWindow->GetID(str_id)); +} + +void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.empty()) + return; + + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. + // Don't close our own child popup windows. + int n = 0; + if (ref_window) + { + for (n = 0; n < g.OpenPopupStack.Size; n++) + { + ImGuiPopupRef& popup = g.OpenPopupStack[n]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) + bool has_focus = false; + for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) + has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); + if (!has_focus) + break; + } + } + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); +} + +static ImGuiWindow* GetFrontMostModalRootWindow() +{ + ImGuiContext& g = *GImGui; + for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +static void ClosePopupToLevel(int remaining) +{ + IM_ASSERT(remaining >= 0); + ImGuiContext& g = *GImGui; + ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow; + if (g.NavLayer == 0) + focus_window = NavRestoreLastChildNavWindow(focus_window); + ImGui::FocusWindow(focus_window); + focus_window->DC.NavHideHighlightOneFrame = true; + g.OpenPopupStack.resize(remaining); +} + +void ImGui::ClosePopup(ImGuiID id) +{ + if (!IsPopupOpen(id)) + return; + ImGuiContext& g = *GImGui; + ClosePopupToLevel(g.OpenPopupStack.Size - 1); +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext& g = *GImGui; + int popup_idx = g.CurrentPopupStack.Size - 1; + if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) + return; + while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) + popup_idx--; + ClosePopupToLevel(popup_idx); +} + +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (extra_flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::IsPopupOpen(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; +} + +bool ImGui::IsPopupOpen(const char* str_id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); +} + +bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + // Center modal windows by default + // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. + if (g.NextWindowData.PosCond == 0) + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); + if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + { + EndPopup(); + if (is_open) + ClosePopup(id); + return false; + } + + return is_open; +} + +static void NavProcessMoveRequestWrapAround(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.NavWindow == window && NavMoveRequestButNoResultYet()) + if ((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == 0) + { + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + g.NavWindow->NavRectRel[0].Min.y = g.NavWindow->NavRectRel[0].Max.y = ((g.NavMoveDir == ImGuiDir_Up) ? ImMax(window->SizeFull.y, window->SizeContents.y) : 0.0f) - window->Scroll.y; + } +} + +void ImGui::EndPopup() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls + IM_ASSERT(g.CurrentPopupStack.Size > 0); + + // Make all menus and popups wrap around for now, may need to expose that policy. + NavProcessMoveRequestWrapAround(g.CurrentWindow); + + End(); +} + +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id); + return true; + } + return false; +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. +bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) +{ + if (!str_id) + str_id = "window_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (also_over_items || !IsAnyItemHovered()) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) +{ + if (!str_id) + str_id = "void_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + const ImVec2 content_avail = ImGui::GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + flags |= extra_flags; + + char title[256]; + if (name) + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); + else + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); + + ImGui::SetNextWindowSize(size); + bool ret = ImGui::Begin(title, NULL, flags); + ImGuiWindow* child_window = ImGui::GetCurrentWindow(); + child_window->ChildId = id; + child_window->AutoFitChildAxises = auto_fit_axises; + g.Style.ChildBorderSize = backup_border_size; + + // Process navigation-in immediately so NavInit can run on first frame + if (!(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && g.NavActivateId == id) + { + ImGui::FocusWindow(child_window); + ImGui::NavInitWindow(child_window, false); + ImGui::SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item + g.ActiveIdSource = ImGuiInputSource_Nav; + } + + return ret; +} + +bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + IM_ASSERT(id != 0); + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss + if (window->BeginCount > 1) + { + End(); + } + else + { + // When using auto-filling child window, we don't provide full width/height to ItemSize so that it doesn't feed back into automatic size-fitting. + ImVec2 sz = GetWindowSize(); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + End(); + + ImGuiWindow* parent_window = g.CurrentWindow; + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) + { + ItemAdd(bb, window->ChildId); + RenderNavHighlight(bb, window->ChildId); + + // When browsing a window that has no activable items (scroll only) we keep a highlight on the child + if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow) + RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + } + else + { + // Not navigable into + ItemAdd(bb, 0); + } + } +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + return BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); +} + +void ImGui::EndChildFrame() +{ + EndChild(); + PopStyleVar(3); + PopStyleColor(); +} + +// Save and compare stack sizes on Begin()/End() to detect usage errors +static void CheckStacksSize(ImGuiWindow* window, bool write) +{ + // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + ImGuiContext& g = *GImGui; + int* p_backup = &window->DC.StackSizesBackup[0]; + { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop() + { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup() + { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup() + { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor() + { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar() + { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont() + IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); +} + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox +}; + +static ImVec2 FindBestWindowPosForPopup(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default) +{ + const ImGuiStyle& style = GImGui->Style; + + // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) + // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. + ImVec2 safe_padding = style.DisplaySafeAreaPadding; + ImRect r_outer(GetViewportRect()); + r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x*2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y*2) ? -safe_padding.y : 0.0f)); + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) + { + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Default popup policy + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + if (avail_w < size.x || avail_h < size.y) + continue; + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + *last_dir = dir; + return pos; + } + + // Fallback, try to keep within display + *last_dir = ImGuiDir_None; + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); +} + +ImGuiWindow* ImGui::FindWindowByName(const char* name) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = ImHash(name, 0); + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + { + // Retrieve settings from .ini file + // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + window->Pos = window->PosFloat = ImVec2(60, 60); + + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) + { + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + window->PosFloat = settings->Pos; + window->Pos = ImFloor(window->PosFloat); + window->Collapsed = settings->Collapsed; + if (ImLengthSqr(settings->Size) > 0.00001f) + size = settings->Size; + } + } + window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; + + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } + + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + return window; +} + +static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) +{ + ImGuiContext& g = *GImGui; + if (g.NextWindowData.SizeConstraintCond != 0) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.NextWindowData.SizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.NextWindowData.SizeCallback) + { + ImGuiSizeCallbackData data; + data.UserData = g.NextWindowData.SizeCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.NextWindowData.SizeCallback(&data); + new_size = data.DesiredSize; + } + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + { + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + } + return new_size; +} + +static ImVec2 CalcSizeContents(ImGuiWindow* window) +{ + ImVec2 sz; + sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); + sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); + return sz + window->WindowPadding; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + ImVec2 size_auto_fit; + if ((flags & ImGuiWindowFlags_Tooltip) != 0) + { + // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. + size_auto_fit = size_contents; + } + else + { + // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); + ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) + size_auto_fit.y += style.ScrollbarSize; + if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) + size_auto_fit.x += style.ScrollbarSize; + } + return size_auto_fit; +} + +static float GetScrollMaxX(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x)); +} + +static float GetScrollMaxY(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y)); +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) +{ + ImVec2 scroll = window->Scroll; + float cr_x = window->ScrollTargetCenterRatio.x; + float cr_y = window->ScrollTargetCenterRatio.y; + if (window->ScrollTarget.x < FLT_MAX) + scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); + if (window->ScrollTarget.y < FLT_MAX) + scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); + scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, GetScrollMaxX(window)); + scroll.y = ImMin(scroll.y, GetScrollMaxY(window)); + } + return scroll; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildBg; + return ImGuiCol_WindowBg; +} + +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +struct ImGuiResizeGripDef +{ + ImVec2 CornerPos; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; + +const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right + { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left + { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left + { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right +}; + +static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) rect.Max -= ImVec2(1,1); + if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness); + if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding); + if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y); + if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + IM_ASSERT(0); + return ImRect(); +} + +// Handle resize for: Resize Grips, Borders, Gamepad +static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return; + + const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0; + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f); + + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); + + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size); + resize_rect.FixInverted(); + bool hovered, held; + ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcSizeAfterConstraint(window, size_auto_fit); + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip + CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); + } + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) + { + const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check. + const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise + bool hovered, held; + ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE); + ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held) + { + g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) *border_held = border_n; + } + if (held) + { + ImVec2 border_target = window->Pos; + ImVec2 border_posn; + if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); } + if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); } + if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); } + if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); } + CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + } + } + PopID(); + + // Navigation/gamepad resize + if (g.NavWindowingTarget == window) + { + ImVec2 nav_resize_delta; + if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); + if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) + { + const float NAV_RESIZE_SPEED = 600.0f; + nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck. + size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta); + } + } + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) + { + window->Pos = window->PosFloat = ImFloor(pos_target); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; +} + +// Push a new ImGui window to add widgets to. +// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. +// - Begin/End can be called multiple times during the frame with the same window name to append content. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + IM_ASSERT(name != NULL); // Window name required + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + + // Find or create + ImGuiWindow* window = FindWindowByName(name); + if (!window) + { + ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. + window = CreateNewWindow(name, size_on_first_use, flags); + } + + // Automatically disable manual moving/resizing when NoInputs is set + if (flags & ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + if (flags & ImGuiWindowFlags_NavFlattened) + IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); + + const int current_frame = g.FrameCount; + const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); + if (first_begin_of_the_frame) + window->Flags = (ImGuiWindowFlags)flags; + else + flags = window->Flags; + + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); + window->CloseButton = (p_open != NULL); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack + ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + + // Add to stack + g.CurrentWindowStack.push_back(window); + SetCurrentWindow(window); + CheckStacksSize(window, true); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + popup_ref.Window = window; + g.CurrentPopupStack.push_back(popup_ref); + window->PopupId = popup_ref.PopupId; + } + + if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) + window->NavLastIds[0] = 0; + + // Process SetNextWindow***() calls + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.NextWindowData.PosCond) + { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) + { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.NextWindowData.PosVal; + window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } + else + { + SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); + } + g.NextWindowData.PosCond = 0; + } + if (g.NextWindowData.SizeCond) + { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); + SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); + g.NextWindowData.SizeCond = 0; + } + if (g.NextWindowData.ContentSizeCond) + { + // Adjust passed "client size" to become a "window size" + window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal; + if (window->SizeContentsExplicit.y != 0.0f) + window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); + g.NextWindowData.ContentSizeCond = 0; + } + else if (first_begin_of_the_frame) + { + window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); + } + if (g.NextWindowData.CollapsedCond) + { + SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); + g.NextWindowData.CollapsedCond = 0; + } + if (g.NextWindowData.FocusCond) + { + SetWindowFocus(); + g.NextWindowData.FocusCond = 0; + } + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) + { + const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) + + // Initialize + window->ParentWindow = parent_window; + window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = window->RootWindowForNav = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !window_is_child_tooltip) + window->RootWindow = parent_window->RootWindow; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = parent_window->RootWindowForTitleBarHighlight; // Same value in master branch, will differ for docking + while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + window->RootWindowForNav = window->RootWindowForNav->ParentWindow; + + window->Active = true; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = g.WindowsActiveCount++; + window->BeginCount = 0; + window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); + window->LastFrameActive = current_frame; + window->IDStack.resize(1); + + // Lock window rounding, border size and rounding so that altering the border sizes for children doesn't have side-effects. + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + + // Collapse window by double-clicking on title bar + // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + { + ImRect title_bar_rect = window->TitleBarRect(); + if (window->CollapseToggleWanted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])) + { + window->Collapsed = !window->Collapsed; + MarkIniSettingsDirty(window); + FocusWindow(window); + } + } + else + { + window->Collapsed = false; + } + window->CollapseToggleWanted = false; + + // SIZE + + // Update contents size from last frame for auto-fitting (unless explicitly specified) + window->SizeContents = CalcSizeContents(window); + + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + if (window->HiddenFrames > 0) + window->HiddenFrames--; + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user) + { + window->HiddenFrames = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) + { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->SizeContents = ImVec2(0.f, 0.f); + } + } + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); + ImVec2 size_full_modified(FLT_MAX, FLT_MAX); + if (flags & ImGuiWindowFlags_AlwaysAutoResize && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + window->SizeFull.x = size_full_modified.x = size_auto_fit.x; + if (!window_size_y_set_by_api) + window->SizeFull.y = size_full_modified.y = size_auto_fit.y; + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit only grows during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (!window->Collapsed) + MarkIniSettingsDirty(window); + } + + // Apply minimum/maximum window size constraints and final size + window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); + window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; + + // SCROLLBAR STATUS + + // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) + { + // When reading the current size we need to read it after size constraints have been applied + float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; + float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + } + + // POSITION + + // Popup latch its initial position, will position itself when it appears next frame + if (window_just_activated_by_user) + { + window->AutoPosLastDirection = ImGuiDir_None; + if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) + window->Pos = window->PosFloat = g.CurrentPopupStack.back().OpenPopupPos; + } + + // Position child window + if (flags & ImGuiWindowFlags_ChildWindow) + { + window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = window->PosFloat = parent_window->DC.CursorPos; + } + + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0); + if (window_pos_with_pivot) + { + // Position given a pivot (e.g. for centering) + SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); + } + else if (flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then our FindBestPopupWindowPos() function will move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(window_pos_set_by_api); + float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value). + ImGuiWindow* parent_menu = parent_window_in_stack; + ImRect rect_to_avoid; + if (parent_menu->DC.MenuBarAppending) + rect_to_avoid = ImRect(-FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight(), FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight() + parent_menu->MenuBarHeight()); + else + rect_to_avoid = ImRect(parent_menu->Pos.x + horizontal_overlap, -FLT_MAX, parent_menu->Pos.x + parent_menu->Size.x - horizontal_overlap - parent_menu->ScrollbarSizes.x, FLT_MAX); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + { + ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + + // Position tooltip (always follows mouse) + if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) + { + float sc = g.Style.MouseCursorScale; + ImVec2 ref_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos; + ImRect rect_to_avoid; + if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.NavFlags & ImGuiNavFlags_MoveMouse)) + rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + else + rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. + window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + if (window->AutoPosLastDirection == ImGuiDir_None) + window->PosFloat = ref_pos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + } + + // Clamp position so it stays visible + if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + { + if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + { + ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size; + window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding); + } + } + window->Pos = ImFloor(window->PosFloat); + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); + else + window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); + + // Prepare for focus requests + window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); + window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); + window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; + window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; + + // Apply scrolling + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + + // Apply focus, new windows appears in front + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + want_focus = true; + + // Handle manual resize: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = { 0 }; + const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4 + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + if (!window->Collapsed) + UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]); + + // DRAWING + + // Setup draw list and outer clipping rectangle + window->DrawList->Clear(); + window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + ImRect viewport_rect(GetViewportRect()); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) + PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); + else + PushClipRect(viewport_rect.Min, viewport_rect.Max, true); + + // Draw modal window background (darkens what is behind them) + if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) + window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio)); + + // Draw navigation selection/windowing rectangle background + if (g.NavWindowingTarget == window) + { + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); + } + + // Draw window + handle manual resize + const float window_rounding = window->WindowRounding; + const float window_border_size = window->WindowBorderSize; + const bool title_bar_is_highlight = want_focus || (g.NavWindow && window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); + const ImRect title_bar_rect = window->TitleBarRect(); + if (window->Collapsed) + { + // Title bar only + float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } + else + { + // Window background + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + if (g.NextWindowData.BgAlphaCond != 0) + { + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT); + g.NextWindowData.BgAlphaCond = 0; + } + window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); + + // Title bar + ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Scrollbars + if (window->ScrollbarX) + Scrollbar(ImGuiLayoutType_Horizontal); + if (window->ScrollbarY) + Scrollbar(ImGuiLayoutType_Vertical); + + // Render resize grips (after their input handling so we don't have a frame of latency) + if (!(flags & ImGuiWindowFlags_NoResize)) + { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } + } + + // Borders + if (window_border_size > 0.0f) + window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); + if (border_held != -1) + { + ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f); + window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); + } + if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Draw navigation selection/windowing rectangle border + if (g.NavWindowingTarget == window) + { + float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward + { + bb.Expand(-g.FontSize - 1.0f); + rounding = window->WindowRounding; + } + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + } + + // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. + window->SizeFullAtLastBegin = window->SizeFull; + + // Update ContentsRegionMax. All the variable it depends on are set above in this function. + window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; + window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); + window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); + window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); + + // Setup drawing context + // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) + window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffsetX = 0.0f; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.NavHideHighlightOneFrame = false; + window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f); + window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; + window->DC.NavLayerActiveMaskNext = 0x00; + window->DC.MenuBarAppending = false; + window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x); + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + window->DC.ChildWindows.resize(0); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + window->DC.ItemFlags = ImGuiItemFlags_Default_; + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemFlagsStack.resize(0); + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + window->DC.ColumnsSet = NULL; + window->DC.TreeDepth = 0; + window->DC.TreeDepthMayCloseOnPop = 0x00; + window->DC.StateStorage = &window->StateStorage; + window->DC.GroupStack.resize(0); + window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + + if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) + { + window->DC.ItemFlags = parent_window->DC.ItemFlags; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + } + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + { + FocusWindow(window); + NavInitWindow(window, false); + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + { + // Close & collapse button are on layer 1 (same as menus) and don't default focus + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + + // Collapse button + if (!(flags & ImGuiWindowFlags_NoCollapse)) + { + ImGuiID id = window->GetID("#COLLAPSE"); + ImRect bb(window->Pos + style.FramePadding + ImVec2(1,1), window->Pos + style.FramePadding + ImVec2(g.FontSize,g.FontSize) - ImVec2(1,1)); + ItemAdd(bb, id); // To allow navigation + if (ButtonBehavior(bb, id, NULL, NULL)) + window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function + RenderNavHighlight(bb, id); + RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + } + + // Close button + if (p_open != NULL) + { + const float PAD = 2.0f; + const float rad = (window->TitleBarHeight() - PAD*2.0f) * 0.5f; + if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-PAD - rad, PAD + rad), rad)) + *p_open = false; + } + + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.ItemFlags = item_flags_backup; + + // Title text (FIXME: refactor text alignment facilities along with RenderText helpers) + ImVec2 text_size = CalcTextSize(name, NULL, true); + ImRect text_r = title_bar_rect; + float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); + text_r.Min.x += pad_left; + text_r.Max.x -= pad_right; + ImRect clip_rect = text_r; + clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton() + RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); + } + + // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() + window->WindowRectClipped = window->Rect(); + window->WindowRectClipped.ClipWith(window->ClipRect); + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. + // Maybe we can support CTRL+C on every element? + /* + if (g.ActiveId == move_id) + if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + ImGui::LogToClipboard(); + */ + + // Inner rectangle + // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame + // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. + window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; + window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; + //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); + + // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.). + window->DC.LastItemId = window->MoveId; + window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; + window->DC.LastItemRect = title_bar_rect; + } + + // Inner clipping rectangle + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + const float border_size = window->WindowBorderSize; + ImRect clip_rect; + clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y); + clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y); + PushClipRect(clip_rect.Min, clip_rect.Max, true); + + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) + if (first_begin_of_the_frame) + window->WriteAccessed = false; + + window->BeginCount++; + g.NextWindowData.SizeConstraintCond = 0; + + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar). + if (flags & ImGuiWindowFlags_ChildWindow) + { + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + window->Collapsed = parent_window && parent_window->Collapsed; + + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y); + + // We also hide the window from rendering because we've already added its border to the command list. + // (we could perform the check earlier in the function but it is simpler at this point) + if (window->Collapsed) + window->Active = false; + } + if (style.Alpha <= 0.0f) + window->Active = false; + + // Return false if we don't intend to display anything to allow user to perform an early out optimization + window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0; + return !window->SkipItems; +} + +// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags) +{ + // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file. + if (size_first_use.x != 0.0f || size_first_use.y != 0.0f) + ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver); + + // Old API feature: override the window background alpha with a parameter. + if (bg_alpha_override >= 0.0f) + ImGui::SetNextWindowBgAlpha(bg_alpha_override); + + return ImGui::Begin(name, p_open, flags); +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +void ImGui::End() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + PopClipRect(); // Inner window clip rectangle + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + // Pop from window stack + g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.CurrentPopupStack.pop_back(); + CheckStacksSize(window, false); + SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); +} + +// Vertical scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +void ImGui::Scrollbar(ImGuiLayoutType direction) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const bool horizontal = (direction == ImGuiLayoutType_Horizontal); + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); + + // Render background + bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); + float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; + const ImRect window_rect = window->Rect(); + const float border_size = window->WindowBorderSize; + ImRect bb = horizontal + ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) + : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); + if (!horizontal) + bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); + if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) + return; + + int window_rounding_corners; + if (horizontal) + window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + else + window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); + bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); + float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; + float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; + float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); + const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + const bool previously_held = (g.ActiveId == id); + ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + + float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); + float scroll_ratio = ImSaturate(scroll_v / scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + if (held && grab_h_norm < 1.0f) + { + float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; + float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + SetHoveredID(id); + + bool seek_absolute = false; + if (!previously_held) + { + // On initial click calculate the distance between mouse and the center of the grab + if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) + { + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + else + { + seek_absolute = true; + *click_delta_to_grab_center_v = 0.0f; + } + } + + // Apply scroll + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position + const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); + scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); + if (horizontal) + window->Scroll.x = scroll_v; + else + window->Scroll.y = scroll_v; + + // Update values for rendering + scroll_ratio = ImSaturate(scroll_v / scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seeked and saturated + if (seek_absolute) + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + + // Render + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); + ImRect grab_rect; + if (horizontal) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); +} + +void ImGui::BringWindowToFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window + if (g.Windows[i] == window) + { + g.Windows.erase(g.Windows.Data + i); + g.Windows.push_back(window); + break; + } +} + +void ImGui::BringWindowToBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + if (g.NavWindow != window) + { + g.NavWindow = window; + if (window && g.NavDisableMouseHover) + g.NavMousePosDirty = true; + g.NavInitRequest = false; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavIdIsAlive = false; + g.NavLayer = 0; + } + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Move the root window to the top of the pile + if (window->RootWindow) + window = window->RootWindow; + + // Steal focus on active widgets + if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) + ClearActiveID(); + + // Bring to front + if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) + BringWindowToFront(window); +} + +void ImGui::FocusFrontMostActiveWindow(ImGuiWindow* ignore_window) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) + { + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]); + FocusWindow(focus_window); + return; + } +} + +void ImGui::PushItemWidth(float item_width) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = GImGui->Style; + if (w_full <= 0.0f) + w_full = CalcItemWidth(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components-1; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidthStack.pop_back(); + window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); +} + +float ImGui::CalcItemWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + float w = window->DC.ItemWidth; + if (w < 0.0f) + { + // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. + float width_to_right_edge = GetContentRegionAvail().x; + w = ImMax(1.0f, width_to_right_edge + w); + } + w = (float)(int)w; + return w; +} + +static ImFont* GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; +} + +void ImGui::SetCurrentFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; + g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + + ImFontAtlas* atlas = g.Font->ContainerAtlas; + g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (enabled) + window->DC.ItemFlags |= option; + else + window->DC.ItemFlags &= ~option; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); +} + +void ImGui::PopItemFlag() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); +} + +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPosStack.push_back(wrap_pos_x); +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPosStack.pop_back(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); +} + +// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 +void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiColMod& backup = g.ColorModifiers.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorModifiers.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo +{ + ImGuiDataType Type; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImGuiStyleVarInfo GStyleVarInfo[] = +{ + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_); + IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_Count_); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float) + { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float2) + { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiStyleMod& backup = g.StyleModifiers.back(); + const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); + if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr(&g.Style)) = backup.BackupFloat[0]; + else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr(&g.Style)) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); + else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr(&g.Style)) = backup.BackupInt[0]; + g.StyleModifiers.pop_back(); + count--; + } +} + +const char* ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) + { + case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; + case ImGuiCol_WindowBg: return "WindowBg"; + case ImGuiCol_ChildBg: return "ChildBg"; + case ImGuiCol_PopupBg: return "PopupBg"; + case ImGuiCol_Border: return "Border"; + case ImGuiCol_BorderShadow: return "BorderShadow"; + case ImGuiCol_FrameBg: return "FrameBg"; + case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: return "FrameBgActive"; + case ImGuiCol_TitleBg: return "TitleBg"; + case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_SliderGrab: return "SliderGrab"; + case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; + case ImGuiCol_Button: return "Button"; + case ImGuiCol_ButtonHovered: return "ButtonHovered"; + case ImGuiCol_ButtonActive: return "ButtonActive"; + case ImGuiCol_Header: return "Header"; + case ImGuiCol_HeaderHovered: return "HeaderHovered"; + case ImGuiCol_HeaderActive: return "HeaderActive"; + case ImGuiCol_Separator: return "Separator"; + case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: return "SeparatorActive"; + case ImGuiCol_ResizeGrip: return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_CloseButton: return "CloseButton"; + case ImGuiCol_CloseButtonHovered: return "CloseButtonHovered"; + case ImGuiCol_CloseButtonActive: return "CloseButtonActive"; + case ImGuiCol_PlotLines: return "PlotLines"; + case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening"; + case ImGuiCol_DragDropTarget: return "DragDropTarget"; + case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; + } + IM_ASSERT(0); + return "Unknown"; +} + +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) + { + if (window == potential_parent) + return true; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function + ImGuiContext& g = *GImGui; + + if (flags & ImGuiHoveredFlags_AnyWindow) + { + if (g.HoveredWindow == NULL) + return false; + } + else + { + switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) + { + case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: + if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_RootWindow: + if (g.HoveredWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_ChildWindows: + if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) + return false; + break; + default: + if (g.HoveredWindow != g.CurrentWindow) + return false; + break; + } + } + + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) + return false; + return true; +} + +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() + + if (flags & ImGuiFocusedFlags_AnyWindow) + return g.NavWindow != NULL; + + switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) + { + case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_RootWindow: + return g.NavWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); + default: + return g.NavWindow == g.CurrentWindow; + } +} + +// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + return window->Active && window == window->RootWindowForTabbing && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow); +} + +float ImGui::GetWindowWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.x; +} + +float ImGui::GetWindowHeight() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.y; +} + +ImVec2 ImGui::GetWindowPos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return window->Pos; +} + +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x) +{ + window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.x = new_scroll_x; + window->DC.CursorMaxPos.x -= window->Scroll.x; +} + +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) +{ + window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.y = new_scroll_y; + window->DC.CursorMaxPos.y -= window->Scroll.y; +} + +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowPosAllowFlags & cond) == 0) + return; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + + // Set + const ImVec2 old_pos = window->Pos; + window->PosFloat = pos; + window->Pos = ImFloor(pos); + window->DC.CursorPos += (window->Pos - old_pos); // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. +} + +void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + SetWindowPos(window, pos, cond); +} + +void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowPos(window, pos, cond); +} + +ImVec2 ImGui::GetWindowSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Size; +} + +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) + return; + window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + if (size.x > 0.0f) + { + window->AutoFitFramesX = 0; + window->SizeFull.x = size.x; + } + else + { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) + { + window->AutoFitFramesY = 0; + window->SizeFull.y = size.y; + } + else + { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) +{ + SetWindowSize(GImGui->CurrentWindow, size, cond); +} + +void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowSize(window, size, cond); +} + +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) + return; + window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + window->Collapsed = collapsed; +} + +void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); +} + +bool ImGui::IsWindowCollapsed() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; +} + +void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowCollapsed(window, collapsed, cond); +} + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.PosVal = pos; + g.NextWindowData.PosPivotVal = pivot; + g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeVal = size; + g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeConstraintCond = ImGuiCond_Always; + g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); + g.NextWindowData.SizeCallback = custom_callback; + g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; +} + +void ImGui::SetNextWindowContentSize(const ImVec2& size) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.ContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. + g.NextWindowData.ContentSizeCond = ImGuiCond_Always; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.CollapsedVal = collapsed; + g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +void ImGui::SetNextWindowBgAlpha(float alpha) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.BgAlphaVal = alpha; + g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +// In window space (not screen space!) +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImVec2 mx = window->ContentsRegionRect.Max; + if (window->DC.ColumnsSet) + mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); +} + +float ImGui::GetContentRegionAvailWidth() +{ + return GetContentRegionAvail().x; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetWindowContentRegionMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Min; +} + +ImVec2 ImGui::GetWindowContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max; +} + +float ImGui::GetWindowContentRegionWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x; +} + +float ImGui::GetTextLineHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize; +} + +float ImGui::GetTextLineHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.ItemSpacing.y; +} + +float ImGui::GetFrameHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + +float ImGui::GetFrameHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; +} + +ImDrawList* ImGui::GetWindowDrawList() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont* ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +void ImGui::SetWindowFontScale(float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->FontWindowScale = scale; + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. +// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. +ImVec2 ImGui::GetCursorPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos - window->Pos + window->Scroll; +} + +float ImGui::GetCursorPosX() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; +} + +float ImGui::GetCursorPosY() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; +} + +void ImGui::SetCursorPos(const ImVec2& local_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); +} + +void ImGui::SetCursorPosY(float y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); +} + +ImVec2 ImGui::GetCursorStartPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorStartPos - window->Pos; +} + +ImVec2 ImGui::GetCursorScreenPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos; +} + +void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = screen_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +float ImGui::GetScrollX() +{ + return GImGui->CurrentWindow->Scroll.x; +} + +float ImGui::GetScrollY() +{ + return GImGui->CurrentWindow->Scroll.y; +} + +float ImGui::GetScrollMaxX() +{ + return GetScrollMaxX(GImGui->CurrentWindow); +} + +float ImGui::GetScrollMaxY() +{ + return GetScrollMaxY(GImGui->CurrentWindow); +} + +void ImGui::SetScrollX(float scroll_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.x = scroll_x; + window->ScrollTargetCenterRatio.x = 0.0f; +} + +void ImGui::SetScrollY(float scroll_y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY + window->ScrollTargetCenterRatio.y = 0.0f; +} + +void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) +{ + // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); + window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); + window->ScrollTargetCenterRatio.y = center_y_ratio; + + // Minor hack to to make scrolling to top/bottom of window take account of WindowPadding, it looks more right to the user this way + if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y) + window->ScrollTarget.y = 0.0f; + else if (center_y_ratio >= 1.0f && window->ScrollTarget.y >= window->SizeContents.y - window->WindowPadding.y + GImGui->Style.ItemSpacing.y) + window->ScrollTarget.y = window->SizeContents.y; +} + +// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. +void ImGui::SetScrollHere(float center_y_ratio) +{ + ImGuiWindow* window = GetCurrentWindow(); + float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space + target_y += (window->DC.PrevLineHeight * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line. + SetScrollFromPosY(target_y, center_y_ratio); +} + +void ImGui::ActivateItem(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavNextActivateId = id; +} + +void ImGui::SetKeyboardFocusHere(int offset) +{ + IM_ASSERT(offset >= -1); // -1 is allowed but not below + ImGuiWindow* window = GetCurrentWindow(); + window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; + window->FocusIdxTabRequestNext = INT_MAX; +} + +void ImGui::SetItemDefaultFocus() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!window->Appearing) + return; + if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent) + { + g.NavInitRequest = false; + g.NavInitResultId = g.NavWindow->DC.LastItemId; + g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); + NavUpdateAnyRequestFlag(); + if (!IsItemVisible()) + SetScrollHere(); + } +} + +void ImGui::SetStateStorage(ImGuiStorage* tree) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.StateStorage = tree ? tree : &window->StateStorage; +} + +ImGuiStorage* ImGui::GetStateStorage() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.StateStorage; +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + TextUnformatted(g.TempBuffer, text_end); +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set + if (need_wrap) PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_wrap) PopTextWrapPos(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(text != NULL); + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = wrap_pos_x >= 0.0f; + if (text_end - text > 2000 && !wrap_enabled) + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + const char* line = text; + const float line_height = GetTextLineHeight(); + const ImRect clip_rect = window->ClipRect; + ImVec2 text_size(0,0); + + if (text_pos.y <= clip_rect.Max.y) + { + ImVec2 pos = text_pos; + + // Lines to skip (can't skip when logging text) + if (!g.LogEnabled) + { + int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (IsClippedEx(line_rect, 0, false)) + break; + + const ImVec2 line_size = CalcTextSize(line, line_end, false); + text_size.x = ImMax(text_size.x, line_size.x); + RenderText(pos, line, line_end, false); + if (!line_end) + line_end = text_end; + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + + text_size.y += (pos - text_pos).y; + } + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(bb); + ItemAdd(bb, 0); + } + else + { + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + // Account of baseline offset + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); + const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) ClearActiveID(); + return false; + } + + // Default behavior requires click+release on same spot + if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) + flags |= ImGuiButtonFlags_PressedOnClickRelease; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = window; + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && g.DragDropActive && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy + { + pressed = true; + FocusWindow(window); + } + } + + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = backup_hovered_window; + + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + // Mouse + if (hovered) + { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + { + // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat + // PressedOnClickRelease | * | .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds + // PressedOnClick | | .. + // PressedOnRelease | | .. (NOT on release) + // PressedOnDoubleClick | | .. + // FIXME-NAV: We don't honor those different behaviors. + if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) + { + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + FocusWindow(window); + } + if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveID) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) + { + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + pressed = true; + ClearActiveID(); + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) + pressed = true; + } + + if (pressed) + g.NavDisableHighlight = true; + } + + // Gamepad/Keyboard navigation + // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + hovered = true; + + if (g.NavActivateDownId == id) + { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); + if (nav_activated_by_code || nav_activated_by_inputs) + pressed = true; + if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + g.NavActivateId = id; // This is so SetActiveId assign a Nav source + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } + } + + bool held = false; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + if (g.IO.MouseDown[0]) + { + held = true; + } + else + { + if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + if (!g.DragDropActive) + pressed = true; + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + g.NavDisableHighlight = true; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + if (g.NavActivateDownId != id) + ClearActiveID(); + } + } + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, 0); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + return pressed; +} + +// Button to close a window +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. + // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); + bool is_clipped = !ItemAdd(bb, id); + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (is_clipped) + return pressed; + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton); + ImVec2 center = bb.GetCenter(); + window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12); + + const float cross_extent = (radius * 0.7071f) - 1.0f; + if (hovered) + { + center -= ImVec2(0.5f, 0.5f); + window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), GetColorU32(ImGuiCol_Text)); + window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), GetColorU32(ImGuiCol_Text)); + } + return pressed; +} + +// [Internal] +bool ImGui::ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + padding.x * 2.0f, g.FontSize + padding.y * 2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTriangle(bb.Min + padding, dir, 1.0f); + + return pressed; +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2,2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) + { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col)); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +// The color used are the button colors. +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. + PushID((void *)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2); + const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +// Start logging ImGui output to TTY +void ImGui::LogToTTY(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = stdout; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to given file +void ImGui::LogToFile(int max_depth, const char* filename) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + if (!filename) + { + filename = g.IO.LogFilename; + if (!filename) + return; + } + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = ImFileOpen(filename, "ab"); + if (!g.LogFile) + { + IM_ASSERT(g.LogFile != NULL); // Consider this an error + return; + } + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to clipboard +void ImGui::LogToClipboard(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = NULL; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +void ImGui::LogFinish() +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + if (g.LogFile != NULL) + { + if (g.LogFile == stdout) + fflush(g.LogFile); + else + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard->size() > 1) + { + SetClipboardText(g.LogClipboard->begin()); + g.LogClipboard->clear(); + } + g.LogEnabled = false; +} + +// Helper to display logging buttons +void ImGui::LogButtons() +{ + ImGuiContext& g = *GImGui; + + PushID("LogButtons"); + const bool log_to_tty = Button("Log To TTY"); SameLine(); + const bool log_to_file = Button("Log To File"); SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); + PushItemWidth(80.0f); + PushAllowKeyboardFocus(false); + SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); + PopAllowKeyboardFocus(); + PopItemWidth(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(g.LogAutoExpandMaxDepth); + if (log_to_file) + LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); + if (log_to_clipboard) + LogToClipboard(g.LogAutoExpandMaxDepth); +} + +bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions) + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.NextTreeNodeOpenCond != 0) + { + if (g.NextTreeNodeOpenCond & ImGuiCond_Always) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + g.NextTreeNodeOpenCond = 0; + } + else + { + is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) + is_open = true; + + return is_open; +} + +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + // We vertically grow up to current line height up the typical widget height. + const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); + ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); + if (display_frame) + { + // Framed header expand a little outside the default padding + frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; + frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; + } + + const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing + const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser + ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) + const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y); + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + + // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child. + // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). + // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero. + if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavCloseFromChild) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + window->DC.TreeDepthMayCloseOnPop |= (1 << window->DC.TreeDepth); + + bool item_add = ItemAdd(interact_bb, id); + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + window->DC.LastItemDisplayRect = frame_bb; + + if (!item_add) + { + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; + } + + // Flags that affects opening behavior: + // - 0(default) ..................... single-click anywhere to open + // - OpenOnDoubleClick .............. double-click anywhere to open + // - OpenOnArrow .................... single-click on arrow to open + // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open + ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); + + bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + { + bool toggled = false; + if (pressed) + { + toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id); + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover); + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + toggled |= g.IO.MouseDoubleClicked[0]; + if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = false; + } + + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) + { + toggled = true; + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavMoveRequestCancel(); + } + + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y); + if (display_frame) + { + // Framed type + RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + RenderTriangle(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + if (g.LogEnabled) + { + // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. + const char log_prefix[] = "\n##"; + const char log_suffix[] = "##"; + LogRenderedText(&text_pos, log_prefix, log_prefix+3); + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); + } + else + { + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + } + } + else + { + // Unframed typed for tree nodes + if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) + { + RenderFrame(frame_bb.Min, frame_bb.Max, col, false); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + } + + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); + else if (!(flags & ImGuiTreeNodeFlags_Leaf)) + RenderTriangle(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogRenderedText(&text_pos, ">"); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label); +} + +bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_open && !*p_open) + return false; + + ImGuiID id = window->GetID(label); + bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label); + if (p_open) + { + // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + ImGuiContext& g = *GImGui; + float button_sz = g.FontSize * 0.5f; + ImGuiItemHoveredDataBackup last_item_backup; + if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_sz, window->DC.LastItemRect.Min.y + g.Style.FramePadding.y + button_sz), button_sz)) + *p_open = false; + last_item_backup.Restore(); + } + + return is_open; +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, label, NULL); +} + +void ImGui::TreeAdvanceToLabelPos() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextTreeNodeOpenVal = is_open; + g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::PushID(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id)); +} + +void ImGui::PushID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id_begin, str_id_end)); +} + +void ImGui::PushID(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PushID(int int_id) +{ + const void* ptr_id = (void*)(intptr_t)int_id; + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PopID() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.pop_back(); +} + +ImGuiID ImGui::GetID(const char* str_id) +{ + return GImGui->CurrentWindow->GetID(str_id); +} + +ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) +{ + return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); +} + +ImGuiID ImGui::GetID(const void* ptr_id) +{ + return GImGui->CurrentWindow->GetID(ptr_id); +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x*2); + return; + } + + // Render and stay on same line + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + SameLine(0, style.FramePadding.x*2); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin = g.TempBuffer; + const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + // Render + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + ImFormatString(buf, buf_size, display_format, *(int*)data_ptr); + else if (data_type == ImGuiDataType_Float) + ImFormatString(buf, buf_size, display_format, *(float*)data_ptr); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%d", *(int*)data_ptr); + else + ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr); + } + else if (data_type == ImGuiDataType_Float) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%f", *(float*)data_ptr); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + else + ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr); + } +} + +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2)// Store into value1 +{ + if (data_type == ImGuiDataType_Int) + { + if (op == '+') + *(int*)value1 = *(int*)value1 + *(const int*)value2; + else if (op == '-') + *(int*)value1 = *(int*)value1 - *(const int*)value2; + } + else if (data_type == ImGuiDataType_Float) + { + if (op == '+') + *(float*)value1 = *(float*)value1 + *(const float*)value2; + else if (op == '-') + *(float*)value1 = *(float*)value1 - *(const float*)value2; + } +} + +// User can input math operators (e.g. +100) to edit a numerical values. +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format) +{ + while (ImCharIsSpace(*buf)) + buf++; + + // We don't support '-' op because it would conflict with inputing negative value. + // Instead you can use +-100 to subtract from an existing value + char op = buf[0]; + if (op == '+' || op == '*' || op == '/') + { + buf++; + while (ImCharIsSpace(*buf)) + buf++; + } + else + { + op = 0; + } + if (!buf[0]) + return false; + + if (data_type == ImGuiDataType_Int) + { + if (!scalar_format) + scalar_format = "%d"; + int* v = (int*)data_ptr; + const int old_v = *v; + int arg0i = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1) + return false; + + // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision + float arg1f = 0.0f; + if (op == '+') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i + arg1f); } // Add (use "+-" to subtract) + else if (op == '*') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i * arg1f); } // Multiply + else if (op == '/') { if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }// Divide + else { if (sscanf(buf, scalar_format, &arg0i) == 1) *v = arg0i; } // Assign constant (read as integer so big values are not lossy) + return (old_v != *v); + } + else if (data_type == ImGuiDataType_Float) + { + // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in + scalar_format = "%f"; + float* v = (float*)data_ptr; + const float old_v = *v; + float arg0f = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1) + return false; + + float arg1f = 0.0f; + if (sscanf(buf, scalar_format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + return (old_v != *v); + } + + return false; +} + +// Create text input in place of a slider (when CTRL+Clicking on slider) +// FIXME: Logic is messy and confusing. +bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) + // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id + SetActiveID(g.ScalarAsInputTextId, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetHoveredID(0); + FocusableItemUnregister(window); + + char buf[32]; + DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf)); + bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); + if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget + { + IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID (else we'd need to store them both, which is also possible) + g.ScalarAsInputTextId = g.ActiveId; + SetHoveredID(id); + } + if (text_value_changed) + return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL); + return false; +} + +// Parse display precision back from the display format string +int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) +{ + int precision = default_precision; + while ((fmt = strchr(fmt, '%')) != NULL) + { + fmt++; + if (fmt[0] == '%') { fmt++; continue; } // Ignore "%%" + while (*fmt >= '0' && *fmt <= '9') + fmt++; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 10) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + break; + } + return precision; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision); +} + +float ImGui::RoundScalar(float value, int decimal_precision) +{ + // Round past decimal precision + // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0 + // FIXME: Investigate better rounding methods + if (decimal_precision < 0) + return value; + const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision); + bool negative = value < 0.0f; + value = fabsf(value); + float remainder = fmodf(value, min_step); + if (remainder <= min_step*0.5f) + value -= remainder; + else + value += (min_step - remainder); + return negative ? -value : value; +} + +static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) +{ + if (v_min == v_max) + return 0.0f; + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_non_linear) + { + if (v_clamped < 0.0f) + { + const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f,v_max) - v_min); + return (1.0f - powf(f, 1.0f/power)) * linear_zero_pos; + } + else + { + const float f = (v_clamped - ImMax(0.0f,v_min)) / (v_max - ImMax(0.0f,v_min)); + return linear_zero_pos + powf(f, 1.0f/power) * (1.0f - linear_zero_pos); + } + } + + // Linear slider + return (v_clamped - v_min) / (v_max - v_min); +} + +bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32((g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Nav) ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; + + const float grab_padding = 2.0f; + const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); + float grab_sz; + if (decimal_precision != 0) + grab_sz = ImMin(style.GrabMinSize, slider_sz); + else + grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); // Integer sliders, if possible have the grab size represent 1 unit + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz*0.5f; + const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz*0.5f; + + // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f + float linear_zero_pos = 0.0f; // 0.0->1.0f + if (v_min * v_max < 0.0f) + { + // Different sign + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); + linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); + } + else + { + // Same sign + linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; + } + + // Process interacting with the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (!is_horizontal) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); + float delta = is_horizontal ? delta2.x : -delta2.y; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + { + ClearActiveID(); + } + else if (delta != 0.0f) + { + clicked_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (decimal_precision == 0 && !is_non_linear) + { + if (fabsf(v_max - v_min) <= 100.0f || IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (v_max - v_min); // Gamepad/keyboard tweak speeds in integer steps + else + delta /= 100.0f; + } + else + { + delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + if (IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta /= 10.0f; + } + if (IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= 10.0f; + set_new_value = true; + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits + set_new_value = false; + else + clicked_t = ImSaturate(clicked_t + delta); + } + } + + if (set_new_value) + { + float new_value; + if (is_non_linear) + { + // Account for logarithmic scale on both sides of the zero + if (clicked_t < linear_zero_pos) + { + // Negative: rescale to the negative range before powering + float a = 1.0f - (clicked_t / linear_zero_pos); + a = powf(a, power); + new_value = ImLerp(ImMin(v_max,0.0f), v_min, a); + } + else + { + // Positive: rescale to the positive range before powering + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f) + a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = clicked_t; + a = powf(a, power); + new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); + } + } + else + { + // Linear slider + new_value = ImLerp(v_min, v_max, clicked_t); + } + + // Round past decimal precision + new_value = RoundScalar(new_value, decimal_precision); + if (*v != new_value) + { + *v = new_value; + value_changed = true; + } + } + } + + // Draw + float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (!is_horizontal) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + ImRect grab_bb; + if (is_horizontal) + grab_bb = ImRect(ImVec2(grab_pos - grab_sz*0.5f, frame_bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz*0.5f, frame_bb.Max.y - grab_padding)); + else + grab_bb = ImRect(ImVec2(frame_bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f), ImVec2(frame_bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f)); + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + return value_changed; +} + +// Use power!=1.0 for logarithmic sliders. +// Adjust display_format to decorate the value with a prefix or a suffix. +// "%.3f" 1.234 +// "%5.2f secs" 01.23 secs +// "Gold: %.0f" Gold: 1 +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual slider behavior + render grab + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + + // Actual slider behavior + render grab + bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) +{ + float v_deg = (*v_rad) * 360.0f / (2*IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); + *v_rad = v_deg * (2*IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 2, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 3, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 4, v_min, v_max, display_format, power); +} + +bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 2, v_min, v_max, display_format); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 3, v_min, v_max, display_format); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 4, v_min, v_max, display_format); +} + +bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + bool value_changed = false; + + // Process interacting with the drag + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId == id) + { + if (g.ActiveIdIsJustActivated) + { + // Lock current value on click + g.DragCurrentValue = *v; + g.DragLastMouseDelta = ImVec2(0.f, 0.f); + } + + if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX) + v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio; + + float v_cur = g.DragCurrentValue; + const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f); + float adjust_delta = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid()) + { + adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x; + if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f) + adjust_delta *= g.DragSpeedScaleFast; + if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f) + adjust_delta *= g.DragSpeedScaleSlow; + g.DragLastMouseDelta.x = mouse_drag_delta.x; + } + if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; + if (v_min < v_max && ((v_cur >= v_max && adjust_delta > 0.0f) || (v_cur <= v_min && adjust_delta < 0.0f))) // This is to avoid applying the saturation when already past the limits + adjust_delta = 0.0f; + v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); + } + adjust_delta *= v_speed; + + if (fabsf(adjust_delta) > 0.0f) + { + if (fabsf(power - 1.0f) > 0.001f) + { + // Logarithmic curve on both side of 0.0 + float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur; + float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f; + float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign); + float v1_abs = v1 >= 0.0f ? v1 : -v1; + float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line + v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign + } + else + { + v_cur += adjust_delta; + } + + // Clamp + if (v_min < v_max) + v_cur = ImClamp(v_cur, v_min, v_max); + g.DragCurrentValue = v_cur; + } + + // Round to user desired precision, then apply + v_cur = RoundScalar(v_cur, decimal_precision); + if (*v != v_cur) + { + *v = v_cur; + value_changed = true; + } + } + + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Drag turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual drag behavior + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + return value_changed; +} + +bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* display_format, const char* display_format_max, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, display_format_max ? display_format_max : display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* display_format, const char* display_format_max) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, display_format_max ? display_format_max : display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + if (graph_size.x == 0.0f) + graph_size.x = CalcItemWidth(); + if (graph_size.y == 0.0f) + graph_size.y = label_size.y + (style.FramePadding.y * 2); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0, &frame_bb)) + return; + const bool hovered = ItemHoverable(inner_bb, 0); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + if (values_count > 0) + { + int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + int v_hovered = -1; + if (hovered) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + v_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); +} + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); + } + + if (!ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + *v = !(*v); + + RenderNavHighlight(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + if (*v) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + bool v = ((*flags & flags_value) == flags_value); + bool pressed = Checkbox(label, &v); + if (pressed) + { + if (v) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + + return pressed; +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb.Add(text_bb); + } + + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = (float)(int)center.x + 0.5f; + center.y = (float)(int)center.y + 0.5f; + const float radius = check_bb.GetHeight() * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + + RenderNavHighlight(total_bb, id); + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); + if (active) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + { + *v = v_button; + } + return pressed; +} + +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImFont* font = GImGui->Font; + const float line_height = GImGui->FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((unsigned short)c) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +namespace ImGuiStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) +{ + const ImWchar* text = obj->Text.Data; + const ImWchar* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool is_separator(unsigned int c) { return ImCharIsSpace(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->Text[idx-1] ) && !is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +#ifdef __APPLE__ // FIXME: Move setting to IO structure +static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +#else +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +#endif +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) +{ + ImWchar* dst = obj->Text.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text + const ImWchar* src = obj->Text.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) +{ + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + if (new_text_len + text_len + 1 > obj->Text.Size) + return false; + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA) + return false; + + ImWchar* text = obj->Text.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->Text[obj->CurLenW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_SHIFT 0x20000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + +} + +void ImGuiTextEditState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &StbState, key); + CursorFollow = true; + CursorAnimReset(); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos + bytes_count >= pos) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen + 1 >= BufSize) + return; + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + unsigned int c = *p_char; + + if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } + + if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. + return false; + + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank)) + { + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + *p_char = (c += (unsigned int)('A'-'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsSpace(c)) + return false; + } + + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Edit a string of text +// NB: when active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while active has no effect. +// FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188 +bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + + ImGuiContext& g = *GImGui; + const ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + + if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); + + ImGuiWindow* draw_window = window; + if (is_multiline) + { + ItemAdd(total_bb, id, &frame_bb); + if (!BeginChildFrame(id, frame_bb.GetSize())) + { + EndChildFrame(); + EndGroup(); + return false; + } + draw_window = GetCurrentWindow(); + size.x -= draw_window->ScrollbarSizes.x; + } + else + { + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // Password pushes a temporary font with only a fallback glyph + if (is_password) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->DisplayOffset = g.Font->DisplayOffset; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // NB: we are only allowed to access 'edit_state' if we are the active widget. + ImGuiTextEditState& edit_state = g.InputTextState; + + const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); + const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + + bool clear_active_id = false; + + bool select_all = (g.ActiveId != id) && (((flags & ImGuiInputTextFlags_AutoSelectAll) != 0) || (g.NavInputId == id)) && (!is_multiline); + if (focus_requested || user_clicked || user_scrolled || g.NavInputId == id) + { + if (g.ActiveId != id) + { + // Start edition + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int prev_len_w = edit_state.CurLenW; + edit_state.Text.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + edit_state.InitialText.resize(buf_size+1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + edit_state.CursorAnimReset(); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). + const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW); + if (recycle_state) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + edit_state.CursorClamp(); + } + else + { + edit_state.Id = id; + edit_state.ScrollX = 0.0f; + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); + if (!is_multiline && focus_requested_by_code) + select_all = true; + } + if (flags & ImGuiInputTextFlags_AlwaysInsertMode) + edit_state.StbState.insert_mode = true; + if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + select_all = true; + } + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)); + } + else if (io.MouseClicked[0]) + { + // Release focus when we click outside + clear_active_id = true; + } + + bool value_changed = false; + bool enter_pressed = false; + + if (g.ActiveId == id) + { + if (!is_editable && !g.ActiveIdIsJustActivated) + { + // When read-only we always use the live data passed to the function + edit_state.Text.resize(buf_size+1); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); + edit_state.CursorClamp(); + } + + edit_state.BufSizeA = buf_size; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); + + const bool osx_double_click_selects_words = io.OptMacOSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text + if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0])) + { + edit_state.SelectAll(); + edit_state.SelectedAllMouseLock = true; + } + else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0]) + { + // Select a word only, OS X style (by simulating keystrokes) + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) + { + if (hovered) + { + stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + } + } + else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + edit_state.CursorFollow = true; + } + if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) + edit_state.SelectedAllMouseLock = false; + + if (io.InputCharacters[0]) + { + // Process text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow CTRL+ALT as some keyboards (e.g. German) use AltGR - which is Alt+Ctrl - to input certain characters. + if (!(io.KeyCtrl && !io.KeyAlt) && is_editable) + { + for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) + if (unsigned int c = (unsigned int)io.InputCharacters[n]) + { + // Insert character if they pass filtering + if (!InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + edit_state.OnKeyPressed((int)c); + } + } + + // Consume characters + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + } + } + + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + // Handle key-presses + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_shortcut_key_only = (io.OptMacOSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl + const bool is_wordmove_key_down = io.OptMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = io.OptMacOSXBehaviors && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; + const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; + + const bool is_cut = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_copy = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_paste = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; + + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) + { + if (!edit_state.HasSelection()) + { + if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); + else if (io.OptMacOSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); + } + edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (IsKeyPressedMap(ImGuiKey_Enter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + enter_pressed = clear_active_id = true; + } + else if (is_editable) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } + else if (is_cut || is_copy) + { + // Cut, Copy + if (io.SetClipboardTextFn) + { + const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; + const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; + edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie); + SetClipboardText(edit_state.TempTextBuffer.Data); + } + + if (is_cut) + { + if (!edit_state.HasSelection()) + edit_state.SelectAll(); + edit_state.CursorFollow = true; + stb_textedit_cut(&edit_state, &edit_state.StbState); + } + } + else if (is_paste) + { + // Paste + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s; ) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) + break; + if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + edit_state.CursorFollow = true; + } + ImGui::MemFree(clipboard_filtered); + } + } + } + + if (g.ActiveId == id) + { + if (cancel_edit) + { + // Restore initial value + if (is_editable) + { + ImStrncpy(buf, edit_state.InitialText.Data, buf_size); + value_changed = true; + } + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + if (is_editable) + { + edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL); + } + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_COUNT; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + event_flag = ImGuiInputTextFlags_CallbackAlways; + + if (event_flag) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = user_data; + callback_data.ReadOnly = !is_editable; + + callback_data.EventKey = event_key; + callback_data.Buf = edit_state.TempTextBuffer.Data; + callback_data.BufTextLen = edit_state.CurLenA; + callback_data.BufSize = edit_state.BufSizeA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) + ImWchar* text = edit_state.Text.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA); + IM_ASSERT(callback_data.Flags == flags); + if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); + if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); + if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); + if (callback_data.BufDirty) + { + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL); + edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + edit_state.CursorAnimReset(); + } + } + } + + // Copy back to user buffer + if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0) + { + ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size); + value_changed = true; + } + } + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + + // Render + // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL; + + RenderNavHighlight(frame_bb, id); + if (!is_multiline) + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.f, 0.f); + const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); + if (g.ActiveId == id || is_currently_scrolling) + { + edit_state.CursorAnim += io.DeltaTime; + + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar* text_begin = edit_state.Text.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. + const ImWchar* searches_input_ptr[2]; + searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; + searches_input_ptr[1] = NULL; + int searches_remaining = 1; + int searches_result_line_number[2] = { -1, -999 }; + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + searches_result_line_number[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + for (const ImWchar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; + if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_number[0] * g.FontSize; + if (searches_result_line_number[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_number[1] * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(size.x, line_count * g.FontSize); + } + + // Scroll + if (edit_state.CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = size.x * 0.25f; + if (cursor_offset.x < edit_state.ScrollX) + edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= edit_state.ScrollX) + edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); + } + else + { + edit_state.ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + float scroll_y = draw_window->Scroll.y; + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - size.y >= scroll_y) + scroll_y = cursor_offset.y - size.y; + draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag + draw_window->Scroll.y = scroll_y; + render_pos.y = draw_window->DC.CursorPos.y; + } + } + edit_state.CursorFollow = false; + const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); + + // Draw selection + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); + + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); + ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = render_pos.x - render_scroll.x; + rect_pos.y += g.FontSize; + } + } + + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect); + + // Draw blinking cursor + bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (is_editable) + g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + } + else + { + // Render text only + const char* buf_end = NULL; + if (is_multiline) + text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + if (is_multiline) + { + Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line + EndChildFrame(); + EndGroup(); + } + + if (is_password) + PopFont(); + + // Log as text + if (g.LogEnabled && !is_password) + LogRenderedText(&render_pos, buf_display, NULL); + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +// NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument) +bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + BeginGroup(); + PushID(label); + const ImVec2 button_sz = ImVec2(GetFrameHeight(), GetFrameHeight()); + if (step_ptr) + PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2)); + + char buf[64]; + DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf)); + + bool value_changed = false; + if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal)) + extra_flags |= ImGuiInputTextFlags_CharsDecimal; + extra_flags |= ImGuiInputTextFlags_AutoSelectAll; + if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format); + + // Step buttons + if (step_ptr) + { + PopItemWidth(); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + } + PopID(); + + if (label_size.x > 0) + { + SameLine(0, style.ItemInnerSpacing.x); + RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); + ItemSize(label_size, style.FramePadding.y); + } + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char display_format[16]; + if (decimal_precision < 0) + strcpy(display_format, "%f"); // Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1 + else + ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision); + return InputScalarEx(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), display_format, extra_flags); +} + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), scalar_format, extra_flags); +} + +bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 2, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 3, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 4, decimal_precision, extra_flags); +} + +bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 2, extra_flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 3, extra_flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 4, extra_flags); +} + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + // Always consume the SetNextWindowSizeConstraint() call in our early return paths + ImGuiContext& g = *GImGui; + ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond; + g.NextWindowData.SizeConstraintCond = 0; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + bool popup_open = IsPopupOpen(id); + + const float arrow_size = GetFrameHeight(); + const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING + RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); + if (preview_value != NULL) + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((pressed || g.NavActivateId == id) && !popup_open) + { + if (window->DC.NavLayerCurrent == 0) + window->NavLastIds[0] = id; + OpenPopupEx(id); + popup_open = true; + } + + if (!popup_open) + return false; + + if (backup_next_window_size_constraint) + { + g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint; + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + } + + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + + // Peak into expected window size so we can position it + if (ImGuiWindow* popup_window = FindWindowByName(name)) + if (popup_window->WasActive) + { + ImVec2 size_contents = CalcSizeContents(popup_window); + ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents)); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; + ImVec2 pos = FindBestWindowPosForPopup(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + if (!Begin(name, NULL, window_flags)) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + + // Horizontally align ourselves with the framed text + if (style.FramePadding.x != style.WindowPadding.x) + Indent(style.FramePadding.x - style.WindowPadding.x); + + return true; +} + +void ImGui::EndCombo() +{ + const ImGuiStyle& style = GImGui->Style; + if (style.FramePadding.x != style.WindowPadding.x) + Unindent(style.FramePadding.x - style.WindowPadding.x); + EndPopup(); +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + const char* preview_text = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_text); + + // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here. + if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond) + { + float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, popup_max_height)); + } + + if (!BeginCombo(label, preview_text, 0)) + return false; + + // Display items + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + return value_changed; +} + +static bool Items_ArrayGetter(void* data, int idx, const char** out_text) +{ + const char* const* items = (const char* const*)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string. +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped. + PopClipRect(); + + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrentLineTextBaseOffset; + ImRect bb(pos, pos + size); + ItemSize(bb); + + // Fill horizontal space. + ImVec2 window_padding = window->WindowPadding; + float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; + float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); + ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y); + ImRect bb_with_spacing(pos, pos + size_draw); + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) + bb_with_spacing.Max.x += window_padding.x; + + // Selectables are tightly packed together, we extend the box to cover spacing between selectable. + float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); + float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); + float spacing_R = style.ItemSpacing.x - spacing_L; + float spacing_D = style.ItemSpacing.y - spacing_U; + bb_with_spacing.Min.x -= spacing_L; + bb_with_spacing.Min.y -= spacing_U; + bb_with_spacing.Max.x += spacing_R; + bb_with_spacing.Max.y += spacing_D; + if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id)) + { + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + PushColumnClipRect(); + return false; + } + + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID; + if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease; + if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; + if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + bool hovered, held; + bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags); + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; + + // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets) + if (pressed || hovered)// && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)) + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerActiveMask) + { + g.NavDisableHighlight = true; + SetNavID(id, window->DC.NavLayerCurrent); + } + + // Render + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f); + RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + { + PushColumnClipRect(); + bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x); + } + + if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); + if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + return pressed; +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + +// Helper to calculate the size of a listbox and display a label on the right. +// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" +bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = GetStyle(); + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. + + BeginGroup(); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) +{ + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + // However we don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. + // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); + + // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; + return ListBoxHeader(label, size); +} + +void ImGui::ListBoxFooter() +{ + ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; + const ImRect bb = parent_window->DC.LastItemRect; + const ImGuiStyle& style = GetStyle(); + + EndChildFrame(); + + // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) + // We call SameLine() to restore DC.CurrentLine* data + SameLine(); + parent_window->DC.CursorPos = bb.Min; + ItemSize(bb, style.FramePadding.y); + EndGroup(); +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + if (!ListBoxHeader(label, items_count, height_in_items)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. + bool value_changed = false; + ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + ListBoxFooter(); + return value_changed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation we render neither the shortcut neither the selected tick mark + float w = label_size.x; + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); + if (shortcut_size.x > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + SetNextWindowPos(ImVec2(0.0f, 0.0f)); + SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f)); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); + if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar) + || !BeginMenuBar()) + { + End(); + PopStyleVar(2); + return false; + } + g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x; + return true; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + ImGuiContext& g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) + FocusFrontMostActiveWindow(g.NavWindow); + + End(); + PopStyleVar(2); +} + +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Save position + PushID("##menubar"); + + // We don't clip with regular window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); + clip_rect.ClipWith(window->WindowRectClipped); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y);// + g.Style.FramePadding.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. + if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + { + ImGuiWindow* nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) + { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) + IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check + FocusWindow(window); + SetNavIDAndMoveMouse(window->NavLastIds[1], 1, window->NavRectRel[1]); + g.NavLayer = 1; + g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + } + } + + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x; + window->DC.GroupStack.back().AdvanceCursor = false; + EndGroup(); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.MenuBarAppending = false; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + + bool pressed; + bool menu_is_open = IsPopupOpen(id); + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) + g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos). + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestPopupWindowPos() in Begin() + popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + float w = label_size.x; + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a menu + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); + if (!enabled) PopStyleColor(); + } + + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) + g.NavWindow = backed_nav_window; + + bool want_open = false, want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_within_opened_triangle = false; + if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar)) + { + if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) + { + ImRect next_window_rect = next_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug + } + } + + want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); + want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); + + if (g.NavActivateId == id) + { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id)) + ClosePopupToLevel(g.CurrentPopupStack.Size); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) + OpenPopup(label); + + if (menu_is_open) + { + SetNextWindowPos(popup_pos, ImGuiCond_Always); + ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + + return menu_is_open; +} + +void ImGui::EndMenu() +{ + // Nav: When a left move request _within our child menu_ failed, close the menu. + // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. + // However it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + NavMoveRequestCancel(); + } + + EndPopup(); +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + BeginTooltipEx(0, true); + + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextUnformatted(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + EndTooltip(); +} + +static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) +{ + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); + return IM_COL32(r, g, b, 0xFF); +} + +// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. +// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether. +void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) + { + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col)); + window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); + + int yi = 0; + for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) + { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + int rounding_corners_flags_cell = 0; + if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } + rounding_corners_flags_cell &= rounding_corners_flags; + window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); + } + } + } + else + { + window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); + } +} + +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags__InputsMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask; + if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; + if ((flags & ImGuiColorEditFlags__PickerMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// A little colored square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + float default_size = GetFrameHeight(); + if (size.x == 0.0f) + size.x = default_size; + if (size.y == 0.0f) + size.y = default_size; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) + { + float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); + RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); + } + RenderNavHighlight(bb, id); + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + + // Drag and Drop Source + if (g.ActiveId == id && BeginDragDropSource()) // NB: The ActiveId test is merely an optional micro-optimization + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextUnformatted("Color"); + EndDragDropSource(); + hovered = false; + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; + if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1,0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + if (flags & ImGuiColorEditFlags_NoAlpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); +} + +static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + ImGui::PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) ImGui::Separator(); + ImGui::PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); + if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + ImGui::SetCursorScreenPos(backup_pos); + ImVec4 dummy_ref_col; + memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4)); + ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) ImGui::Separator(); + ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + ImGui::EndPopup(); +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_items_all = CalcItemWidth() - w_extra; + const char* label_display_end = FindRenderedTextEnd(label); + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + const ImGuiColorEditFlags flags_untouched = flags; + + BeginGroup(); + PushID(label); + + // If we're not showing any slider there's no point in doing any HSV conversions + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__InputsMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask); + if (!(flags & ImGuiColorEditFlags__DataTypeMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask)); + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + + const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + const char* fmt_table_int[3][4] = + { + { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, // Short display + { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, // Long display for RGBA + { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } // Long display for HSVA + }; + const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1; + + PushItemWidth(w_item_one); + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + if (n + 1 == components) + PushItemWidth(w_item_last); + if (flags & ImGuiColorEditFlags_Float) + value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + else + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + PopItemWidth(); + PopItemWidth(); + } + else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255)); + PushItemWidth(w_items_all); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsSpace(*p)) + p++; + i[0] = i[1] = i[2] = i[3] = 0; + if (alpha) + sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + PopItemWidth(); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + if (!(flags & ImGuiColorEditFlags_NoInputs)) + SameLine(0, style.ItemInnerSpacing.x); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + if (BeginPopup("picker")) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextUnformatted(label, label_display_end); + Separator(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemWidth(); + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + + // Convert back + if (picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + if (value_changed) + { + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + } + + PopID(); + EndGroup(); + + // Drag and Drop Target + if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && BeginDragDropTarget()) // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); + value_changed = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = true; + } + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +// 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. +static void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) +{ + switch (direction) + { + case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_None: case ImGuiDir_Count_: break; // Fix warnings + } +} + +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) +{ + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); +} + +// ColorPicker +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImDrawList* draw_list = window->DrawList; + + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + PushID(label); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(flags, col); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H,S,V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V); + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + PushItemFlag(ImGuiItemFlags_NoNav, true); + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1)) + { + // Interactive with Hue wheel + H = atan2f(current_off.y, current_off.x) / IM_PI*0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = cosf(-H * 2.0f * IM_PI); + float sin_hue_angle = sinf(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); + + // R,G,B and H,S,V slider color editor + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB); + if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV); + if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit), if any + if (value_changed) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f)); + + const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) }; + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list->VtxBuffer.Data + vert_start_idx, draw_list->VtxBuffer.Data + vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = cosf(H * 2.0f * IM_PI); + float sin_hue_angle = sinf(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE); + draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS); + draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK); + draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS); + draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]); + float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + EndGroup(); + PopID(); + + return value_changed && memcmp(backup_initial_col, col, components * sizeof(float)); +} + +// Horizontal separating line. +void ImGui::Separator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + ImGuiWindowFlags flags = 0; + if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0) + flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected + if (flags & ImGuiSeparatorFlags_Vertical) + { + VerticalSeparator(); + return; + } + + // Horizontal Separator + if (window->DC.ColumnsSet) + PopClipRect(); + + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + if (!window->DC.GroupStack.empty()) + x1 += window->DC.IndentX; + + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); + ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. + if (!ItemAdd(bb, 0)) + { + if (window->DC.ColumnsSet) + PushColumnClipRect(); + return; + } + + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); + + if (g.LogEnabled) + LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); + + if (window->DC.ColumnsSet) + { + PushColumnClipRect(); + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; + } +} + +void ImGui::VerticalSeparator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); + ItemSize(ImVec2(bb.GetWidth(), 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); +} + +bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool item_add = ItemAdd(bb, id); + window->DC.ItemFlags = item_flags_backup; + if (!item_add) + return false; + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + + if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // Minimum pane size + if (mouse_delta < min_size1 - *size1) + mouse_delta = min_size1 - *size1; + if (mouse_delta > *size2 - min_size2) + mouse_delta = *size2 - min_size2; + + // Apply resize + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + } + + // Render + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding); + + return held; +} + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0,0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + ItemAdd(bb, 0); +} + +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) +void ImGui::BeginGroup() +{ + ImGuiWindow* window = GetCurrentWindow(); + + window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndentX = window->DC.IndentX; + group_data.BackupGroupOffsetX = window->DC.GroupOffsetX; + group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight; + group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; + group_data.BackupLogLinePosY = window->DC.LogLinePosY; + group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive; + group_data.AdvanceCursor = true; + + window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX; + window->DC.IndentX = window->DC.GroupOffsetX; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.CurrentLineHeight = 0.0f; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; +} + +void ImGui::EndGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls + + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + + ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); + group_bb.Max = ImMax(group_bb.Min, group_bb.Max); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight; + window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; + window->DC.IndentX = group_data.BackupIndentX; + window->DC.GroupOffsetX = group_data.BackupGroupOffsetX; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + + if (group_data.AdvanceCursor) + { + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); + ItemAdd(group_bb, 0); + } + + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context. + const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); + if (active_id_within_group) + window->DC.LastItemId = g.ActiveId; + window->DC.LastItemRect = group_bb; + + window->DC.GroupStack.pop_back(); + + //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] +} + +// Gets back to previous line and continue with horizontal layout +// pos_x == 0 : follow right after previous item +// pos_x != 0 : align to specified x position (relative to window/group left) +// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float pos_x, float spacing_w) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + if (pos_x != 0.0f) + { + if (spacing_w < 0.0f) spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + else + { + if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrentLineHeight = window->DC.PrevLineHeight; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + if (window->DC.CurrentLineHeight > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0,0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.ColumnsSet == NULL) + return; + + ImGuiContext& g = *GImGui; + PopItemWidth(); + PopClipRect(); + + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + if (++columns->Current < columns->Count) + { + // Columns 1+ cancel out IndentX + window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(columns->Current); + } + else + { + window->DC.ColumnsOffsetX = 0.0f; + window->DrawList->ChannelsSetCurrent(0); + columns->Current = 0; + columns->CellMinY = columns->CellMaxY; + } + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + window->DC.CursorPos.y = columns->CellMinY; + window->DC.CurrentLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = 0.0f; + + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; +} + +static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) +{ + return offset_norm * (columns->MaxX - columns->MinX); +} + +static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) +{ + return offset / (columns->MaxX - columns->MinX); +} + +static inline float GetColumnsRectHalfWidth() { return 4.0f; } + +static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + /* + if (g.ActiveId) + { + ImGuiContext& g = *GImGui; + const ImGuiID column_id = columns->ColumnsSetId + ImGuiID(column_index); + if (g.ActiveId == column_id) + return GetDraggedColumnOffset(columns, column_index); + } + */ + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return OffsetNormToPixels(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + if (column_index < 0) + column_index = columns->Current; + + PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); +} + +static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +{ + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiColumnsSet()); + ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count > 1); + IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + // Acquire storage for the columns set + ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.ColumnsSet = columns; + + // Set state for first column + const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->Size.x -window->ScrollbarSizes.x); + columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range + //column->MaxX = content_region_width - window->Scroll.x - ((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x; + columns->MaxX = content_region_width - window->Scroll.x; + columns->StartPosY = window->DC.CursorPos.y; + columns->StartMaxPosX = window->DC.CursorMaxPos.x; + columns->CellMinY = columns->CellMaxY = window->DC.CursorPos.y; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize defaults + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count + 1; n++) + { + // Clamp position + ImGuiColumnData* column = &columns->Columns[n]; + float t = column->OffsetNorm; + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + t = ImMin(t, PixelsToOffsetNorm(columns, (columns->MaxX - columns->MinX) - g.Style.ColumnsMinSpacing * (columns->Count - n))); + column->OffsetNorm = t; + + if (n == columns_count) + continue; + + // Compute clipping rectangle + float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); + float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWith(window->ClipRect); + } + + window->DrawList->ChannelsSplit(columns->Count); + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + PopClipRect(); + window->DrawList->ChannelsMerge(); + + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->CellMaxY; + if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = ImMax(columns->StartMaxPosX, columns->MaxX); // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + bool is_being_resized = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) + { + const float y1 = columns->StartPosY; + const float y2 = window->DC.CursorPos.y; + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction + const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) + { + ButtonBehavior(column_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) + dragging_column = n; + } + + // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->DC.ColumnsSet = NULL; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); +} + +// [2017/12: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count != columns_count) + EndColumns(); + + ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); + //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +void ImGui::Indent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::Unindent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id ? str_id : "#TreePush"); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); +} + +void ImGui::TreePushRawID(ImGuiID id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + window->IDStack.push_back(id); +} + +void ImGui::TreePop() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Unindent(); + + window->DC.TreeDepth--; + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + if (g.NavIdIsAlive && (window->DC.TreeDepthMayCloseOnPop & (1 << window->DC.TreeDepth))) + { + SetNavID(window->IDStack.back(), g.NavLayer); + NavMoveRequestCancel(); + } + window->DC.TreeDepthMayCloseOnPop &= (1 << window->DC.TreeDepth) - 1; + + PopID(); +} + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//----------------------------------------------------------------------------- +// DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; +} + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags, int mouse_button) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case + return false; + if (g.IO.MouseDown[mouse_button] == false) + return false; + + if (source_id == 0) + { + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; + if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + if (is_hovered) + SetHoveredID(source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + } + else + { + window = NULL; + source_id = ImHash("#SourceExtern", 0); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // FIXME-DRAG + //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :( + SetNextWindowPos(g.IO.MousePos); + PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); + BeginTooltip(); + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + EndTooltip(); + PopStyleColor(); + //PopStyleVar(); + } + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 12 characters long"); + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy((void*)payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy((void*)payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + + const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(display_rect); + if (g.DragDropPayload.SourceId == id) + return false; + + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface < g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAG: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.DragDropActive); +} + +//----------------------------------------------------------------------------- +// PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#ifndef __MINGW32__ +#include +#else +#include +#endif +#endif + +// Win32 API clipboard implementation +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#endif + +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + static ImVector buf_local; + buf_local.clear(); + if (!OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return NULL; + } + if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle)) + { + int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; + buf_local.resize(buf_len); + ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); + } + GlobalUnlock(wbuf_handle); + CloseClipboard(); + return buf_local.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!OpenClipboard(NULL)) + return; + const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; + HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return; + } + ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle); + ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); + GlobalUnlock(wbuf_handle); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, wbuf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin(); +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + ImGuiContext& g = *GImGui; + g.PrivateClipboard.clear(); + const char* text_end = text + strlen(text); + g.PrivateClipboard.resize((int)(text_end - text) + 1); + memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); + g.PrivateClipboard[(int)(text_end - text)] = 0; +} + +#endif + +// Win32 API IME support (for Asian languages, etc.) +#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + +#include +#ifdef _MSC_VER +#pragma comment(lib, "imm32") +#endif + +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +{ + // Notify OS Input Method Editor of text input position + if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) + if (HIMC himc = ImmGetContext(hwnd)) + { + COMPOSITIONFORM cf; + cf.ptCurrentPos.x = x; + cf.ptCurrentPos.y = y; + cf.dwStyle = CFS_FORCE_POSITION; + ImmSetCompositionWindow(himc, &cf); + } +} + +#else + +static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + +#endif + +//----------------------------------------------------------------------------- +// HELP +//----------------------------------------------------------------------------- + +void ImGui::ShowMetricsWindow(bool* p_open) +{ + if (ImGui::Begin("ImGui Metrics", p_open)) + { + ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3); + ImGui::Text("%d allocations", (int)GImAllocatorActiveAllocationsCount); + static bool show_clip_rects = true; + ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_clip_rects); + ImGui::Separator(); + + struct Funcs + { + static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label) + { + bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); + if (draw_list == ImGui::GetWindowDrawList()) + { + ImGui::SameLine(); + ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) ImGui::TreePop(); + return; + } + + ImDrawList* overlay_draw_list = ImGui::GetOverlayDrawList(); // Render additional visuals into the top-most draw list + if (window && ImGui::IsItemHovered()) + overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!node_open) + return; + + int elem_offset = 0; + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; + if (pcmd->UserCallback) + { + ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + if (show_clip_rects && ImGui::IsItemHovered()) + { + ImRect clip_rect = pcmd->ClipRect; + ImRect vtxs_rect; + for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) + vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); + clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); + vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); + } + if (!pcmd_node_open) + continue; + + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + { + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangles_pos[3]; + for (int n = 0; n < 3; n++, vtx_i++) + { + ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; + triangles_pos[n] = v.pos; + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + } + ImGui::Selectable(buf, false); + if (ImGui::IsItemHovered()) + { + ImDrawListFlags backup_flags = overlay_draw_list->Flags; + overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. + overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); + overlay_draw_list->Flags = backup_flags; + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + static void NodeWindows(ImVector& windows, const char* label) + { + if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) + return; + for (int i = 0; i < windows.Size; i++) + Funcs::NodeWindow(windows[i], "Window"); + ImGui::TreePop(); + } + + static void NodeWindow(ImGuiWindow* window, const char* label) + { + if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) + return; + ImGuiWindowFlags flags = window->Flags; + NodeDrawList(window, window->DrawList, "DrawList"); + ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s..)", flags, + (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", + (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : ""); + ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window)); + ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed); + ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask); + ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + if (window->NavRectRel[0].IsFinite()) + ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); + else + ImGui::BulletText("NavRectRel[0]: "); + if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); + if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); + ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); + ImGui::TreePop(); + } + }; + + // Access private state, we are going to display the draw lists from last frame + ImGuiContext& g = *GImGui; + Funcs::NodeWindows(g.Windows, "Windows"); + if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size)) + { + for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++) + Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) + { + for (int i = 0; i < g.OpenPopupStack.Size; i++) + { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Internal state")) + { + const char* input_source_names[] = { "None", "Mouse", "Nav", "NavGamepad", "NavKeyboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_Count_); + ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec)", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, input_source_names[g.ActiveIdSource]); + ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); + ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + ImGui::TreePop(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- + +// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. +// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. +#ifdef IMGUI_INCLUDE_IMGUI_USER_INL +#include "imgui_user.inl" +#endif + +//----------------------------------------------------------------------------- diff --git a/apps/MDL_sdf/imgui/imgui.h b/apps/MDL_sdf/imgui/imgui.h new file mode 100644 index 00000000..7629e06c --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui.h @@ -0,0 +1,1787 @@ +// dear imgui, v1.60 WIP +// (headers) + +// See imgui.cpp file for documentation. +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui + +#pragma once + +// User-editable configuration files (edit stock imconfig.h or define IMGUI_USER_CONFIG to your own filename) +#ifdef IMGUI_USER_CONFIG +#include IMGUI_USER_CONFIG +#endif +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" +#endif + +#include // FLT_MAX +#include // va_list +#include // ptrdiff_t, NULL +#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + +#define IMGUI_VERSION "1.60 WIP" + +// Define attributes of all API symbols declarations, e.g. for DLL under Windows. +#ifndef IMGUI_API +#define IMGUI_API +#endif + +// Define assertion handler. +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) +#endif + +// Helpers +// Some compilers support applying printf-style warnings to user functions. +#if defined(__clang__) || defined(__GNUC__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) +#define IM_OFFSETOF(_TYPE,_MEMBER) ((size_t)&(((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in modern C++. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +// Forward declarations +struct ImDrawChannel; // Temporary storage for outputting drawing commands out of order, used by ImDrawList::ChannelsSplit() +struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call) +struct ImDrawData; // All draw command lists required to render the frame +struct ImDrawList; // A single draw command list (generally one per window) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawVert; // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) +struct ImFont; // Runtime data for a single font within a parent ImFontAtlas +struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader +struct ImFontConfig; // Configuration data when adding a font or merging fonts +struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiOnceUponAFrame; // Simple helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro +struct ImGuiStorage; // Simple custom key value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTextFilter; // Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextBuffer; // Text buffer for logging/accumulating text +struct ImGuiTextEditCallbackData; // Shared state of ImGui::InputText() when using custom ImGuiTextEditCallback (rare/advanced use) +struct ImGuiSizeCallbackData; // Structure used to constraint window size in custom ways when using custom ImGuiSizeCallback (rare/advanced use) +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiContext; // ImGui context (opaque) + +// Typedefs and Enumerations (declared as int for compatibility and to not pollute the top of this file) +typedef unsigned int ImU32; // 32-bit unsigned integer (typically used to store packed colors) +typedef unsigned int ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) +typedef unsigned short ImWchar; // character for keyboard input/display +typedef void* ImTextureID; // user data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) +typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_ +typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ +typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_ +typedef int ImGuiNavInput; // enum: an input identifier for navigation // enum ImGuiNavInput_ +typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_ +typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImDrawCornerFlags; // flags: for ImDrawList::AddRect*() etc. // enum ImDrawCornerFlags_ +typedef int ImDrawListFlags; // flags: for ImDrawList // enum ImDrawListFlags_ +typedef int ImFontAtlasFlags; // flags: for ImFontAtlas // enum ImFontAtlasFlags_ +typedef int ImGuiColorEditFlags; // flags: for ColorEdit*(), ColorPicker*() // enum ImGuiColorEditFlags_ +typedef int ImGuiColumnsFlags; // flags: for *Columns*() // enum ImGuiColumnsFlags_ +typedef int ImGuiDragDropFlags; // flags: for *DragDrop*() // enum ImGuiDragDropFlags_ +typedef int ImGuiComboFlags; // flags: for BeginCombo() // enum ImGuiComboFlags_ +typedef int ImGuiFocusedFlags; // flags: for IsWindowFocused() // enum ImGuiFocusedFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() etc. // enum ImGuiHoveredFlags_ +typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ +typedef int ImGuiNavFlags; // flags: for io.NavFlags // enum ImGuiNavFlags_ +typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ +typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(),CollapsingHeader()// enum ImGuiTreeNodeFlags_ +typedef int ImGuiWindowFlags; // flags: for Begin*() // enum ImGuiWindowFlags_ +typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); +typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); +#if defined(_MSC_VER) && !defined(__clang__) +typedef unsigned __int64 ImU64; // 64-bit unsigned integer +#else +typedef unsigned long long ImU64; // 64-bit unsigned integer +#endif + +// Others helpers at bottom of the file: +// class ImVector<> // Lightweight std::vector like class. +// IMGUI_ONCE_UPON_A_FRAME // Execute a block of code once per frame only (convenient for creating UI within deep-nested code that runs multiple times) + +struct ImVec2 +{ + float x, y; + ImVec2() { x = y = 0.0f; } + ImVec2(float _x, float _y) { x = _x; y = _y; } + float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, thus an assert is fine. +#ifdef IM_VEC2_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec2. + IM_VEC2_CLASS_EXTRA +#endif +}; + +struct ImVec4 +{ + float x, y, z, w; + ImVec4() { x = y = z = w = 0.0f; } + ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } +#ifdef IM_VEC4_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec4. + IM_VEC4_CLASS_EXTRA +#endif +}; + +// ImGui end-user API +// In a namespace so that user can add extra functions in a separate file (e.g. Value() helpers for your vector or common types) +namespace ImGui +{ + // Context creation and access, if you want to use multiple context, share context between modules (e.g. DLL). + // All contexts share a same ImFontAtlas by default. If you want different font atlas, you can new() them and overwrite the GetIO().Fonts variable of an ImGui context. + // All those functions are not reliant on the current context. + IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); + IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = Destroy current context + IMGUI_API ImGuiContext* GetCurrentContext(); + IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + + // Main + IMGUI_API ImGuiIO& GetIO(); + IMGUI_API ImGuiStyle& GetStyle(); + IMGUI_API void NewFrame(); // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame(). + IMGUI_API void Render(); // ends the ImGui frame, finalize the draw data. (Obsolete: optionally call io.RenderDrawListsFn if set. Nowadays, prefer calling your render function yourself.) + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. (Obsolete: this used to be passed to your io.RenderDrawListsFn() function.) + IMGUI_API void EndFrame(); // ends the ImGui frame. automatically called by Render(), so most likely don't need to ever call that yourself directly. If you don't need to render you may call EndFrame() but you'll have wasted CPU already. If you don't need to render, better to not create any imgui windows instead! + + // Demo, Debug, Informations + IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create metrics window. display ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc. + IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) + IMGUI_API bool ShowStyleSelector(const char* label); + IMGUI_API void ShowFontSelector(const char* label); + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + IMGUI_API const char* GetVersion(); + + // Styles + IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // New, recommended style + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // Classic imgui style (default) + IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // Best used with borders and a custom, thicker font + + // Window + IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // push window to the stack and start appending to it. see .cpp for details. return false when window is collapsed (so you can early out in your code) but you always need to call End() regardless. 'bool* p_open' creates a widget on the upper-right to close the window (which sets your bool to false). + IMGUI_API void End(); // always call even if Begin() return false (which indicates a collapsed window)! finish appending to current window, pop it off the window stack. + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). + IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // " + IMGUI_API void EndChild(); // always call even if BeginChild() return false (which indicates a collapsed or clipping child window) + IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates + IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() + IMGUI_API float GetContentRegionAvailWidth(); // + IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API float GetWindowContentRegionWidth(); // + IMGUI_API ImDrawList* GetWindowDrawList(); // get rendering command-list if you want to append your own draw primitives + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList api) + IMGUI_API ImVec2 GetWindowSize(); // get current window size + IMGUI_API float GetWindowWidth(); + IMGUI_API float GetWindowHeight(); + IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowAppearing(); + IMGUI_API void SetWindowFontScale(float scale); // per-window font scale. Adjust IO.FontGlobalScale if you want to scale all windows + + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. + IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() + IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() + IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() + IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg. + IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. + IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. + IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). + IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / front-most. prefer using SetNextWindowFocus(). + IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. + IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. + IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state + IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / front-most. use NULL to remove focus. + + IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()] + IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()] + IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X + IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y + IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] + IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] + IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. + IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it) + IMGUI_API ImGuiStorage* GetStateStorage(); + + // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); + IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); + IMGUI_API void PopStyleColor(int count = 1); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); + IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwhise use GetColorU32() to get style color + style alpha. + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier + IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied + IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align width to the right side) + IMGUI_API void PopItemWidth(); + IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position + IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space + IMGUI_API void PopTextWrapPos(); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); + + // Cursor / Layout + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. + IMGUI_API void SameLine(float pos_x = 0.0f, float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally + IMGUI_API void NewLine(); // undo a SameLine() + IMGUI_API void Spacing(); // add vertical spacing + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) + IMGUI_API void EndGroup(); + IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position + IMGUI_API float GetCursorPosX(); // " + IMGUI_API float GetCursorPosY(); // " + IMGUI_API void SetCursorPos(const ImVec2& local_pos); // " + IMGUI_API void SetCursorPosX(float x); // " + IMGUI_API void SetCursorPosY(float y); // " + IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] + IMGUI_API void AlignTextToFramePadding(); // vertically align/lower upcoming text to FramePadding.y so that it will aligns to upcoming widgets (call if you have text on a line before regular widgets) + IMGUI_API float GetTextLineHeight(); // ~ FontSize + IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) + IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 + IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + + // Columns + // You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather lacking. + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API int GetColumnsCount(); + + // ID scopes + // If you are creating widgets in a loop you most likely want to push a unique identifier (e.g. object pointer, loop index) so ImGui can differentiate them. + // You can also use the "##foobar" syntax within widget label to distinguish them from each others. Read "A primer on the use of labels/IDs" in the FAQ for more details. + IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); + IMGUI_API void PushID(const void* ptr_id); + IMGUI_API void PushID(int int_id); + IMGUI_API void PopID(); + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Main + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); + IMGUI_API bool RadioButton(const char* label, bool active); + IMGUI_API bool RadioButton(const char* label, int* v, int v_button); + IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL); + + // Widgets: Combo Box + // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it. + // The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + + // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds) + // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x + // Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound + IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", const char* display_format_max = NULL, float power = 1.0f); + IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); // If v_min >= v_max we have no bound + IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f", const char* display_format_max = NULL); + + // Widgets: Input with Keyboard + IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0); + + // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds) + IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); // adjust display_format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for logarithmic sliders + IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f); + IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be left-clicked to open a picker, and right-clicked to open an option menu.) + // Note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x + IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); + IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0)); // display a colored square/button, hover for details, return true when pressed. + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + + // Widgets: Trees + IMGUI_API bool TreeNode(const char* label); // if returning 'true' the node is open and the tree id is pushed into the id stack. user is responsible for calling TreePop(). + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call Push/Pop yourself for layout purpose + IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePop(); // ~ Unindent()+PopId() + IMGUI_API void TreeAdvanceToLabelPos(); // advance cursor x position by GetTreeNodeToLabelSpacing() + IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode + IMGUI_API void SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). + IMGUI_API bool CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small close button on upper right of the header + + // Widgets: Selectable / Lists + IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height + IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + IMGUI_API bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. make sure to call ListBoxFooter() afterwards. + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " + IMGUI_API void ListBoxFooter(); // terminate the scrolling region + + // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) + IMGUI_API void Value(const char* prefix, bool b); + IMGUI_API void Value(const char* prefix, int v); + IMGUI_API void Value(const char* prefix, unsigned int v); + IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + + // Tooltips + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set text tooltip under mouse-cursor, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of contents). + IMGUI_API void EndTooltip(); + + // Menus + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. + IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). + IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); // only call EndBegin() if BeginMenu() returns true! + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + + // Popups + IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returns true! + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). + IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // modal dialog (regular window with title bar, block interactions behind the modal window, can't close the modal window by clicking outside) + IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. + IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open + IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. + + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. + IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty + IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file + IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard + IMGUI_API void LogFinish(); // stop logging (close file, etc.) + IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + + // Drag and Drop + // [BETA API] Missing Demo code. API may evolve. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0, int mouse_button = 0); // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond = 0);// type is a user defined string of maximum 12 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! + IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! + + // Clipping + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); + IMGUI_API void PopClipRect(); + + // Focus, Activation + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged) + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. Please use instead of "if (IsWindowAppearing()) SetScrollHere()" to signify "default item". + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + + // Utilities + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) + IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? + IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) + IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) + IMGUI_API bool IsAnyItemHovered(); + IMGUI_API bool IsAnyItemActive(); + IMGUI_API bool IsAnyItemFocused(); + IMGUI_API ImVec2 GetItemRectMin(); // get bounding rectangle of last item, in screen space + IMGUI_API ImVec2 GetItemRectMax(); // " + IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space + IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. + IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags = 0); // is current window focused? or its root/child, depending on flags. see flags for options. + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. + IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. + IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. + IMGUI_API float GetTime(); + IMGUI_API int GetFrameCount(); + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text + IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); + IMGUI_API const char* GetStyleColorName(ImGuiCol idx); + IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. + + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) + + IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); + IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); + IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); + IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + + // Inputs + IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] + IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]! + IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate + IMGUI_API bool IsMouseDown(int button); // is mouse button held + IMGUI_API bool IsAnyMouseDown(); // is any mouse button held + IMGUI_API bool IsMouseClicked(int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) + IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime. + IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) + IMGUI_API bool IsMouseDragging(int button = 0, float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings. disregarding of consideration of focus/window ordering/blocked by a popup. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // + IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls + IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse positioning at the time of opening popup we have BeginPopup() into + IMGUI_API ImVec2 GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API void ResetMouseDragDelta(int button = 0); // + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor type); // set desired cursor type + IMGUI_API void CaptureKeyboardFromApp(bool capture = true); // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application handle). e.g. force capture keyboard when your widget is being hovered. + IMGUI_API void CaptureMouseFromApp(bool capture = true); // manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application handle). + + // Clipboard Utilities (also see the LogToClipboard() function to capture or output text data to the clipboard) + IMGUI_API const char* GetClipboardText(); + IMGUI_API void SetClipboardText(const char* text); + + // Memory Utilities + // All those functions are not reliant on the current context. + // If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again. + IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data = NULL); + IMGUI_API void* MemAlloc(size_t size); + IMGUI_API void MemFree(void* ptr); + +} // namespace ImGui + +// Flags for ImGui::Begin() +enum ImGuiWindowFlags_ +{ + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + //ImGuiWindowFlags_ShowBorders = 1 << 7, // Show borders around windows and items (OBSOLETE! Use e.g. style.FrameBorderSize=1.0f to enable borders). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // (WIP) Enable resize from any corners and borders. Your back-end needs to honor the different values of io.MouseCursor set by imgui. + ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + + // [Internal] + ImGuiWindowFlags_NavFlattened = 1 << 23, // (WIP) Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!) + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() +}; + +// Flags for ImGui::InputText() +enum ImGuiInputTextFlags_ +{ + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to when the value was modified) + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ +{ + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Full colored frame (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_NavCloseFromChild = 1 << 13, // (WIP) Nav: left direction may close this TreeNode() when focusing on any child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoAutoOpenOnLog + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap +#endif +}; + +// Flags for ImGui::Selectable() +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ +{ + ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_AnyWindow = 1 << 2, // IsWindowFocused(): Return true if any window is focused + ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows +}; + +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_Default = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 3, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 4, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 5, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 6, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return true, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + +// Standard Drag and Drop payload types. You can define you own payload types using 12-characters long strings. Types starting with '_' are defined by Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3] // Standard type for colors, without alpha. User code may use this type. +#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4] // Standard type for colors. User code may use this type. + +// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +enum ImGuiKey_ +{ + ImGuiKey_Tab, + ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, + ImGuiKey_UpArrow, + ImGuiKey_DownArrow, + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, + ImGuiKey_End, + ImGuiKey_Insert, + ImGuiKey_Delete, + ImGuiKey_Backspace, + ImGuiKey_Space, + ImGuiKey_Enter, + ImGuiKey_Escape, + ImGuiKey_A, // for text edit CTRL+A: select all + ImGuiKey_C, // for text edit CTRL+C: copy + ImGuiKey_V, // for text edit CTRL+V: paste + ImGuiKey_X, // for text edit CTRL+X: cut + ImGuiKey_Y, // for text edit CTRL+Y: redo + ImGuiKey_Z, // for text edit CTRL+Z: undo + ImGuiKey_COUNT +}; + +// [BETA] Gamepad/Keyboard directional navigation +// Keyboard: Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays. +// Gamepad: Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). +// Read instructions in imgui.cpp for more details. +enum ImGuiNavInput_ +{ + // Gamepad Mapping + ImGuiNavInput_Activate, // activate / open / toggle / tweak value // e.g. Circle (PS4), A (Xbox), B (Switch), Space (Keyboard) + ImGuiNavInput_Cancel, // cancel / close / exit // e.g. Cross (PS4), B (Xbox), A (Switch), Escape (Keyboard) + ImGuiNavInput_Input, // text input / on-screen keyboard // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard) + ImGuiNavInput_Menu, // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard) + ImGuiNavInput_DpadLeft, // move / tweak / resize window (w/ PadMenu) // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard) + ImGuiNavInput_DpadRight, // + ImGuiNavInput_DpadUp, // + ImGuiNavInput_DpadDown, // + ImGuiNavInput_LStickLeft, // scroll / move window (w/ PadMenu) // e.g. Left Analog Stick Left/Right/Up/Down + ImGuiNavInput_LStickRight, // + ImGuiNavInput_LStickUp, // + ImGuiNavInput_LStickDown, // + ImGuiNavInput_FocusPrev, // next window (w/ PadMenu) // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_FocusNext, // prev window (w/ PadMenu) // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + ImGuiNavInput_TweakSlow, // slower tweaks // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_TweakFast, // faster tweaks // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + + // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them. + // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) may be directly reading from io.KeyDown[] instead of io.NavInputs[]. + ImGuiNavInput_KeyMenu_, // toggle menu // = io.KeyAlt + ImGuiNavInput_KeyLeft_, // move left // = Arrow keys + ImGuiNavInput_KeyRight_, // move right + ImGuiNavInput_KeyUp_, // move up + ImGuiNavInput_KeyDown_, // move down + ImGuiNavInput_COUNT, + ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_ +}; + +// [BETA] Gamepad/Keyboard directional navigation flags, stored in io.NavFlags +enum ImGuiNavFlags_ +{ + ImGuiNavFlags_EnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. NewFrame() will automatically fill io.NavInputs[] based on io.KeyDown[]. + ImGuiNavFlags_EnableGamepad = 1 << 1, // Master gamepad navigation enable flag. This is mostly to instruct your imgui back-end to fill io.NavInputs[]. + ImGuiNavFlags_MoveMouse = 1 << 2, // Request navigation to allow moving the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantMoveMouse=true. If enabled you MUST honor io.WantMoveMouse requests in your binding, otherwise ImGui will react as if the mouse is jumping around back and forth. + ImGuiNavFlags_NoCaptureKeyboard = 1 << 3 // Do not set the io.WantCaptureKeyboard flag with io.NavActive is set. +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ +{ + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_CloseButton, + ImGuiCol_CloseButtonHovered, + ImGuiCol_CloseButtonActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TextSelectedBg, + ImGuiCol_ModalWindowDarkening, // darken entire screen when a modal window is active + ImGuiCol_DragDropTarget, + ImGuiCol_NavHighlight, // gamepad/keyboard: current highlighted item + ImGuiCol_NavWindowingHighlight, // gamepad/keyboard: when holding NavMenu to focus/move/resize windows + ImGuiCol_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //, ImGuiCol_ComboBg = ImGuiCol_PopupBg // ComboBg has been merged with PopupBg, so a redirect isn't accurate. + , ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, ImGuiCol_Column = ImGuiCol_Separator, ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive +#endif +}; + +// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. +// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During initialization, feel free to just poke into ImGuiStyle directly. +// NB: if changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. +enum ImGuiStyleVar_ +{ + // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_Count_ + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding +#endif +}; + +// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ +{ + ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on colored square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable colored square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview colored square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small colored square preview instead. + // User Options (right-click on widget to change some of them). You can set application defaults using SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the user choose and/or call SetColorEditOptions() during startup. + ImGuiColorEditFlags_AlphaBar = 1 << 9, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaPreview = 1 << 10, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 11, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_HDR = 1 << 12, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). + ImGuiColorEditFlags_RGB = 1 << 13, // [Inputs] // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any combination using RGB/HSV/HEX. + ImGuiColorEditFlags_HSV = 1 << 14, // [Inputs] // " + ImGuiColorEditFlags_HEX = 1 << 15, // [Inputs] // " + ImGuiColorEditFlags_Uint8 = 1 << 16, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 17, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 18, // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value. + ImGuiColorEditFlags_PickerHueWheel = 1 << 19, // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value. + // Internals/Masks + ImGuiColorEditFlags__InputsMask = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX, + ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_Float, + ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel|ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_PickerHueBar // Change application default using SetColorEditOptions() +}; + +// Enumeration for GetMouseCursor() +enum ImGuiMouseCursor_ +{ + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_ResizeAll, // Unused + ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Count_ +}; + +// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +// All those functions treat 0 as a shortcut to ImGuiCond_Always. From the point of view of the user use this as an enum (don't combine multiple values into flags). +enum ImGuiCond_ +{ + ImGuiCond_Always = 1 << 0, // Set the variable + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the window has no saved data (if doesn't exist in the .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the window is appearing after being hidden/inactive (or the first time) + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiSetCond_Always = ImGuiCond_Always, ImGuiSetCond_Once = ImGuiCond_Once, ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing = ImGuiCond_Appearing +#endif +}; + +// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). +// During the frame, prefer using ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. +struct ImGuiStyle +{ + float Alpha; // Global alpha applies to everything in ImGui. + ImVec2 WindowPadding; // Padding within a window. + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float PopupRounding; // Radius of popup window corners rounding. + float PopupBorderSize; // Thickness of border around popup windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. + float ScrollbarRounding; // Radius of grab corners for scrollbar. + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. + ImVec2 DisplayWindowPadding; // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. + bool AntiAliasedFill; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + ImVec4 Colors[ImGuiCol_COUNT]; + + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); +}; + +// This is where your app communicate with ImGui. Access via ImGui::GetIO(). +// Read 'Programmer guide' section in .cpp file for general usage. +struct ImGuiIO +{ + //------------------------------------------------------------------ + // Settings (fill once) // Default value: + //------------------------------------------------------------------ + + ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + ImGuiNavFlags NavFlags; // = 0x00 // See ImGuiNavFlags_. Gamepad/keyboard navigation options. + float IniSavingRate; // = 5.0f // Maximum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. + const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + void* UserData; // = NULL // Store your own data for retrieval by callbacks. + + ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui. + ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area. + ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize + + // Advanced/subtle behaviors + bool OptMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl + bool OptCursorBlink; // = true // Enable blinking cursor, for users who consider it annoying. + + //------------------------------------------------------------------ + // Settings (User Functions) + //------------------------------------------------------------------ + + // Optional: access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; + + // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) + // (default to use native imm32 api on Windows) + void (*ImeSetInputScreenPosFn)(int x, int y); + void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // [OBSOLETE] Rendering function, will be automatically called in Render(). Please call your rendering function yourself now! You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). + // See example applications if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawData* data); +#endif + + //------------------------------------------------------------------ + // Input - Fill before calling NewFrame() + //------------------------------------------------------------------ + + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: left, right, middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel: 1 unit scrolls about 5 lines text. + float MouseWheelH; // Mouse wheel (Horizontal). Most users don't have a mouse with an horizontal wheel, may not be filled by all back-ends. + bool MouseDrawCursor; // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). + bool KeyCtrl; // Keyboard modifier pressed: Control + bool KeyShift; // Keyboard modifier pressed: Shift + bool KeyAlt; // Keyboard modifier pressed: Alt + bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows + bool KeysDown[512]; // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). + ImWchar InputCharacters[16+1]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. + float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs (keyboard keys will be auto-mapped and be written here by ImGui::NewFrame) + + // Functions + IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[] + IMGUI_API void AddInputCharactersUTF8(const char* utf8_chars); // Add new characters into InputCharacters[] from an UTF-8 string + inline void ClearInputCharacters() { InputCharacters[0] = 0; } // Clear the text input buffer manually + + //------------------------------------------------------------------ + // Output - Retrieve after calling NewFrame() + //------------------------------------------------------------------ + + bool WantCaptureMouse; // When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. This is set by ImGui when it wants to use your mouse (e.g. unclicked mouse is hovering a window, or a widget is active). + bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. This is set by ImGui when it wants to use your keyboard inputs. + bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). + bool WantMoveMouse; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when ImGuiNavFlags_MoveMouse flag is enabled in io.NavFlags. + bool NavActive; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. + bool NavVisible; // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsActiveWindows; // Number of visible root windows (exclude child windows) + ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + + //------------------------------------------------------------------ + // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! + //------------------------------------------------------------------ + + ImVec2 MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) + ImVec2 MouseClickedPos[5]; // Position at time of clicking + float MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down + float NavInputsDownDuration[ImGuiNavInput_COUNT]; + float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + // OBSOLETED in 1.60 (from Dec 2017) + static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } + static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = 0.f) { (void)on_edge; (void)outward; IM_ASSERT(0); return pos; } + // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + static inline void ShowTestWindow() { return ShowDemoWindow(); } + static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } + static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } + static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } + static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } + // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead. + static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } + static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } + static inline void SetNextWindowPosCenter(ImGuiCond c=0) { ImGuiIO& io = GetIO(); SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); } + // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } + static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead. + static inline bool IsMouseHoveringAnyWindow() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } + // OBSOLETED IN 1.49 (between Apr 2016 and May 2016) + static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } +} +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +// Lightweight std::vector<> like class to avoid dragging dependencies (also: windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). +// Our implementation does NOT call C++ constructors/destructors. This is intentional and we do not require it. Do not use this class as a straight std::vector replacement in your code! +template +class ImVector +{ +public: + int Size; + int Capacity; + T* Data; + + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + inline ImVector() { Size = Capacity = 0; Data = NULL; } + inline ~ImVector() { if (Data) ImGui::MemFree(Data); } + + inline bool empty() const { return Size == 0; } + inline int size() const { return Size; } + inline int capacity() const { return Capacity; } + + inline value_type& operator[](int i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& operator[](int i) const { IM_ASSERT(i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } + inline iterator begin() { return Data; } + inline const_iterator begin() const { return Data; } + inline iterator end() { return Data + Size; } + inline const_iterator end() const { return Data + Size; } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > sz ? new_capacity : sz; } + + inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size, const T& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) Data[n] = v; Size = new_size; } + inline void reserve(int new_capacity) + { + if (new_capacity <= Capacity) + return; + T* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(T)); + if (Data) + memcpy(new_data, Data, (size_t)Size * sizeof(T)); + ImGui::MemFree(Data); + Data = new_data; + Capacity = new_capacity; + } + + // NB: &v cannot be pointing inside the ImVector Data itself! e.g. v.push_back(v[10]) is forbidden. + inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); Data[Size++] = v; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const value_type& v) { if (Size == 0) push_back(v); else insert(Data, v); } + + inline iterator erase(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } + inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } + inline bool contains(const value_type& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } +}; + +// Helper: execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Usage: +// static ImGuiOnceUponAFrame oaf; +// if (oaf) +// ImGui::Text("This will be called only once per frame"); +struct ImGuiOnceUponAFrame +{ + ImGuiOnceUponAFrame() { RefFrame = -1; } + mutable int RefFrame; + operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } +}; + +// Helper macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it within e.g. an if() statement without curly braces. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Will obsolete +#define IMGUI_ONCE_UPON_A_FRAME static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf) +#endif + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextFilter +{ + struct TextRange + { + const char* b; + const char* e; + + TextRange() { b = e = NULL; } + TextRange(const char* _b, const char* _e) { b = _b; e = _e; } + const char* begin() const { return b; } + const char* end() const { return e; } + bool empty() const { return b == e; } + char front() const { return *b; } + static bool is_blank(char c) { return c == ' ' || c == '\t'; } + void trim_blanks() { while (b < e && is_blank(*b)) b++; while (e > b && is_blank(*(e-1))) e--; } + IMGUI_API void split(char separator, ImVector& out); + }; + + char InputBuf[256]; + ImVector Filters; + int CountGrep; + + IMGUI_API ImGuiTextFilter(const char* default_filter = ""); + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; + IMGUI_API void Build(); + void Clear() { InputBuf[0] = 0; Build(); } + bool IsActive() const { return !Filters.empty(); } +}; + +// Helper: Text buffer for logging/accumulating text +struct ImGuiTextBuffer +{ + ImVector Buf; + + ImGuiTextBuffer() { Buf.push_back(0); } + inline char operator[](int i) { return Buf.Data[i]; } + const char* begin() const { return &Buf.front(); } + const char* end() const { return &Buf.back(); } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const { return Buf.Size - 1; } + bool empty() { return Buf.Size <= 1; } + void clear() { Buf.clear(); Buf.push_back(0); } + void reserve(int capacity) { Buf.reserve(capacity); } + const char* c_str() const { return Buf.Data; } + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); +}; + +// Helper: Simple Key->value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1), store color edit options. +// This is optimized for efficient reading (dichotomy into a contiguous buffer), rare writing (typically tied to user interactions) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage +{ + struct Pair + { + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + Pair(ImGuiID _key, int _val_i) { key = _key; val_i = _val_i; } + Pair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; } + Pair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; } + }; + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ImGuiID key, float val); + IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +// Shared state of InputText(), passed to callback when a ImGuiInputTextFlags_Callback* flag is used and the corresponding callback is triggered. +struct ImGuiTextEditCallbackData +{ + ImGuiInputTextFlags EventFlag; // One of ImGuiInputTextFlags_Callback* // Read-only + ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only + void* UserData; // What user passed to InputText() // Read-only + bool ReadOnly; // Read-only mode // Read-only + + // CharFilter event: + ImWchar EventChar; // Character input // Read-write (replace character or set to zero) + + // Completion,History,Always events: + // If you modify the buffer contents make sure you update 'BufTextLen' and set 'BufDirty' to true. + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only + char* Buf; // Current text buffer // Read-write (pointed data only, can't replace the actual pointer) + int BufTextLen; // Current text length in bytes // Read-write + int BufSize; // Maximum text length in bytes // Read-only + bool BufDirty; // Set if you modify Buf/BufTextLen!! // Write + int CursorPos; // // Read-write + int SelectionStart; // // Read-write (== to SelectionEnd when no selection) + int SelectionEnd; // // Read-write + + // NB: Helper functions for text manipulation. Calling those function loses selection. + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); + bool HasSelection() const { return SelectionStart != SelectionEnd; } +}; + +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. +struct ImGuiSizeCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + +// Data payload for Drag and Drop operations +struct ImGuiPayload +{ + // Members + const void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + int DataFrameCount; // Data timestamp + char DataType[12 + 1]; // Data type tag (short user-supplied string, 12 characters max) + bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) + bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsPreview() const { return Preview; } + bool IsDelivery() const { return Delivery; } +}; + +// Helpers macros to generate 32-bits encoded colors +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#define IM_COL32(R,G,B,A) (((ImU32)(A)<>IM_COL32_R_SHIFT)&0xFF) * sc; Value.y = (float)((rgba>>IM_COL32_G_SHIFT)&0xFF) * sc; Value.z = (float)((rgba>>IM_COL32_B_SHIFT)&0xFF) * sc; Value.w = (float)((rgba>>IM_COL32_A_SHIFT)&0xFF) * sc; } + ImColor(float r, float g, float b, float a = 1.0f) { Value.x = r; Value.y = g; Value.z = b; Value.w = a; } + ImColor(const ImVec4& col) { Value = col; } + inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } + inline operator ImVec4() const { return Value; } + + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. + inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } + static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r,g,b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r,g,b,a); } +}; + +// Helper: Manually clip large list of items. +// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse clipping based on visibility to save yourself from processing those items at all. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// ImGui already clip items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this cost and your own data fetching/submission cost null. +// Usage: +// ImGuiListClipper clipper(1000); // we have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height (step skipped if we passed a known height as second arg to constructor). +// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. +// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call Step(). Does nothing and switch to Step 3.) +// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. +struct ImGuiListClipper +{ + float StartPosY; + float ItemsHeight; + int ItemsCount, StepNo, DisplayStart, DisplayEnd; + + // items_count: Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step). + // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). + // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step(). + ImGuiListClipper(int items_count = -1, float items_height = -1.0f) { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want). + ~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. + + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. +}; + +//----------------------------------------------------------------------------- +// Draw List +// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. +//----------------------------------------------------------------------------- + +// Draw callbacks for advanced uses. +// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that) +// Draw callback may be useful for example, A) Change your GPU render state, B) render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else RenderTriangles()' +typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); + +// Typically, 1 command = 1 GPU draw call (unless command is a callback) +struct ImDrawCmd +{ + unsigned int ElemCount; // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. + ImVec4 ClipRect; // Clipping rectangle (x1, y1, x2, y2) + ImTextureID TextureId; // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImDrawCallback UserCallback; // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void* UserCallbackData; // The draw callback code can access this. + + ImDrawCmd() { ElemCount = 0; ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; TextureId = NULL; UserCallback = NULL; UserCallbackData = NULL; } +}; + +// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h) +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; +#endif + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert +{ + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. +// The type has to be described within the macro (you can either declare the struct or use a typedef) +// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. +IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; +#endif + +// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together. +// You can also use them to simulate drawing layers and submit primitives in a different order than how they will be rendered. +struct ImDrawChannel +{ + ImVector CmdBuffer; + ImVector IdxBuffer; +}; + +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 + ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 + ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 + ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA + ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience +}; + +enum ImDrawListFlags_ +{ + ImDrawListFlags_AntiAliasedLines = 1 << 0, + ImDrawListFlags_AntiAliasedFill = 1 << 1 +}; + +// Draw command list +// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. +// Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), however you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) +// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. +struct ImDrawList +{ + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those + ImVector VtxBuffer; // Vertex buffer. + + // [Internal, used while building lists] + ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + const char* _OwnerName; // Pointer to owner window's name for debugging + unsigned int _VtxCurrentIdx; // [Internal] == VtxBuffer.Size + ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + int _ChannelsCurrent; // [Internal] current channel number (0) + int _ChannelsCount; // [Internal] number of active channels (1+) + ImVector _Channels; // [Internal] draw channels for columns API (not resized down so _ChannelsCount may be smaller than _Channels.Size) + + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) + ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; _OwnerName = NULL; Clear(); } + ~ImDrawList() { ClearFreeMemory(); } + IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) + IMGUI_API void PushClipRectFullScreen(); + IMGUI_API void PopClipRect(); + IMGUI_API void PushTextureID(const ImTextureID& texture_id); + IMGUI_API void PopTextureID(); + inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } + inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + + // Primitives + IMGUI_API void AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f); // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round + IMGUI_API void AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); // a: upper-left, b: lower-right + IMGUI_API void AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); + IMGUI_API void AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col); + IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); + IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,0), const ImVec2& uv_c = ImVec2(1,1), const ImVec2& uv_d = ImVec2(0,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, const int num_points, ImU32 col); + IMGUI_API void AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 0); + + // Stateful path API, add points then finish with PathFill() or PathStroke() + inline void PathClear() { _Path.resize(0); } + inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } + inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path[_Path.Size-1], &pos, 8) != 0) _Path.push_back(pos); } + inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); PathClear(); } + inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); PathClear(); } + IMGUI_API void PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10); + IMGUI_API void PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + IMGUI_API void PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0); + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); + + // Channels + // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives before background primitives) + // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, prefer to append into separate channels then merge at the end) + IMGUI_API void ChannelsSplit(int channels_count); + IMGUI_API void ChannelsMerge(); + IMGUI_API void ChannelsSetCurrent(int channel_index); + + // Advanced + IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible + + // Internal helpers + // NB: all primitives needs to be reserved via PrimReserve() beforehand! + IMGUI_API void Clear(); + IMGUI_API void ClearFreeMemory(); + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); + IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); + inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col){ _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } + inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } + inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } + IMGUI_API void UpdateClipRect(); + IMGUI_API void UpdateTextureID(); +}; + +// All draw data to render an ImGui frame +struct ImDrawData +{ + bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + ImDrawList** CmdLists; + int CmdListsCount; + int TotalVtxCount; // For convenience, sum of all cmd_lists vtx_buffer.Size + int TotalIdxCount; // For convenience, sum of all cmd_lists idx_buffer.Size + + // Functions + ImDrawData() { Clear(); } + void Clear() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; } // Draw lists are owned by the ImGuiContext and only pointed to here. + IMGUI_API void DeIndexAllBuffers(); // For backward compatibility or convenience: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! + IMGUI_API void ScaleClipRects(const ImVec2& sc); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +}; + +struct ImFontConfig +{ + void* FontData; // // TTF/OTF data + int FontDataSize; // // TTF/OTF data size + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer. + int OversampleH, OversampleV; // 3, 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. + + // [Internal] + char Name[32]; // Name (strictly to ease debugging) + ImFont* DstFont; + + IMGUI_API ImFontConfig(); +}; + +struct ImFontGlyph +{ + ImWchar Codepoint; // 0x0000..0xFFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +enum ImFontAtlasFlags_ +{ + ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two + ImFontAtlasFlags_NoMouseCursors = 1 << 1 // Don't build software mouse cursors into the atlas +}; + +// Load and rasterize multiple TTF/OTF fonts into a same texture. +// Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. +// We also add custom graphic data into the texture that serves for ImGui. +// 1. (Optional) Call AddFont*** functions. If you don't call any, the default font will be loaded for you. +// 2. Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// 3. Upload the pixels data into a texture within your graphics system. +// 4. Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture. This value will be passed back to you during rendering to identify the texture. +// IMPORTANT: If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the ImFont is build (when calling GetTextData*** or Build()). We only copy the pointer, not the data. +struct ImFontAtlas +{ + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after Build(). Set font_cfg->FontDataOwnedByAtlas to false to keep ownership. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void ClearTexData(); // Clear the CPU-side texture data. Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearInputData(); // Clear the input TTF data (inc sizes, glyph ranges) + IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates) + IMGUI_API void Clear(); // Clear all + + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // RGBA32 format is provided for convenience and compatibility, but note that unless you use CustomRect to draw color data, the RGB pixels emitted from Fonts will all be white (~75% of waste). + // Pitch = Width * BytesPerPixels + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Default + Japanese + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters + IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters + + // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges(). + struct GlyphRangesBuilder + { + ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + GlyphRangesBuilder() { UsedChars.resize(0x10000 / 8); memset(UsedChars.Data, 0, 0x10000 / 8); } + bool GetBit(int n) { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; } + void SetBit(int n) { UsedChars[n >> 3] |= 1 << (n & 7); } // Set bit 'c' in the array + void AddChar(ImWchar c) { SetBit(c); } // Add character + IMGUI_API void AddText(const char* text, const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) + IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault) to force add all of ASCII/Latin+Ext + IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges + }; + + //------------------------------------------- + // Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels. + // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. + struct CustomRect + { + unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data. + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset + ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font + CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } + }; + + IMGUI_API int AddCustomRectRegular(unsigned int id, int width, int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. + const CustomRect* GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; } + + // Internals + IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + + //------------------------------------------- + // Members + //------------------------------------------- + + ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; // Texture width calculated during Build(). + int TexHeight; // Texture height calculated during Build(). + ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. + ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. + ImVector ConfigData; // Internal data + int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList +}; + +// Font runtime data and rendering +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +struct ImFont +{ + // Members: Hot ~62/78 bytes + float FontSize; // // Height of characters, set during loading (don't change after loading) + float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() + ImVec2 DisplayOffset; // = (0.f,1.f) // Offset font rendering by xx pixels + ImVector Glyphs; // // All glyphs. + ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). + ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. + const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + float FallbackAdvanceX; // == FallbackGlyph->AdvanceX + ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() + + // Members: Cold ~18/26 bytes + short ConfigDataCount; // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. + ImFontConfig* ConfigData; // // Pointer within ContainerAtlas->ConfigData + ImFontAtlas* ContainerAtlas; // // What we has been loaded into + float Ascent, Descent; // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + int MetricsTotalSurface;// // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API void ClearOutputData(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; + IMGUI_API void SetFallbackChar(ImWchar c); + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + + // [Internal] + IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); + IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ +#endif +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif diff --git a/apps/MDL_sdf/imgui/imgui_demo.cpp b/apps/MDL_sdf/imgui/imgui_demo.cpp new file mode 100644 index 00000000..2a197278 --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_demo.cpp @@ -0,0 +1,3150 @@ +// dear imgui, v1.60 WIP +// (demo code) + +// Message to the person tempted to delete this file when integrating ImGui into their code base: +// Don't do it! Do NOT remove this file from your project! It is useful reference code that you and other users will want to refer to. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// During development, you can call ImGui::ShowDemoWindow() in your code to learn about various features of ImGui. Have it wired in a debug menu! +// Removing this file from your project is hindering access to documentation for everyone in your team, likely leading you to poorer usage of the library. +// Note that you can #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h for the same effect. +// If you want to link core ImGui in your final builds but not those demo windows, #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h and those functions will be empty. +// In other situation, when you have ImGui available you probably want this to be available for reference and execution. +// Thank you, +// -Your beloved friend, imgui_demo.cpp (that you won't delete) + +// Message to beginner C/C++ programmers. About the meaning of 'static': in this demo code, we frequently we use 'static' variables inside functions. +// We do this as a way to gather code and data in the same place, just to make the demo code faster to read, faster to write, and use less code. +// A static variable persist across calls, so it is essentially like a global variable but declared inside the scope of the function. +// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant or used in threads. +// This might be a pattern you occasionally want to use in your code, but most of the real data you would be editing is likely to be stored outside your function. + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include // toupper, isprint +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored "-Wformat-security" // warning : warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning : format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#if (__GNUC__ >= 6) +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#endif +#endif + +// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) + +//----------------------------------------------------------------------------- +// DEMO CODE +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Obsolete name since 1.53, TEST->DEMO +#define IMGUI_DISABLE_DEMO_WINDOWS +#endif + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +static void ShowExampleAppConsole(bool* p_open); +static void ShowExampleAppLog(bool* p_open); +static void ShowExampleAppLayout(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppLongText(bool* p_open); +static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); +static void ShowExampleAppFixedOverlay(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); +static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleAppMainMenuBar(); +static void ShowExampleMenuFile(); + +static void ShowHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +void ImGui::ShowUserGuide() +{ + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents)."); + ImGui::BulletText("Click and drag on any empty space to move window."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + if (ImGui::GetIO().FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("Mouse Wheel to scroll."); + ImGui::BulletText("While editing text:\n"); + ImGui::Indent(); + ImGui::BulletText("Hold SHIFT or use mouse to select text."); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); +} + +// Demonstrate most ImGui features (big function!) +void ImGui::ShowDemoWindow(bool* p_open) +{ + // Examples apps + static bool show_app_main_menu_bar = false; + static bool show_app_console = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_long_text = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_fixed_overlay = false; + static bool show_app_window_titles = false; + static bool show_app_custom_rendering = false; + static bool show_app_style_editor = false; + + static bool show_app_metrics = false; + static bool show_app_about = false; + + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } + if (show_app_about) + { + ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Text("Dear ImGui, %s", ImGui::GetVersion()); + ImGui::Separator(); + ImGui::Text("By Omar Cornut and all dear imgui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); + ImGui::End(); + } + + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + static bool no_nav = false; + + // Demonstrate the various window flags. Typically you would just use the default. + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + + ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); // 2/3 of the space for widget and 1/3 for labels + ImGui::PushItemWidth(-140); // Right align, keep 140 pixels for labels + + ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + ImGui::MenuItem("Metrics", NULL, &show_app_metrics); + ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + if (ImGui::CollapsingHeader("Help")) + { + ImGui::TextWrapped("This window is being created by the ShowDemoWindow() function. Please refer to the code in imgui_demo.cpp for reference.\n\n"); + ImGui::Text("USER GUIDE:"); + ImGui::ShowUserGuide(); + } + + if (ImGui::CollapsingHeader("Window options")) + { + ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); + ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); + ImGui::Checkbox("No menu", &no_menu); + ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); + ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); + ImGui::Checkbox("No collapse", &no_collapse); + ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); + ImGui::Checkbox("No nav", &no_nav); + + if (ImGui::TreeNode("Style")) + { + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Capture/Logging")) + { + ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. You can also call ImGui::LogText() to output directly to the log without a visual output."); + ImGui::LogButtons(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Widgets")) + { + if (ImGui::TreeNode("Basic")) + { + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + ImGui::Text("Hover over me"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::Text("- or me"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + // Testing ImGuiOnceUponAFrame helper. + //static ImGuiOnceUponAFrame once; + //for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + ImGui::Separator(); + + ImGui::LabelText("label", "Value"); + + { + // Simplified one-liner Combo() API, using values packed in a single constant string + static int current_item_1 = 1; + ImGui::Combo("combo", ¤t_item_1, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + //ImGui::Combo("combo w/ array of char*", ¤t_item_2_idx, items, IM_ARRAYSIZE(items)); // Combo using proper array. You can also pass a callback to retrieve array value, no need to create/copy an array just for that. + + // General BeginCombo() API, you have full control over your selection data and display type + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO", "PPPP", "QQQQQQQQQQ", "RRR", "SSSS" }; + static const char* current_item_2 = NULL; + if (ImGui::BeginCombo("combo 2", current_item_2)) // The second parameter is the label previewed before opening the combo. + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + bool is_selected = (current_item_2 == items[n]); // You can store your selection however you want, outside or inside your objects + if (ImGui::Selectable(items[n], is_selected)) + current_item_2 = items[n]; + if (is_selected) + ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch) + } + ImGui::EndCombo(); + } + } + + { + static char str0[128] = "Hello, world!"; + static int i0=123; + static float f0=0.001f; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); ShowHelpMarker("Hold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n"); + + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); + + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + ImGui::InputFloat3("input float3", vec4a); + } + + { + static int i1=50, i2=42; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%.0f%%"); + + static float f1=1.00f, f2=0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + } + + { + static int i1=0; + ImGui::SliderInt("slider int", &i1, -1, 3); + ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value."); + + static float f1=0.123f, f2=0.0f; + ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); + ImGui::SliderFloat("slider log float", &f2, -10.0f, 10.0f, "%.4f", 3.0f); + static float angle = 0.0f; + ImGui::SliderAngle("slider angle", &angle); + } + + static float col1[3] = { 1.0f,0.0f,0.2f }; + static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n"); + + ImGui::ColorEdit4("color 2", col2); + + const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int listbox_item_current = 1; + ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + + //static int listbox_item_current2 = 2; + //ImGui::PushItemWidth(-1); + //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + //ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Trees")) + { + if (ImGui::TreeNode("Basic trees")) + { + for (int i = 0; i < 5; i++) + if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("button")) { }; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced, with Selectable nodes")) + { + ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static bool align_label_with_current_x_position = false; + ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position); + ImGui::Text("Hello!"); + if (align_label_with_current_x_position) + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + + static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit. + int node_clicked = -1; // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc. + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents. + for (int i = 0; i < 6; i++) + { + // Disable the default open on single-click behavior and pass in Selected flag according to our selection state. + ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0); + if (i < 3) + { + // Node + bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (node_open) + { + ImGui::Text("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } + else + { + // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + } + } + if (node_clicked != -1) + { + // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame. + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + ImGui::PopStyleVar(); + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collapsing Headers")) + { + static bool closable_group = true; + ImGui::Checkbox("Enable extra group", &closable_group); + if (ImGui::CollapsingHeader("Header")) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Bullets")) + { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text")) + { + if (ImGui::TreeNode("Colored Text")) + { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Word Wrapping")) + { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); + ImGui::Spacing(); + + static float wrap_width = 200.0f; + ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + + ImGui::Text("Test paragraph 1:"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::Text("Test paragraph 2:"); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("UTF-8 Text")) + { + // UTF-8 test with Japanese characters + // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature') + // - HOWEVER, FOR THIS DEMO FILE, BECAUSE WE WANT TO SUPPORT COMPILER, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE. + // Instead we are encoding a few string with hexadecimal constants. Don't do this in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // "nihongo" + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Images")) + { + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); + ImGuiIO& io = ImGui::GetIO(); + + // Here we are grabbing the font texture because that's the only one we have access to inside the demo code. + // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure. + // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID. + // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.) + // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc. + // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this. + // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). + ImTextureID my_tex_id = io.Fonts->TexID; + float my_tex_w = (float)io.Fonts->TexWidth; + float my_tex_h = (float)io.Fonts->TexHeight; + + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float focus_sz = 32.0f; + float focus_x = io.MousePos.x - pos.x - focus_sz * 0.5f; if (focus_x < 0.0f) focus_x = 0.0f; else if (focus_x > my_tex_w - focus_sz) focus_x = my_tex_w - focus_sz; + float focus_y = io.MousePos.y - pos.y - focus_sz * 0.5f; if (focus_y < 0.0f) focus_y = 0.0f; else if (focus_y > my_tex_h - focus_sz) focus_y = my_tex_h - focus_sz; + ImGui::Text("Min: (%.2f, %.2f)", focus_x, focus_y); + ImGui::Text("Max: (%.2f, %.2f)", focus_x + focus_sz, focus_y + focus_sz); + ImVec2 uv0 = ImVec2((focus_x) / my_tex_w, (focus_y) / my_tex_h); + ImVec2 uv1 = ImVec2((focus_x + focus_sz) / my_tex_w, (focus_y + focus_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(128,128), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::EndTooltip(); + } + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); + int frame_padding = -1 + i; // -1 = uses default padding + if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255))) + pressed_count += 1; + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Selectables")) + { + // Selectable() has 2 overloads: + // - The one taking "bool selected" as a read-only selection information. When Selectable() has been clicked is returns true and you can alter selection state accordingly. + // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) + // The earlier is more flexible, as in real application your selection may be stored in a different manner (in flags within objects, as an external list, etc). + if (ImGui::TreeNode("Basic")) + { + static bool selection[5] = { false, true, false, false, false }; + ImGui::Selectable("1. I am selectable", &selection[0]); + ImGui::Selectable("2. I am selectable", &selection[1]); + ImGui::Text("3. I am not selectable"); + ImGui::Selectable("4. I am selectable", &selection[3]); + if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selection[4] = !selection[4]; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Single Selection")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Multiple Selection")) + { + ShowHelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; + } + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rendering more text into the same line")) + { + // Using the Selectable() override that takes "bool* p_selected" parameter and toggle your booleans automatically. + static bool selected[3] = { false, false, false }; + ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); + ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("In columns")) + { + ImGui::Columns(3, NULL, false); + static bool selected[16] = { 0 }; + for (int i = 0; i < 16; i++) + { + char label[32]; sprintf(label, "Item %d", i); + if (ImGui::Selectable(label, &selected[i])) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Grid")) + { + static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true }; + for (int i = 0; i < 16; i++) + { + ImGui::PushID(i); + if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50))) + { + int x = i % 4, y = i / 4; + if (x > 0) selected[i - 1] ^= 1; + if (x < 3) selected[i + 1] ^= 1; + if (y > 0) selected[i - 4] ^= 1; + if (y < 3) selected[i + 4] ^= 1; + } + if ((i % 4) < 3) ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Filtered Text Input")) + { + static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); + static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); + static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); + static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); + struct TextFilters { static int FilterImGuiLetters(ImGuiTextEditCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } }; + static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); + + ImGui::Text("Password input"); + static char bufpass[64] = "password123"; + ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank); + ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-line Text Input")) + { + static bool read_only = false; + static char text[1024*16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\n\n" + "label:\n" + "\tlock cmpxchg8b eax\n"; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Read-only", &read_only); + ImGui::PopStyleVar(); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Plots widgets")) + { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + + // Create a dummy array of contiguous float values to plot + // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter. + static float values[90] = { 0 }; + static int values_offset = 0; + static float refresh_time = 0.0f; + if (!animate || refresh_time == 0.0f) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset+1) % IM_ARRAYSIZE(values); + phase += 0.10f*values_offset; + refresh_time += 1.0f/60.0f; + } + ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80)); + + // Use functions to generate output + // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count. + struct Funcs + { + static float Sin(void*, int i) { return sinf(i * 0.1f); } + static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } + }; + static int func_type = 0, display_count = 70; + ImGui::Separator(); + ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::Separator(); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) + { + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + } + + // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; + char buf[32]; + sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Color/Picker Widgets")) + { + static ImVec4 color = ImColor(114, 144, 154, 200); + + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool options_menu = true; + static bool hdr = false; + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options."); + ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + ImGui::Text("Color widget:"); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n"); + ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); + + ImGui::Text("Color widget HSV with Alpha:"); + ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags); + + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); + + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup."); + ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); + + ImGui::Text("Color button with Custom Picker Popup:"); + + // Generate a dummy palette + static bool saved_palette_inited = false; + static ImVec4 saved_palette[32]; + if (!saved_palette_inited) + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_inited = true; + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(); + open_popup |= ImGui::Button("Palette"); + if (open_popup) + { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) + { + // FIXME: Adding a drag and drop example here would be perfect! + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("Current"); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::PushID(n); + if ((n % 8) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20))) + color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); + EndDragDropTarget(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::EndPopup(); + } + + ImGui::Text("Color button only:"); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); + + ImGui::Text("Color picker:"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f); + static int inputs_mode = 2; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) + { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) + { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0"); + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); + ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; + if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; + if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB; + if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV; + if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX; + ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + + ImGui::Text("Programmatically set defaults/options:"); + ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Uint8 + HSV")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV); + ImGui::SameLine(); + if (ImGui::Button("Float + HDR")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_RGB); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Range Widgets")) + { + static float begin = 10, end = 90; + static int begin_i = 100, end_i = 1000; + ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%"); + ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %.0f units", "Max: %.0f units"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-component Widgets")) + { + static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + static int vec4i[4] = { 1, 5, 100, 255 }; + + ImGui::InputFloat2("input float2", vec4f); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); + ImGui::InputInt2("input int2", vec4i); + ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat3("input float3", vec4f); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); + ImGui::InputInt3("input int3", vec4i); + ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat4("input float4", vec4f); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Vertical Sliders")) + { + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + static int int_value = 0; + ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5); + ImGui::SameLine(); + + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; + ImGui::PushID("set1"); + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; + const int rows = 3; + const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows); + for (int nx = 0; nx < 4; nx++) + { + if (nx > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) + { + ImGui::PushID(nx*rows+ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Layout")) + { + if (ImGui::TreeNode("Child regions")) + { + static bool disable_mouse_wheel = false; + static bool disable_menu = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + ImGui::Checkbox("Disable Menu", &disable_menu); + + static int line = 50; + bool goto_line = ImGui::Button("Goto"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopItemWidth(); + + // Child 1: no border, enable horizontal scrollbar + { + ImGui::BeginChild("Child1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); + for (int i = 0; i < 100; i++) + { + ImGui::Text("%04d: scrollable region", i); + if (goto_line && line == i) + ImGui::SetScrollHere(); + } + if (goto_line && line >= 100) + ImGui::SetScrollHere(); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("Child2", ImVec2(0,300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0) | (disable_menu ? 0 : ImGuiWindowFlags_MenuBar)); + if (!disable_menu && ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Columns(2); + for (int i = 0; i < 100; i++) + { + if (i == 50) + ImGui::NextColumn(); + char buf[32]; + sprintf(buf, "%08x", i*5731); + ImGui::Button(buf, ImVec2(-1.0f, 0.0f)); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Widgets Width")) + { + static float f = 0.0f; + ImGui::Text("PushItemWidth(100)"); + ImGui::SameLine(); ShowHelpMarker("Fixed width."); + ImGui::PushItemWidth(100); + ImGui::DragFloat("float##1", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of window width."); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + ImGui::DragFloat("float##2", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f); + ImGui::DragFloat("float##3", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-100)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##4", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-1)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge"); + ImGui::PushItemWidth(-1); + ImGui::DragFloat("float##5", &f); + ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Basic Horizontal Layout")) + { + ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + + // Text + ImGui::Text("Two items: Hello"); ImGui::SameLine(); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Adjust spacing + ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Button + ImGui::AlignTextToFramePadding(); + ImGui::Text("Normal buttons"); ImGui::SameLine(); + ImGui::Button("Banana"); ImGui::SameLine(); + ImGui::Button("Apple"); ImGui::SameLine(); + ImGui::Button("Corniflower"); + + // Button + ImGui::Text("Small buttons"); ImGui::SameLine(); + ImGui::SmallButton("Like this one"); ImGui::SameLine(); + ImGui::Text("can fit within a text block."); + + // Aligned to arbitrary position. Easy/cheap column. + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::Text("x=150"); + ImGui::SameLine(300); ImGui::Text("x=300"); + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::SmallButton("x=150"); + ImGui::SameLine(300); ImGui::SmallButton("x=300"); + + // Checkbox + static bool c1=false,c2=false,c3=false,c4=false; + ImGui::Checkbox("My", &c1); ImGui::SameLine(); + ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); + ImGui::Checkbox("Is", &c3); ImGui::SameLine(); + ImGui::Checkbox("Rich", &c4); + + // Various + static float f0=1.0f, f1=2.0f, f2=3.0f; + ImGui::PushItemWidth(80); + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Y", &f1, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Z", &f2, 0.0f,5.0f); + ImGui::PopItemWidth(); + + ImGui::PushItemWidth(80); + ImGui::Text("Lists:"); + static int selection[4] = { 0, 1, 2, 3 }; + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); + } + ImGui::PopItemWidth(); + + // Dummy + ImVec2 sz(30,30); + ImGui::Button("A", sz); ImGui::SameLine(); + ImGui::Dummy(sz); ImGui::SameLine(); + ImGui::Button("B", sz); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Groups")) + { + ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + ImVec2 size = ImGui::GetItemRectSize(); + const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; + ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::SameLine(); + ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::Button("LEVERAGE\nBUZZWORD", size); + ImGui::SameLine(); + + ImGui::ListBoxHeader("List", size); + ImGui::Selectable("Selected", true); + ImGui::Selectable("Not Selected", false); + ImGui::ListBoxFooter(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text Baseline Alignment")) + { + ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)"); + + ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("One\nTwo\nThree"); + + ImGui::Button("HOP##1"); ImGui::SameLine(); + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("HOP##2"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("TEST##1"); ImGui::SameLine(); + ImGui::Text("TEST"); ImGui::SameLine(); + ImGui::SmallButton("TEST##2"); + + ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. + ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); + ImGui::Button("Widget##1"); ImGui::SameLine(); + ImGui::Text("Widget"); ImGui::SameLine(); + ImGui::SmallButton("Widget##2"); ImGui::SameLine(); + ImGui::Button("Widget##3"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). + bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + // Bullet + ImGui::Button("Button##3"); + ImGui::SameLine(0.0f, spacing); + ImGui::BulletText("Bullet text"); + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Node"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Scrolling")) + { + ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)"); + static bool track = true; + static int track_line = 50, scroll_to_px = 200; + ImGui::Checkbox("Track", &track); + ImGui::PushItemWidth(100); + ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %.0f"); + bool scroll_to = ImGui::Button("Scroll To Pos"); + ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %.0f px"); + ImGui::PopItemWidth(); + if (scroll_to) track = false; + + for (int i = 0; i < 5; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom"); + ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true); + if (scroll_to) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f); + for (int line = 0; line < 100; line++) + { + if (track && line == track_line) + { + ImGui::TextColored(ImColor(255,255,0), "Line %d", line); + ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } + else + { + ImGui::Text("Line %d", line); + } + } + float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag."); + ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing()*7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) + { + // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off + // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API) + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) + { + if (n > 0) ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf; + float hue = n*0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("Scroll from code"); ImGui::SameLine(); + ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) + { + ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window) + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + ImGui::End(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Clipping")) + { + static ImVec2 size(100, 100), offset(50, 20); + ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::TextWrapped("(Click and drag)"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y); + ImGui::InvisibleButton("##dummy", size); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } + ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x+size.x,pos.y+size.y), IM_COL32(90,90,120,255)); + ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x+offset.x,pos.y+offset.y), IM_COL32(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Popups & Modal windows")) + { + if (ImGui::TreeNode("Popups")) + { + ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); + + static int selected_fish = -1; + const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; + static bool toggles[] = { true, false, false, false, false }; + + // Simple selection popup + // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label) + if (ImGui::Button("Select..")) + ImGui::OpenPopup("select"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("select")) + { + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("toggle"); + if (ImGui::BeginPopup("toggle")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); + } + + if (ImGui::Button("Popup Menu..")) + ImGui::OpenPopup("FilePopup"); + if (ImGui::BeginPopup("FilePopup")) + { + ShowExampleMenuFile(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Context menus")) + { + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (IsItemHovered() && IsMouseClicked(0)) + // OpenPopup(id); + // return BeginPopup(id); + // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. + static float value = 0.5f; + ImGui::Text("Value = %.3f (<-- right-click here)", value); + if (ImGui::BeginPopupContextItem("item context menu")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::PushItemWidth(-1); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PopItemWidth(); + ImGui::EndPopup(); + } + + static char name[32] = "Label1"; + char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Modals")) + { + ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window."); + + if (ImGui::Button("Delete..")) + ImGui::OpenPopup("Delete?"); + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Separator(); + + //static int dummy_i = 0; + //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0"); + + static bool dont_ask_me_next_time = false; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); + ImGui::PopStyleVar(); + + if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::Button("Stacked modals..")) + ImGui::OpenPopup("Stacked 1"); + if (ImGui::BeginPopupModal("Stacked 1")) + { + ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDarkening] for darkening."); + static int item = 1; + ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal + + if (ImGui::Button("Add another modal..")) + ImGui::OpenPopup("Stacked 2"); + if (ImGui::BeginPopupModal("Stacked 2")) + { + ImGui::Text("Hello from Stacked The Second!"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. + // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here + // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Columns")) + { + ImGui::PushID("Columns"); + + // Basic columns + if (ImGui::TreeNode("Basic")) + { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) {} + //if (ImGui::Button(label, ImVec2(-1,0))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); + ImGui::Separator(); + const char* names[3] = { "One", "Two", "Three" }; + const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; + static int selected = -1; + for (int i = 0; i < 3; i++) + { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); ImGui::NextColumn(); + ImGui::Text(paths[i]); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Create multiple items in a same cell before switching to next column + if (ImGui::TreeNode("Mixed items")) + { + ImGui::Columns(3, "mixed"); + ImGui::Separator(); + + ImGui::Text("Hello"); + ImGui::Button("Banana"); + ImGui::NextColumn(); + + ImGui::Text("ImGui"); + ImGui::Button("Apple"); + static float foo = 1.0f; + ImGui::InputFloat("red", &foo, 0.05f, 0, 3); + ImGui::Text("An extra line here."); + ImGui::NextColumn(); + + ImGui::Text("Sailor"); + ImGui::Button("Corniflower"); + static float bar = 1.0f; + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); + ImGui::NextColumn(); + + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + if (ImGui::TreeNode("Word-wrapping")) + { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Borders")) + { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(4, NULL, v_borders); + for (int i = 0; i < 4*3; i++) + { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i); + ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + ImGui::Separator(); + ImGui::TreePop(); + } + + // Scrolling columns + /* + if (ImGui::TreeNode("Vertical Scrolling")) + { + ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); + ImGui::Columns(3); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::EndChild(); + ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); + ImGui::Columns(3); + for (int i = 0; i < 10; i++) + { + ImGui::Text("%04d", i); ImGui::NextColumn(); + ImGui::Text("Foobar"); ImGui::NextColumn(); + ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + */ + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) + { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + bool node_open = ImGui::TreeNode("Tree within single cell"); + ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell."); + if (node_open) + { + ImGui::Columns(2, "tree items"); + ImGui::Separator(); + if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn(); + if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::PopID(); + } + + if (ImGui::CollapsingHeader("Filtering")) + { + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + } + + if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) + { + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantMoveMouse: %d", io.WantMoveMouse); + ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); + + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to render a mouse cursor for you in software. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::CheckboxFlags("io.NavFlags: EnableGamepad", (unsigned int *)&io.NavFlags, ImGuiNavFlags_EnableGamepad); + ImGui::CheckboxFlags("io.NavFlags: EnableKeyboard", (unsigned int *)&io.NavFlags, ImGuiNavFlags_EnableKeyboard); + ImGui::CheckboxFlags("io.NavFlags: MoveMouse", (unsigned int *)&io.NavFlags, ImGuiNavFlags_MoveMouse); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to move your move cursor when using gamepad/keyboard navigation. NewFrame() will change io.MousePos and set the io.WantMoveMouse flag, your backend will need to apply the new mouse position."); + + if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) + { + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + + ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } + ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } + ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } + + ImGui::Button("Hovering me sets the\nkeyboard capture flag"); + if (ImGui::IsItemHovered()) + ImGui::CaptureKeyboardFromApp(true); + ImGui::SameLine(); + ImGui::Button("Holding me clears the\nthe keyboard capture flag"); + if (ImGui::IsItemActive()) + ImGui::CaptureKeyboardFromApp(false); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tabbing")) + { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "dummy"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushAllowKeyboardFocus(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets."); + ImGui::PopAllowKeyboardFocus(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focus from code")) + { + bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); + bool focus_3 = ImGui::Button("Focus on 3"); + int has_focus = 0; + static char buf[128] = "click on a button to set focus"; + + if (focus_1) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 1; + + if (focus_2) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 2; + + ImGui::PushAllowKeyboardFocus(false); + if (focus_3) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 3; + ImGui::PopAllowKeyboardFocus(); + + if (has_focus) + ImGui::Text("Item with focus: %d", has_focus); + else + ImGui::Text("Item with focus: "); + + // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item + static float f3[3] = { 0.0f, 0.0f, 0.0f }; + int focus_ahead = -1; + if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine(); + if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine(); + if (ImGui::Button("Focus on Z")) focus_ahead = 2; + if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); + ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); + + ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focused & Hovered Test")) + { + static bool embed_all_inside_a_child_window = false; + ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); + if (embed_all_inside_a_child_window) + ImGui::BeginChild("embeddingchild", ImVec2(0, ImGui::GetFontSize() * 25), true); + + // Testing IsWindowFocused() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n" + "IsWindowFocused(_AnyWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + + // Testing IsWindowHovered() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n" + "IsWindowHovered(_AnyWindow) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); + + // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) + ImGui::Button("ITEM"); + ImGui::BulletText( + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemhovered(_RectOnly) = %d\n", + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + + ImGui::BeginChild("child", ImVec2(0,50), true); + ImGui::Text("This is another child window for testing IsWindowHovered() flags."); + ImGui::EndChild(); + + if (embed_all_inside_a_child_window) + EndChild(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Dragging")) + { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + ImGui::Text("IsMouseDragging(%d):\n w/ default threshold: %d,\n w/ zero threshold: %d\n w/ large threshold: %d", + button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f)); + ImGui::Button("Drag Me"); + if (ImGui::IsItemActive()) + { + // Draw a line between the button and the mouse cursor + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRectFullScreen(); + draw_list->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); + draw_list->PopClipRect(); + + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold) + // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta() + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Mouse cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_Count_); + + ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_Count_; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + } + + ImGui::End(); +} + +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsClassic(); break; + case 1: ImGui::StyleColorsDark(); break; + case 2: ImGui::StyleColorsLight(); break; + } + return true; + } + return false; +} + +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = ImGui::GetIO(); + ImFont* font_current = ImGui::GetFont(); + if (ImGui::BeginCombo(label, font_current->GetDebugName())) + { + for (int n = 0; n < io.Fonts->Fonts.Size; n++) + if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current)) + io.FontDefault = io.Fonts->Fonts[n]; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker( + "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and documentation in misc/fonts/ for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} + +void ImGui::ShowStyleEditor(ImGuiStyle* ref) +{ + // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + init = false; + if (ref == NULL) + ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere."); + + if (ImGui::TreeNode("Rendering")) + { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(100); + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f); + if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f; + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Settings")) + { + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui::Text("BorderSize"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::Text("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::Text("Alignment"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Colors")) + { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export Unsaved")) + { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu."); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + + static ImGuiColorEditFlags alpha_flags = 0; + ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine(); + ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine(); + ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf); + + ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons. + // Read the FAQ and misc/fonts/README.txt about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i]; + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::TreePop(); + } + + bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size); + if (fonts_opened) + { + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::TreePop(); + } + ImGui::PushItemWidth(100); + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); + bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); + ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; + if (font_details_opened) + { + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font + ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, 0); + ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)"); + ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); + ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface)); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + { + ImFontConfig* cfg = &font->ConfigData[config_i]; + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); + } + if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + // Display all glyphs of the fonts in separate pages of 256 characters + const ImFontGlyph* glyph_fallback = font->FallbackGlyph; // Forcefully/dodgily make FindGlyph() return NULL on fallback, which isn't the default behavior. + font->FallbackGlyph = NULL; + for (int base = 0; base < 0x10000; base += 256) + { + int count = 0; + for (int n = 0; n < 256; n++) + count += font->FindGlyph((ImWchar)(base + n)) ? 1 : 0; + if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph")) + { + float cell_spacing = style.ItemSpacing.y; + ImVec2 cell_size(font->FontSize * 1, font->FontSize * 1); + ImVec2 base_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (int n = 0; n < 256; n++) + { + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size.x + cell_spacing), base_pos.y + (n / 16) * (cell_size.y + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size.x, cell_p1.y + cell_size.y); + const ImFontGlyph* glyph = font->FindGlyph((ImWchar)(base+n));; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); + font->RenderChar(draw_list, cell_size.x, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. + if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) + { + ImGui::BeginTooltip(); + ImGui::Text("Codepoint: U+%04X", base+n); + ImGui::Separator(); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); + ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + ImGui::EndTooltip(); + } + } + ImGui::Dummy(ImVec2((cell_size.x + cell_spacing) * 16, (cell_size.y + cell_spacing) * 16)); + ImGui::TreePop(); + } + } + font->FallbackGlyph = glyph_fallback; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + static float window_scale = 1.0f; + ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window + ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything + ImGui::PopItemWidth(); + ImGui::SetWindowFontScale(window_scale); + ImGui::TreePop(); + } + + ImGui::PopItemWidth(); +} + +// Demonstrate creating a fullscreen menu bar and populating it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(dummy menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + static bool b = true; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::Checkbox("Check", &b); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Colors")) + { + float sz = ImGui::GetTextLineHeight(); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName((ImGuiCol)i); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x+sz, p.y+sz), ImGui::GetColorU32((ImGuiCol)i)); + ImGui::Dummy(ImVec2(sz, sz)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + +// Demonstrate creating a window which gets auto-resized according to its content. +static void ShowExampleAppAutoResize(bool* p_open) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + static int lines = 10; + ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + ImGui::Text("%*sThis is line %d", i*4, "", i); // Pad with space to extend size horizontally + ImGui::End(); +} + +// Demonstrate creating a window with custom resize constraints. +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints // Helper functions to demonstrate programmatic constraints + { + static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } + static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + static bool auto_resize = false; + static int type = 0; + static int display_lines = 10; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step + + ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + { + const char* desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } + ImGui::PushItemWidth(200); + ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::PopItemWidth(); + ImGui::Checkbox("Auto-resize", &auto_resize); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + ImGui::End(); +} + +// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. +static void ShowExampleAppFixedOverlay(bool* p_open) +{ + const float DISTANCE = 10.0f; + static int corner = 0; + ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background + if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoFocusOnAppearing|ImGuiWindowFlags_NoNav)) + { + ImGui::Text("Simple overlay\nin the corner of the screen.\n(right-click to change position)"); + ImGui::Separator(); + ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + if (p_open && ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndPopup(); + } + ImGui::End(); + } +} + +// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. +// This apply to regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. +static void ShowExampleAppWindowTitles(bool*) +{ + // By default, Windows are uniquely identified by their title. + // You can use the "##" and "###" markers to manipulate the display/ID. + + // Using "##" to display same title but have unique identifier. + ImGui::SetNextWindowPos(ImVec2(100,100), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##1"); + ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(100,200), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##2"); + ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); + ImGui::End(); + + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" + char buf[128]; + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime()/0.25f)&3], ImGui::GetFrameCount()); + ImGui::SetNextWindowPos(ImVec2(100,300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(350,560), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Custom rendering", p_open)) + { + ImGui::End(); + return; + } + + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc. + // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4. + // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types) + // In this example we are not using the maths operators! + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Primitives + ImGui::Text("Primitives"); + static float sz = 36.0f; + static ImVec4 col = ImVec4(1.0f,1.0f,0.4f,1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); + ImGui::ColorEdit3("Color", &col.x); + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImU32 col32 = ImColor(col); + float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; + for (int n = 0; n < 2; n++) + { + float thickness = (n == 0) ? 1.0f : 4.0f; + draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, thickness); x += sz+spacing; + draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, thickness); x += spacing; + draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, thickness); + x = p.x + 4; + y += sz+spacing; + } + draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; + draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255)); + ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); + } + ImGui::Separator(); + { + static ImVector points; + static bool adding_line = false; + ImGui::Text("Canvas example"); + if (ImGui::Button("Clear")) points.clear(); + if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } } + ImGui::Text("Left-click and drag to add lines,\nRight-click to undo"); + + // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered() + // However you can draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos(). + // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max). + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + if (canvas_size.x < 50.0f) canvas_size.x = 50.0f; + if (canvas_size.y < 50.0f) canvas_size.y = 50.0f; + draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(50,50,50,255), IM_COL32(50,50,60,255), IM_COL32(60,60,70,255), IM_COL32(50,50,60,255)); + draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(255,255,255,255)); + + bool adding_preview = false; + ImGui::InvisibleButton("canvas", canvas_size); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + if (adding_line) + { + adding_preview = true; + points.push_back(mouse_pos_in_canvas); + if (!ImGui::IsMouseDown(0)) + adding_line = adding_preview = false; + } + if (ImGui::IsItemHovered()) + { + if (!adding_line && ImGui::IsMouseClicked(0)) + { + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (ImGui::IsMouseClicked(1) && !points.empty()) + { + adding_line = adding_preview = false; + points.pop_back(); + points.pop_back(); + } + } + draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x+canvas_size.x, canvas_pos.y+canvas_size.y), true); // clip lines within the canvas (if we resize it, etc.) + for (int i = 0; i < points.Size - 1; i += 2) + draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i+1].x, canvas_pos.y + points[i+1].y), IM_COL32(255,255,0,255), 2.0f); + draw_list->PopClipRect(); + if (adding_preview) + points.pop_back(); + } + ImGui::End(); +} + +// Demonstrating creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + bool ScrollToBottom; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImVector Commands; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); // "classify" is here to provide an example of "C"+[tab] completing to "CL" and displaying matches. + AddLog("Welcome to ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } + static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + ScrollToBottom = true; + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf)-1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close")) + *p_open = false; + ImGui::EndPopup(); + } + + ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); + if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + static ImGuiTextFilter filter; + filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::PopStyleVar(); + ImGui::Separator(); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. + // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. + // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: + // ImGuiListClipper clipper(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // However take note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! + // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!filter.PassFilter(item)) + continue; + ImVec4 col = col_default_text; + if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f); + else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(item); + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + if (ScrollToBottom) + ImGui::SetScrollHere(); + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + bool reclaim_focus = false; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) + { + char* input_end = InputBuf+strlen(InputBuf); + while (input_end > InputBuf && input_end[-1] == ' ') { input_end--; } *input_end = 0; + if (InputBuf[0]) + ExecCommand(InputBuf); + strcpy(InputBuf, ""); + reclaim_focus = true; + } + + // Demonstrate keeping focus on the input box + ImGui::SetItemDefaultFocus(); + if (reclaim_focus) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size-1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + } + + static int TextEditCallbackStub(ImGuiTextEditCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiTextEditCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing + data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + data->CursorPos = data->SelectionStart = data->SelectionEnd = data->BufTextLen = (int)snprintf(data->Buf, (size_t)data->BufSize, "%s", (HistoryPos >= 0) ? History[HistoryPos] : ""); + data->BufDirty = true; + } + } + } + return 0; + } +}; + +static void ShowExampleAppConsole(bool* p_open) +{ + static ExampleAppConsole console; + console.Draw("Example: Console", p_open); +} + +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct ExampleAppLog +{ + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear() { Buf.clear(); LineOffsets.clear(); } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open = NULL) + { + ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver); + ImGui::Begin(title, p_open); + if (ImGui::Button("Clear")) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + if (copy) ImGui::LogToClipboard(); + + if (Filter.IsActive()) + { + const char* buf_begin = Buf.begin(); + const char* line = buf_begin; + for (int line_no = 0; line != NULL; line_no++) + { + const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL; + if (Filter.PassFilter(line, line_end)) + ImGui::TextUnformatted(line, line_end); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } + else + { + ImGui::TextUnformatted(Buf.begin()); + } + + if (ScrollToBottom) + ImGui::SetScrollHere(1.0f); + ScrollToBottom = false; + ImGui::EndChild(); + ImGui::End(); + } +}; + +// Demonstrate creating a simple log window with basic filtering. +static void ShowExampleAppLog(bool* p_open) +{ + static ExampleAppLog log; + + // Demo: add random items (unless Ctrl is held) + static float last_time = -1.0f; + float time = ImGui::GetTime(); + if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) + { + const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; + log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); + last_time = time; + } + + log.Draw("Example: Log", p_open); +} + +// Demonstrate create a window with multiple child windows. +static void ShowExampleAppLayout(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // left + static int selected = 0; + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) + { + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + ImGui::SameLine(); + + // right + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + ImGui::End(); +} + +// Demonstrate create a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) + { + ImGui::End(); + return; + } + + ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API."); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2)); + ImGui::Columns(2); + ImGui::Separator(); + + struct funcs + { + static void ShowDummyObject(const char* prefix, int uid) + { + ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::NextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("my sailor is rich"); + ImGui::NextColumn(); + if (node_open) + { + static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f }; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) + { + ShowDummyObject("Child", 424242); + } + else + { + ImGui::AlignTextToFramePadding(); + // Here we use a Selectable (instead of Text) to highlight on hover + //ImGui::Text("Field_%d", i); + char label[32]; + sprintf(label, "Field_%d", i); + ImGui::Bullet(); + ImGui::Selectable(label); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + if (i >= 5) + ImGui::InputFloat("##value", &dummy_members[i], 1.0f); + else + ImGui::DragFloat("##value", &dummy_members[i], 0.01f); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + }; + + // Iterate dummy objects with dummy members (all the same data) + for (int obj_i = 0; obj_i < 3; obj_i++) + funcs::ShowDummyObject("Object", obj_i); + + ImGui::Columns(1); + ImGui::Separator(); + ImGui::PopStyleVar(); + ImGui::End(); +} + +// Demonstrate/test rendering huge amount of text, and the incidence of clipping. +static void ShowExampleAppLongText(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) + { + ImGui::End(); + return; + } + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { log.clear(); lines = 0; } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) + { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) + { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: + { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + ImGuiListClipper clipper(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + ImGui::EndChild(); + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowDemoWindow(bool*) {} +void ImGui::ShowUserGuide() {} +void ImGui::ShowStyleEditor(ImGuiStyle*) {} + +#endif diff --git a/apps/MDL_sdf/imgui/imgui_draw.cpp b/apps/MDL_sdf/imgui/imgui_draw.cpp new file mode 100644 index 00000000..118acabc --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_draw.cpp @@ -0,0 +1,2940 @@ +// dear imgui, v1.60 WIP +// (drawing and font code) + +// Contains implementation for +// - Default styles +// - ImDrawList +// - ImDrawData +// - ImFontAtlas +// - ImFont +// - Default font data + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#ifdef _WIN32 +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#elif defined(__GLIBC__) || defined(__sun) +#include // alloca +#else +#include // alloca +#endif +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" // warning : possible misuse of comma operator here // +#endif +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#endif + +//------------------------------------------------------------------------- +// STB libraries implementation +//------------------------------------------------------------------------- + +//#define IMGUI_STB_NAMESPACE ImGuiStb +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION + +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE +{ +#endif + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] +#endif + +#define STBRP_ASSERT(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#include "stb_rect_pack.h" + +#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) +#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) +#define STBTT_assert(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#include "stb_truetype.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +#ifdef IMGUI_STB_NAMESPACE +} // namespace ImGuiStb +using namespace IMGUI_STB_NAMESPACE; +#endif + +//----------------------------------------------------------------------------- +// Style functions +//----------------------------------------------------------------------------- + +void ImGui::StyleColorsDark(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.41f, 0.41f, 0.41f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); +} + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); +} + +// Those light colors are better suited with a thicker font than the default one + FrameBorder +void ImGui::StyleColorsLight(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + //colors[ImGuiCol_TextHovered] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + //colors[ImGuiCol_TextActive] = ImVec4(1.00f, 1.00f, 0.00f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.59f, 0.59f, 0.59f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); +} + +//----------------------------------------------------------------------------- +// ImDrawListData +//----------------------------------------------------------------------------- + +ImDrawListSharedData::ImDrawListSharedData() +{ + Font = NULL; + FontSize = 0.0f; + CurveTessellationTol = 0.0f; + ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); + + // Const data + for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++) + { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12); + CircleVtx12[i] = ImVec2(cosf(a), sinf(a)); + } +} + +//----------------------------------------------------------------------------- +// ImDrawList +//----------------------------------------------------------------------------- + +void ImDrawList::Clear() +{ + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + // NB: Do not clear channels so our allocations are re-used after the first frame. +} + +void ImDrawList::ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + for (int i = 0; i < _Channels.Size; i++) + { + if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0])); // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i].CmdBuffer.clear(); + _Channels[i].IdxBuffer.clear(); + } + _Channels.clear(); +} + +// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds +#define GetCurrentClipRect() (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1] : _Data->ClipRectFullscreen) +#define GetCurrentTextureId() (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL) + +void ImDrawList::AddDrawCmd() +{ + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = GetCurrentClipRect(); + draw_cmd.TextureId = GetCurrentTextureId(); + + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); + CmdBuffer.push_back(draw_cmd); +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +{ + ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL) + { + AddDrawCmd(); + current_cmd = &CmdBuffer.back(); + } + current_cmd->UserCallback = callback; + current_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. +// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. +void ImDrawList::UpdateClipRect() +{ + // If current command is used with different settings we need to add a new command + const ImVec4 curr_clip_rect = GetCurrentClipRect(); + ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->ClipRect = curr_clip_rect; +} + +void ImDrawList::UpdateTextureID() +{ + // If current command is used with different settings we need to add a new command + const ImTextureID curr_texture_id = GetCurrentTextureId(); + ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->TextureId = curr_texture_id; +} + +#undef GetCurrentClipRect +#undef GetCurrentTextureId + +// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) +void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect && _ClipRectStack.Size) + { + ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1]; + if (cr.x < current.x) cr.x = current.x; + if (cr.y < current.y) cr.y = current.y; + if (cr.z > current.z) cr.z = current.z; + if (cr.w > current.w) cr.w = current.w; + } + cr.z = ImMax(cr.x, cr.z); + cr.w = ImMax(cr.y, cr.w); + + _ClipRectStack.push_back(cr); + UpdateClipRect(); +} + +void ImDrawList::PushClipRectFullScreen() +{ + PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); +} + +void ImDrawList::PopClipRect() +{ + IM_ASSERT(_ClipRectStack.Size > 0); + _ClipRectStack.pop_back(); + UpdateClipRect(); +} + +void ImDrawList::PushTextureID(const ImTextureID& texture_id) +{ + _TextureIdStack.push_back(texture_id); + UpdateTextureID(); +} + +void ImDrawList::PopTextureID() +{ + IM_ASSERT(_TextureIdStack.Size > 0); + _TextureIdStack.pop_back(); + UpdateTextureID(); +} + +void ImDrawList::ChannelsSplit(int channels_count) +{ + IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) + _Channels.resize(channels_count); + _ChannelsCount = channels_count; + + // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer + // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. + // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } + else + { + _Channels[i].CmdBuffer.resize(0); + _Channels[i].IdxBuffer.resize(0); + } + if (_Channels[i].CmdBuffer.Size == 0) + { + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = _ClipRectStack.back(); + draw_cmd.TextureId = _TextureIdStack.back(); + _Channels[i].CmdBuffer.push_back(draw_cmd); + } + } +} + +void ImDrawList::ChannelsMerge() +{ + // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. + if (_ChannelsCount <= 1) + return; + + ChannelsSetCurrent(0); + if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0) + CmdBuffer.pop_back(); + + int new_cmd_buffer_count = 0, new_idx_buffer_count = 0; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0) + ch.CmdBuffer.pop_back(); + new_cmd_buffer_count += ch.CmdBuffer.Size; + new_idx_buffer_count += ch.IdxBuffer.Size; + } + CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count); + IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count); + + ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count; + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } + if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } + } + UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. + _ChannelsCount = 1; +} + +void ImDrawList::ChannelsSetCurrent(int idx) +{ + IM_ASSERT(idx < _ChannelsCount); + if (_ChannelsCurrent == idx) return; + memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times + memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer)); + _ChannelsCurrent = idx; + memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer)); + memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer)); + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size; +} + +// NB: this can be called with negative count for removing primitives (as long as the result does not underflow) +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1]; + draw_cmd.ElemCount += idx_count; + + int vtx_buffer_old_size = VtxBuffer.Size; + VtxBuffer.resize(vtx_buffer_old_size + vtx_count); + _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + + int idx_buffer_old_size = IdxBuffer.Size; + IdxBuffer.resize(idx_buffer_old_size + idx_count); + _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; +} + +// Fully unrolled with inline call to keep our debug builds decently fast. +void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +// TODO: Thickness anti-aliased lines cap are missing their AA fringe. +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) +{ + if (points_count < 2) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + int count = points_count; + if (!closed) + count = points_count-1; + + const bool thick_line = thickness > 1.0f; + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + // Anti-aliased stroke + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const int idx_count = thick_line ? count*18 : count*12; + const int vtx_count = thick_line ? points_count*4 : points_count*3; + PrimReserve(idx_count, vtx_count); + + // Temporary buffer + ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); + ImVec2* temp_points = temp_normals + points_count; + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + ImVec2 diff = points[i2] - points[i1]; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i1].x = diff.y; + temp_normals[i1].y = -diff.x; + } + if (!closed) + temp_normals[points_count-1] = temp_normals[points_count-2]; + + if (!thick_line) + { + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; + temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; + temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; + temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE; + temp_points[i2*2+0] = points[i2] + dm; + temp_points[i2*2+1] = points[i2] - dm; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1); + _IdxWritePtr += 12; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; + _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans; + _VtxWritePtr += 3; + } + } + else + { + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE); + ImVec2 dm_in = dm * half_inner_thickness; + temp_points[i2*4+0] = points[i2] + dm_out; + temp_points[i2*4+1] = points[i2] + dm_in; + temp_points[i2*4+2] = points[i2] - dm_in; + temp_points[i2*4+3] = points[i2] - dm_out; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+1); _IdxWritePtr[1] = (ImDrawIdx)(idx1+1); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3); + _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2); + _IdxWritePtr += 18; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Stroke + const int idx_count = count*6; + const int vtx_count = count*4; // FIXME-OPT: Not sharing edges + PrimReserve(idx_count, vtx_count); + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + const ImVec2& p1 = points[i1]; + const ImVec2& p2 = points[i2]; + ImVec2 diff = p2 - p1; + diff *= ImInvLength(diff, 1.0f); + + const float dx = diff.x * (thickness * 0.5f); + const float dy = diff.y * (thickness * 0.5f); + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2); + _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3); + _IdxWritePtr += 6; + _VtxCurrentIdx += 4; + } + } +} + +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count-2)*3 + points_count*6; + const int vtx_count = (points_count*2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx+1; + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); + _IdxWritePtr += 3; + } + + // Compute normals + ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + ImVec2 diff = p1 - p0; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i0].x = diff.y; + temp_normals[i0].y = -diff.x; + } + + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + ImVec2 dm = (n0 + n1) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count-2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius == 0.0f || a_min_of_12 > a_max_of_12) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); + for (int a = a_min_of_12; a <= a_max_of_12; a++) + { + const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)]; + _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); + } +} + +void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) +{ + if (radius == 0.0f) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (num_segments + 1)); + for (int i = 0; i <= num_segments; i++) + { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _Path.push_back(ImVec2(centre.x + cosf(a) * radius, centre.y + sinf(a) * radius)); + } +} + +static void PathBezierToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy)) + { + path->push_back(ImVec2(x4, y4)); + } + else if (level < 10) + { + float x12 = (x1+x2)*0.5f, y12 = (y1+y2)*0.5f; + float x23 = (x2+x3)*0.5f, y23 = (y2+y3)*0.5f; + float x34 = (x3+x4)*0.5f, y34 = (y3+y4)*0.5f; + float x123 = (x12+x23)*0.5f, y123 = (y12+y23)*0.5f; + float x234 = (x23+x34)*0.5f, y234 = (y23+y34)*0.5f; + float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f; + + PathBezierToCasteljau(path, x1,y1, x12,y12, x123,y123, x1234,y1234, tess_tol, level+1); + PathBezierToCasteljau(path, x1234,y1234, x234,y234, x34,y34, x4,y4, tess_tol, level+1); + } +} + +void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + // Auto-tessellated + PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + { + float t = t_step * i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t*t*t; + _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y)); + } + } +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) +{ + rounding = ImMin(rounding, fabsf(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, fabsf(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); + + if (rounding <= 0.0f || rounding_corners == 0) + { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } + else + { + const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; + const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; + const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; + const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); + } +} + +void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(a + ImVec2(0.5f,0.5f)); + PathLineTo(b + ImVec2(0.5f,0.5f)); + PathStroke(col, false, thickness); +} + +// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly. +void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.5f,0.5f), rounding, rounding_corners_flags); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (rounding > 0.0f) + { + PathRect(a, b, rounding, rounding_corners_flags); + PathFillConvex(col); + } + else + { + PrimReserve(6, 4); + PrimRect(a, b, col); + } +} + +void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) +{ + if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + PrimReserve(6, 4); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3)); + PrimWriteVtx(a, uv, col_upr_left); + PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right); + PrimWriteVtx(c, uv, col_bot_right); + PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left); +} + +void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathFillConvex(col); +} + +void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathFillConvex(col); +} + +void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius, 0.0f, a_max, num_segments); + PathFillConvex(col); +} + +void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(pos0); + PathBezierCurveTo(cp0, cp1, pos1, num_segments); + PathStroke(col, false, thickness); +} + +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // Pull default font/size from the shared ImDrawListSharedData instance + if (font == NULL) + font = _Data->Font; + if (font_size == 0.0f) + font_size = _Data->FontSize; + + IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + + ImVec4 clip_rect = _ClipRectStack.back(); + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); +} + +void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) +{ + AddText(NULL, 0.0f, pos, col, text_begin, text_end); +} + +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(a, b, uv_a, uv_b, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) + { + AddImage(user_texture_id, a, b, uv_a, uv_b, col); + return; + } + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + int vert_start_idx = VtxBuffer.Size; + PathRect(a, b, rounding, rounding_corners); + PathFillConvex(col); + int vert_end_idx = VtxBuffer.Size; + ImGui::ShadeVertsLinearUV(VtxBuffer.Data + vert_start_idx, VtxBuffer.Data + vert_end_idx, a, b, uv_a, uv_b, true); + + if (push_texture_id) + PopTextureID(); +} + +//----------------------------------------------------------------------------- +// ImDrawData +//----------------------------------------------------------------------------- + +// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! +void ImDrawData::DeIndexAllBuffers() +{ + ImVector new_vtx_buffer; + TotalVtxCount = TotalIdxCount = 0; + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + if (cmd_list->IdxBuffer.empty()) + continue; + new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); + for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; + cmd_list->VtxBuffer.swap(new_vtx_buffer); + cmd_list->IdxBuffer.resize(0); + TotalVtxCount += cmd_list->VtxBuffer.Size; + } +} + +// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +void ImDrawData::ScaleClipRects(const ImVec2& scale) +{ + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y); + } + } +} + +//----------------------------------------------------------------------------- +// Shade functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Scan and shade backward from the end of given vertices. Assume vertices are text only (= vert_start..vert_end going left to right) so we can break as soon as we are out the gradient bounds. +void ImGui::ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x) +{ + float gradient_extent_x = gradient_p1_x - gradient_p0_x; + float gradient_inv_length2 = 1.0f / (gradient_extent_x * gradient_extent_x); + int full_alpha_count = 0; + for (ImDrawVert* vert = vert_end - 1; vert >= vert_start; vert--) + { + float d = (vert->pos.x - gradient_p0_x) * (gradient_extent_x); + float alpha_mul = 1.0f - ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + if (alpha_mul >= 1.0f && ++full_alpha_count > 2) + return; // Early out + int a = (int)(((vert->col >> IM_COL32_A_SHIFT) & 0xFF) * alpha_mul); + vert->col = (vert->col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); + } +} + +// Distribute UV over (a, b) rectangle +void ImGui::ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + const ImVec2 scale = ImVec2( + size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, + size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + + if (clamp) + { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } + else + { + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + FontData = NULL; + FontDataSize = 0; + FontDataOwnedByAtlas = true; + FontNo = 0; + SizePixels = 0.0f; + OversampleH = 3; + OversampleV = 1; + PixelSnapH = false; + GlyphExtraSpacing = ImVec2(0.0f, 0.0f); + GlyphOffset = ImVec2(0.0f, 0.0f); + GlyphRanges = NULL; + MergeMode = false; + RasterizerFlags = 0x00; + RasterizerMultiply = 1.0f; + memset(Name, 0, sizeof(Name)); + DstFont = NULL; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas +//----------------------------------------------------------------------------- + +// A work of art lies ahead! (. = white layer, X = black layer, others are blank) +// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. +const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 90; +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX" + "..- -X.....X- X.X - X.X -X.....X - X.....X" + "--- -XXX.XXX- X...X - X...X -X....X - X....X" + "X - X.X - X.....X - X.....X -X...X - X...X" + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X" + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X" + "X..X - X.X - X.X - X.X -XX X.X - X.X XX" + "X...X - X.X - X.X - XX X.X XX - X.X - X.X " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X " + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X " + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X " + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X " + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X " + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X " + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X " + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------" + "X.X X..X - -X.......X- X.......X - XX XX - " + "XX X..X - - X.....X - X.....X - X.X X.X - " + " X..X - X...X - X...X - X..X X..X - " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - " + "------------ - X - X -X.....................X- " + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_Count_][3] = +{ + // Pos ........ Size ......... Offset ...... + { ImVec2(0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow + { ImVec2(13,0), ImVec2(7,16), ImVec2( 4, 8) }, // ImGuiMouseCursor_TextInput + { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll + { ImVec2(21,0), ImVec2( 9,23), ImVec2( 5,11) }, // ImGuiMouseCursor_ResizeNS + { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 5) }, // ImGuiMouseCursor_ResizeEW + { ImVec2(73,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNESW + { ImVec2(55,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNWSE +}; + +ImFontAtlas::ImFontAtlas() +{ + Flags = 0x00; + TexID = NULL; + TexDesiredWidth = 0; + TexGlyphPadding = 1; + + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; + TexWidth = TexHeight = 0; + TexUvScale = ImVec2(0.0f, 0.0f); + TexUvWhitePixel = ImVec2(0.0f, 0.0f); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + for (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) + { + ImGui::MemFree(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) + { + Fonts[i]->ConfigData = NULL; + Fonts[i]->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +void ImFontAtlas::ClearTexData() +{ + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + +void ImFontAtlas::ClearFonts() +{ + for (int i = 0; i < Fonts.Size; i++) + IM_DELETE(Fonts[i]); + Fonts.clear(); +} + +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Build atlas on demand + if (TexPixelsAlpha8 == NULL) + { + if (ConfigData.empty()) + AddFontDefault(); + Build(); + } + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Convert to RGBA32 format on demand + // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels = NULL; + GetTexDataAsAlpha8(&pixels, NULL, NULL); + if (pixels) + { + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + } + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +{ + IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT(font_cfg->SizePixels > 0.0f); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + + ConfigData.push_back(*font_cfg); + ImFontConfig& new_font_cfg = ConfigData.back(); + if (!new_font_cfg.DstFont) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) + { + new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize); + new_font_cfg.FontDataOwnedByAtlas = true; + memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + } + + // Invalidate texture + ClearTexData(); + return new_font_cfg.DstFont; +} + +// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) +static unsigned int stb_decompress_length(unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); +static const char* GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } +static void Decode85(const unsigned char* src, unsigned char* dst) +{ + while (*src) + { + unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px"); + if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f; + + const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, GetGlyphRangesDefault()); + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + int data_size = 0; + void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); + if (!data) + { + IM_ASSERT(0); // Could not load file. + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') + { + // Store a short copy of filename into into the font name for convenience + const char* p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + snprintf(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges); +} + +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels; + if (glyph_ranges) + font_cfg.GlyphRanges = glyph_ranges; + return AddFont(&font_cfg); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + const unsigned int buf_decompressed_size = stb_decompress_length((unsigned char*)compressed_ttf_data); + unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed_data, (unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontDataOwnedByAtlas = true; + return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) +{ + int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size); + Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); + ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + ImGui::MemFree(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) +{ + IM_ASSERT(id >= 0x10000); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) +{ + IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed + *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); + *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); +} + +bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) +{ + if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_Count_) + return false; + if (Flags & ImFontAtlasFlags_NoMouseCursors) + return false; + + ImFontAtlas::CustomRect& r = CustomRects[CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r.X, (float)r.Y); + ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; + *out_size = size; + *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; + out_uv_border[0] = (pos) * TexUvScale; + out_uv_border[1] = (pos + size) * TexUvScale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + out_uv_fill[0] = (pos) * TexUvScale; + out_uv_fill[1] = (pos + size) * TexUvScale; + return true; +} + +bool ImFontAtlas::Build() +{ + return ImFontAtlasBuildWithStbTruetype(this); +} + +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + +bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->ConfigData.Size > 0); + + ImFontAtlasBuildRegisterDefaultCustomRects(atlas); + + atlas->TexID = NULL; + atlas->TexWidth = atlas->TexHeight = 0; + atlas->TexUvScale = ImVec2(0.0f, 0.0f); + atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); + atlas->ClearTexData(); + + // Count glyphs/ranges + int total_glyphs_count = 0; + int total_ranges_count = 0; + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + if (!cfg.GlyphRanges) + cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++) + total_glyphs_count += (in_range[1] - in_range[0]) + 1; + } + + // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. + // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. + atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; + atlas->TexHeight = 0; + + // Start packing + const int max_tex_height = 1024*32; + stbtt_pack_context spc = {}; + if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL)) + return false; + stbtt_PackSetOversampling(&spc, 1, 1); + + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + + // Initialize font information (so we can error without any cleanup) + struct ImFontTempBuildData + { + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + int RectsCount; + stbtt_pack_range* Ranges; + int RangesCount; + }; + ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData)); + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0); + if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + { + atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure + ImGui::MemFree(tmp_array); + return false; + } + } + + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; + stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); + stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); + stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); + memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); + memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. + memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); + + // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + + // Setup ranges + int font_glyphs_count = 0; + int font_ranges_count = 0; + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) + font_glyphs_count += (in_range[1] - in_range[0]) + 1; + tmp.Ranges = buf_ranges + buf_ranges_n; + tmp.RangesCount = font_ranges_count; + buf_ranges_n += font_ranges_count; + for (int i = 0; i < font_ranges_count; i++) + { + const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; + stbtt_pack_range& range = tmp.Ranges[i]; + range.font_size = cfg.SizePixels; + range.first_unicode_codepoint_in_range = in_range[0]; + range.num_chars = (in_range[1] - in_range[0]) + 1; + range.chardata_for_range = buf_packedchars + buf_packedchars_n; + buf_packedchars_n += range.num_chars; + } + + // Pack + tmp.Rects = buf_rects + buf_rects_n; + tmp.RectsCount = font_glyphs_count; + buf_rects_n += font_glyphs_count; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + IM_ASSERT(n == font_glyphs_count); + stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n); + + // Extend texture height + for (int i = 0; i < n; i++) + if (tmp.Rects[i].was_packed) + atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h); + } + IM_ASSERT(buf_rects_n == total_glyphs_count); + IM_ASSERT(buf_packedchars_n == total_glyphs_count); + IM_ASSERT(buf_ranges_n == total_ranges_count); + + // Create texture + atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); + atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); + memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + spc.pixels = atlas->TexPixelsAlpha8; + spc.height = atlas->TexHeight; + + // Second pass: render font characters + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); + } + tmp.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + ImGui::MemFree(buf_rects); + buf_rects = NULL; + + // Third pass: setup ImFont and glyphs for runtime + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) + + const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = unscaled_ascent * font_scale; + const float descent = unscaled_descent * font_scale; + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float off_x = cfg.GlyphOffset.x; + const float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); + + for (int i = 0; i < tmp.RangesCount; i++) + { + stbtt_pack_range& range = tmp.Ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) + { + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + const int codepoint = range.first_unicode_codepoint_in_range + char_idx; + if (cfg.MergeMode && dst_font->FindGlyph((unsigned short)codepoint)) + continue; + + stbtt_aligned_quad q; + float dummy_x = 0.0f, dummy_y = 0.0f; + stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); + dst_font->AddGlyph((ImWchar)codepoint, q.x0 + off_x, q.y0 + off_y, q.x1 + off_x, q.y1 + off_y, q.s0, q.t0, q.s1, q.t1, pc.xadvance); + } + } + } + + // Cleanup temporaries + ImGui::MemFree(buf_packedchars); + ImGui::MemFree(buf_ranges); + ImGui::MemFree(tmp_array); + + ImFontAtlasBuildFinish(atlas); + + return true; +} + +void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) +{ + if (atlas->CustomRectIds[0] >= 0) + return; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + else + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, 2, 2); +} + +void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) + { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) +{ + stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; + + ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + + ImVector pack_rects; + pack_rects.resize(user_rects.Size); + memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); + for (int i = 0; i < user_rects.Size; i++) + { + pack_rects[i].w = user_rects[i].Width; + pack_rects[i].h = user_rects[i].Height; + } + stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); + for (int i = 0; i < pack_rects.Size; i++) + if (pack_rects[i].was_packed) + { + user_rects[i].X = pack_rects[i].x; + user_rects[i].Y = pack_rects[i].y; + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + } +} + +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->CustomRectIds[0] >= 0); + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + IM_ASSERT(r.IsPacked()); + + const int w = atlas->TexWidth; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + { + // Render/copy pixels + IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * 2 + 1 && r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++) + for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++) + { + const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * w; + const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00; + atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00; + } + } + else + { + IM_ASSERT(r.Width == 2 && r.Height == 2); + const int offset = (int)(r.X) + (int)(r.Y) * w; + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + } + atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * atlas->TexUvScale.x, (r.Y + 0.5f) * atlas->TexUvScale.y); +} + +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data block + ImFontAtlasBuildRenderDefaultTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; + if (r.Font == NULL || r.ID > 0x10000) + continue; + + IM_ASSERT(r.Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + atlas->Fonts[i]->BuildLookupTable(); +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD79D, // Korean characters + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesChinese() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() +{ + // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. + // This encoding is designed to helps us reduce the source code size. + // FIXME: Source a list of the revised 2136 joyo kanji list from 2010 and rebuild this. + // The current list was sourced from http://theinstructionlimit.com/author/renaudbedardrenaudbedard/page/3 + // Note that you may use ImFontAtlas::GlyphRangesBuilder to create your own ranges, by merging existing ranges or adding new characters. + static const short offsets_from_0x4E00[] = + { + -1,0,1,3,0,0,0,0,1,0,5,1,1,0,7,4,6,10,0,1,9,9,7,1,3,19,1,10,7,1,0,1,0,5,1,0,6,4,2,6,0,0,12,6,8,0,3,5,0,1,0,9,0,0,8,1,1,3,4,5,13,0,0,8,2,17, + 4,3,1,1,9,6,0,0,0,2,1,3,2,22,1,9,11,1,13,1,3,12,0,5,9,2,0,6,12,5,3,12,4,1,2,16,1,1,4,6,5,3,0,6,13,15,5,12,8,14,0,0,6,15,3,6,0,18,8,1,6,14,1, + 5,4,12,24,3,13,12,10,24,0,0,0,1,0,1,1,2,9,10,2,2,0,0,3,3,1,0,3,8,0,3,2,4,4,1,6,11,10,14,6,15,3,4,15,1,0,0,5,2,2,0,0,1,6,5,5,6,0,3,6,5,0,0,1,0, + 11,2,2,8,4,7,0,10,0,1,2,17,19,3,0,2,5,0,6,2,4,4,6,1,1,11,2,0,3,1,2,1,2,10,7,6,3,16,0,8,24,0,0,3,1,1,3,0,1,6,0,0,0,2,0,1,5,15,0,1,0,0,2,11,19, + 1,4,19,7,6,5,1,0,0,0,0,5,1,0,1,9,0,0,5,0,2,0,1,0,3,0,11,3,0,2,0,0,0,0,0,9,3,6,4,12,0,14,0,0,29,10,8,0,14,37,13,0,31,16,19,0,8,30,1,20,8,3,48, + 21,1,0,12,0,10,44,34,42,54,11,18,82,0,2,1,2,12,1,0,6,2,17,2,12,7,0,7,17,4,2,6,24,23,8,23,39,2,16,23,1,0,5,1,2,15,14,5,6,2,11,0,8,6,2,2,2,14, + 20,4,15,3,4,11,10,10,2,5,2,1,30,2,1,0,0,22,5,5,0,3,1,5,4,1,0,0,2,2,21,1,5,1,2,16,2,1,3,4,0,8,4,0,0,5,14,11,2,16,1,13,1,7,0,22,15,3,1,22,7,14, + 22,19,11,24,18,46,10,20,64,45,3,2,0,4,5,0,1,4,25,1,0,0,2,10,0,0,0,1,0,1,2,0,0,9,1,2,0,0,0,2,5,2,1,1,5,5,8,1,1,1,5,1,4,9,1,3,0,1,0,1,1,2,0,0, + 2,0,1,8,22,8,1,0,0,0,0,4,2,1,0,9,8,5,0,9,1,30,24,2,6,4,39,0,14,5,16,6,26,179,0,2,1,1,0,0,0,5,2,9,6,0,2,5,16,7,5,1,1,0,2,4,4,7,15,13,14,0,0, + 3,0,1,0,0,0,2,1,6,4,5,1,4,9,0,3,1,8,0,0,10,5,0,43,0,2,6,8,4,0,2,0,0,9,6,0,9,3,1,6,20,14,6,1,4,0,7,2,3,0,2,0,5,0,3,1,0,3,9,7,0,3,4,0,4,9,1,6,0, + 9,0,0,2,3,10,9,28,3,6,2,4,1,2,32,4,1,18,2,0,3,1,5,30,10,0,2,2,2,0,7,9,8,11,10,11,7,2,13,7,5,10,0,3,40,2,0,1,6,12,0,4,5,1,5,11,11,21,4,8,3,7, + 8,8,33,5,23,0,0,19,8,8,2,3,0,6,1,1,1,5,1,27,4,2,5,0,3,5,6,3,1,0,3,1,12,5,3,3,2,0,7,7,2,1,0,4,0,1,1,2,0,10,10,6,2,5,9,7,5,15,15,21,6,11,5,20, + 4,3,5,5,2,5,0,2,1,0,1,7,28,0,9,0,5,12,5,5,18,30,0,12,3,3,21,16,25,32,9,3,14,11,24,5,66,9,1,2,0,5,9,1,5,1,8,0,8,3,3,0,1,15,1,4,8,1,2,7,0,7,2, + 8,3,7,5,3,7,10,2,1,0,0,2,25,0,6,4,0,10,0,4,2,4,1,12,5,38,4,0,4,1,10,5,9,4,0,14,4,2,5,18,20,21,1,3,0,5,0,7,0,3,7,1,3,1,1,8,1,0,0,0,3,2,5,2,11, + 6,0,13,1,3,9,1,12,0,16,6,2,1,0,2,1,12,6,13,11,2,0,28,1,7,8,14,13,8,13,0,2,0,5,4,8,10,2,37,42,19,6,6,7,4,14,11,18,14,80,7,6,0,4,72,12,36,27, + 7,7,0,14,17,19,164,27,0,5,10,7,3,13,6,14,0,2,2,5,3,0,6,13,0,0,10,29,0,4,0,3,13,0,3,1,6,51,1,5,28,2,0,8,0,20,2,4,0,25,2,10,13,10,0,16,4,0,1,0, + 2,1,7,0,1,8,11,0,0,1,2,7,2,23,11,6,6,4,16,2,2,2,0,22,9,3,3,5,2,0,15,16,21,2,9,20,15,15,5,3,9,1,0,0,1,7,7,5,4,2,2,2,38,24,14,0,0,15,5,6,24,14, + 5,5,11,0,21,12,0,3,8,4,11,1,8,0,11,27,7,2,4,9,21,59,0,1,39,3,60,62,3,0,12,11,0,3,30,11,0,13,88,4,15,5,28,13,1,4,48,17,17,4,28,32,46,0,16,0, + 18,11,1,8,6,38,11,2,6,11,38,2,0,45,3,11,2,7,8,4,30,14,17,2,1,1,65,18,12,16,4,2,45,123,12,56,33,1,4,3,4,7,0,0,0,3,2,0,16,4,2,4,2,0,7,4,5,2,26, + 2,25,6,11,6,1,16,2,6,17,77,15,3,35,0,1,0,5,1,0,38,16,6,3,12,3,3,3,0,9,3,1,3,5,2,9,0,18,0,25,1,3,32,1,72,46,6,2,7,1,3,14,17,0,28,1,40,13,0,20, + 15,40,6,38,24,12,43,1,1,9,0,12,6,0,6,2,4,19,3,7,1,48,0,9,5,0,5,6,9,6,10,15,2,11,19,3,9,2,0,1,10,1,27,8,1,3,6,1,14,0,26,0,27,16,3,4,9,6,2,23, + 9,10,5,25,2,1,6,1,1,48,15,9,15,14,3,4,26,60,29,13,37,21,1,6,4,0,2,11,22,23,16,16,2,2,1,3,0,5,1,6,4,0,0,4,0,0,8,3,0,2,5,0,7,1,7,3,13,2,4,10, + 3,0,2,31,0,18,3,0,12,10,4,1,0,7,5,7,0,5,4,12,2,22,10,4,2,15,2,8,9,0,23,2,197,51,3,1,1,4,13,4,3,21,4,19,3,10,5,40,0,4,1,1,10,4,1,27,34,7,21, + 2,17,2,9,6,4,2,3,0,4,2,7,8,2,5,1,15,21,3,4,4,2,2,17,22,1,5,22,4,26,7,0,32,1,11,42,15,4,1,2,5,0,19,3,1,8,6,0,10,1,9,2,13,30,8,2,24,17,19,1,4, + 4,25,13,0,10,16,11,39,18,8,5,30,82,1,6,8,18,77,11,13,20,75,11,112,78,33,3,0,0,60,17,84,9,1,1,12,30,10,49,5,32,158,178,5,5,6,3,3,1,3,1,4,7,6, + 19,31,21,0,2,9,5,6,27,4,9,8,1,76,18,12,1,4,0,3,3,6,3,12,2,8,30,16,2,25,1,5,5,4,3,0,6,10,2,3,1,0,5,1,19,3,0,8,1,5,2,6,0,0,0,19,1,2,0,5,1,2,5, + 1,3,7,0,4,12,7,3,10,22,0,9,5,1,0,2,20,1,1,3,23,30,3,9,9,1,4,191,14,3,15,6,8,50,0,1,0,0,4,0,0,1,0,2,4,2,0,2,3,0,2,0,2,2,8,7,0,1,1,1,3,3,17,11, + 91,1,9,3,2,13,4,24,15,41,3,13,3,1,20,4,125,29,30,1,0,4,12,2,21,4,5,5,19,11,0,13,11,86,2,18,0,7,1,8,8,2,2,22,1,2,6,5,2,0,1,2,8,0,2,0,5,2,1,0, + 2,10,2,0,5,9,2,1,2,0,1,0,4,0,0,10,2,5,3,0,6,1,0,1,4,4,33,3,13,17,3,18,6,4,7,1,5,78,0,4,1,13,7,1,8,1,0,35,27,15,3,0,0,0,1,11,5,41,38,15,22,6, + 14,14,2,1,11,6,20,63,5,8,27,7,11,2,2,40,58,23,50,54,56,293,8,8,1,5,1,14,0,1,12,37,89,8,8,8,2,10,6,0,0,0,4,5,2,1,0,1,1,2,7,0,3,3,0,4,6,0,3,2, + 19,3,8,0,0,0,4,4,16,0,4,1,5,1,3,0,3,4,6,2,17,10,10,31,6,4,3,6,10,126,7,3,2,2,0,9,0,0,5,20,13,0,15,0,6,0,2,5,8,64,50,3,2,12,2,9,0,0,11,8,20, + 109,2,18,23,0,0,9,61,3,0,28,41,77,27,19,17,81,5,2,14,5,83,57,252,14,154,263,14,20,8,13,6,57,39,38, + }; + static ImWchar base_ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + }; + static bool full_ranges_unpacked = false; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(offsets_from_0x4E00)*2 + 1]; + if (!full_ranges_unpacked) + { + // Unpack + int codepoint = 0x4e00; + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + ImWchar* dst = full_ranges + IM_ARRAYSIZE(base_ranges);; + for (int n = 0; n < IM_ARRAYSIZE(offsets_from_0x4E00); n++, dst += 2) + dst[0] = dst[1] = (ImWchar)(codepoint += (offsets_from_0x4E00[n] + 1)); + dst[0] = 0; + full_ranges_unpacked = true; + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas::GlyphRangesBuilder +//----------------------------------------------------------------------------- + +void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end) +{ + while (text_end ? (text < text_end) : *text) + { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + if (c < 0x10000) + AddChar((ImWchar)c); + } +} + +void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges) +{ + for (; ranges[0]; ranges += 2) + for (ImWchar c = ranges[0]; c <= ranges[1]; c++) + AddChar(c); +} + +void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector* out_ranges) +{ + for (int n = 0; n < 0x10000; n++) + if (GetBit(n)) + { + out_ranges->push_back((ImWchar)n); + while (n < 0x10000 && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + Scale = 1.0f; + FallbackChar = (ImWchar)'?'; + DisplayOffset = ImVec2(0.0f, 1.0f); + ClearOutputData(); +} + +ImFont::~ImFont() +{ + // Invalidate active font so that the user gets a clear crash instead of a dangling pointer. + // If you want to delete fonts you need to do it between Render() and NewFrame(). + // FIXME-CLEANUP + /* + ImGuiContext& g = *GImGui; + if (g.Font == this) + g.Font = NULL; + */ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + FallbackAdvanceX = 0.0f; + ConfigDataCount = 0; + ConfigData = NULL; + ContainerAtlas = NULL; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +void ImFont::BuildLookupTable() +{ + int max_codepoint = 0; + for (int i = 0; i != Glyphs.Size; i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + + IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + GrowIndex(max_codepoint + 1); + for (int i = 0; i < Glyphs.Size; i++) + { + int codepoint = (int)Glyphs[i].Codepoint; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; + IndexLookup[codepoint] = (unsigned short)i; + } + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (FindGlyph((unsigned short)' ')) + { + if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& tab_glyph = Glyphs.back(); + tab_glyph = *FindGlyph((unsigned short)' '); + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX *= 4; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; + IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); + } + + FallbackGlyph = NULL; + FallbackGlyph = FindGlyph(FallbackChar); + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + for (int i = 0; i < max_codepoint + 1; i++) + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; +} + +void ImFont::SetFallbackChar(ImWchar c) +{ + FallbackChar = c; + BuildLookupTable(); +} + +void ImFont::GrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (unsigned short)-1); +} + +void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX + + if (ConfigData->PixelSnapH) + glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. + int index_size = IndexLookup.Size; + + if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists + return; + if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op + return; + + GrowIndex(dst + 1); + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +{ + if (c < IndexLookup.Size) + { + const unsigned short i = IndexLookup[c]; + if (i != (unsigned short)-1) + return &Glyphs.Data[i]; + } + return FallbackGlyph; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ + // Simple word-wrapping for English, not full-featured. Please submit failing cases! + // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + + // For references, possible wrap point marked with ^ + // "aaa bbb, ccc,ddd. eee fff. ggg!" + // ^ ^ ^ ^ ^__ ^ ^ + + // List of hardcoded separators: .,;!?'" + + // Skip extra blanks after a line returns (that includes not counting them in width computation) + // e.g. "Hello world" --> "Hello" "World" + + // Cut words that cannot possibly fit within one line. + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + float line_width = 0.0f; + float word_width = 0.0f; + float blank_width = 0.0f; + wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + + const char* word_end = text; + const char* prev_word_end = NULL; + bool inside_word = true; + + const char* s = text; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + const char* next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) + break; + + if (c < 32) + { + if (c == '\n') + { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') + { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); + if (ImCharIsSpace(c)) + { + if (inside_word) + { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } + else + { + word_width += char_width; + if (inside_word) + { + word_end = next_s; + } + else + { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width >= wrap_width) + { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + s = prev_word_end ? prev_word_end : word_end; + break; + } + + s = next_s; + } + + return s; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + const char* s = text_begin; + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + const char* prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; + if (line_width + char_width >= max_width) + { + s = prev_s; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const +{ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. + return; + if (const ImFontGlyph* glyph = FindGlyph(c)) + { + float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + draw_list->PrimReserve(6, 4); + draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + } +} + +void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + float x = pos.x; + float y = pos.y; + if (y > clip_rect.w) + return; + + const float scale = size / FontSize; + const float line_height = FontSize * scale; + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + // Skip non-visible lines + const char* s = text_begin; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int vtx_count_max = (int)(text_end - s) * 4; + const int idx_count_max = (int)(text_end - s) * 6; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + x = pos.x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + x = pos.x; + y += line_height; + + if (y > clip_rect.w) + break; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + continue; + } + if (c == '\r') + continue; + } + + float char_width = 0.0f; + if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) + { + char_width = glyph->AdvanceX * scale; + + // Arbitrarily assume that both space and tabs are empty glyphs as an optimization + if (c != ' ' && c != '\t') + { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) + { + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip) + { + if (x1 < clip_rect.x) + { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) + { + x += char_width; + continue; + } + } + + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); + vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; + vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + } + } + } + } + + x += char_width; + } + + // Give back unused vertices + draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); + draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; +} + +//----------------------------------------------------------------------------- +// Internals Drawing Helpers +//----------------------------------------------------------------------------- + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return acosf(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + +//----------------------------------------------------------------------------- +// DEFAULT FONT DATA +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array. +// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- + +static unsigned int stb_decompress_length(unsigned char *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +static unsigned char *stb__barrier, *stb__barrier2, *stb__barrier3, *stb__barrier4; +static unsigned char *stb__dout; +static void stb__match(unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(unsigned char *data, unsigned int length) +{ + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static unsigned char *stb_decompress_token(unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} + +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length) +{ + unsigned int olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier2 = i; + stb__barrier3 = i+length; + stb__barrier = output + olen; + stb__barrier4 = output; + i += 16; + + stb__dout = output; + for (;;) { + unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using binary_to_compressed_c.cpp +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980+1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char* GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} diff --git a/apps/MDL_sdf/imgui/imgui_impl_glfw_gl2.cpp b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl2.cpp new file mode 100644 index 00000000..381ff13a --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl2.cpp @@ -0,0 +1,355 @@ +// ImGui GLFW binding with OpenGL (legacy, fixed pipeline) +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in the opengl3_example/ folder** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-20: Inputs: Renamed GLFW callbacks exposed in .h to not include GL2 in their name. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL2_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-09: Misc: Renamed imgui_impl_glfw.* to imgui_impl_glfw_gl2.*. +// 2017-09-01: OpenGL: Save and restore current polygon mode. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. +// 2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal). +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. + +#include "imgui.h" +#include "imgui_impl_glfw_gl2.h" + +// GLFW +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WGL +#include +#endif + +// GLFW data +static GLFWwindow* g_Window = NULL; +static double g_Time = 0.0f; +static bool g_MouseJustPressed[3] = { false, false, false }; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_Count_] = { 0 }; + +// OpenGL data +static GLuint g_FontTexture = 0; + +// OpenGL2 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplGlfwGL2_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT | GL_TEXTURE_BIT); // DAR Was missing GL_TEXTURE_BIT. Need to store the glTexEnv setting as well. + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // DAR IMGUI uses GL_MODULATE when not using GLSL shaders. + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + + // Restore modified state + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplGlfwGL2_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfwGL2_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) +{ + if (action == GLFW_PRESS && button >= 0 && button < 3) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + (void)mods; // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) +{ + ImGuiIO& io = ImGui::GetIO(); + if (c > 0 && c < 0x10000) + io.AddInputCharacter((unsigned short)c); +} + +bool ImGui_ImplGlfwGL2_CreateDeviceObjects() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplGlfwGL2_InvalidateDeviceObjects() +{ + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +static void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) +{ + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); +} + +bool ImGui_ImplGlfwGL2_Init(GLFWwindow* window, bool install_callbacks) +{ + g_Window = window; + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.SetClipboardTextFn = ImGui_ImplGlfwGL2_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfwGL2_GetClipboardText; + io.ClipboardUserData = g_Window; +#ifdef _WIN32 + io.ImeWindowHandle = glfwGetWin32Window(g_Window); +#endif + + // Load cursors + // FIXME: GLFW doesn't expose suitable cursors for ResizeAll, ResizeNESW, ResizeNWSE. We revert to arrow cursor for those. + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + + if (install_callbacks) + ImGui_ImplGlfw_InstallCallbacks(window); + + return true; +} + +void ImGui_ImplGlfwGL2_Shutdown() +{ + // Destroy GLFW mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_Count_; cursor_n++) + glfwDestroyCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + + // Destroy OpenGL objects + ImGui_ImplGlfwGL2_InvalidateDeviceObjects(); +} + +void ImGui_ImplGlfwGL2_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplGlfwGL2_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) + { + if (io.WantMoveMouse) + { + glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y); // Set mouse position if requested by io.WantMoveMouse flag (used when io.NavMovesTrue is enabled by user and using directional navigation) + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } + else + { + io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX); + } + + for (int i = 0; i < 3; i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update OS/hardware mouse cursor if imgui isn't drawing a software cursor + ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || cursor == ImGuiMouseCursor_None) + { + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + glfwSetCursor(g_Window, g_MouseCursors[cursor] ? g_MouseCursors[cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. + ImGui::NewFrame(); +} diff --git a/apps/MDL_sdf/imgui/imgui_impl_glfw_gl2.h b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl2.h new file mode 100644 index 00000000..f7b5c22c --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl2.h @@ -0,0 +1,32 @@ +// ImGui GLFW binding with OpenGL (legacy, fixed pipeline) +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in the opengl3_example/ folder** +// See imgui_impl_glfw_gl2.cpp for details. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct GLFWwindow; + +IMGUI_API bool ImGui_ImplGlfwGL2_Init(GLFWwindow* window, bool install_callbacks); +IMGUI_API void ImGui_ImplGlfwGL2_Shutdown(); +IMGUI_API void ImGui_ImplGlfwGL2_NewFrame(); +IMGUI_API void ImGui_ImplGlfwGL2_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplGlfwGL2_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplGlfwGL2_CreateDeviceObjects(); + +// GLFW callbacks (registered by default to GLFW if you enable 'install_callbacks' during initialization) +// Provided here if you want to chain callbacks yourself. You may also handle inputs yourself and use those as a reference. +IMGUI_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/apps/MDL_sdf/imgui/imgui_impl_glfw_gl3.cpp b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl3.cpp new file mode 100644 index 00000000..b501c2fc --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl3.cpp @@ -0,0 +1,501 @@ +// ImGui GLFW binding with OpenGL3 + shaders +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (GL3W is a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Gamepad navigation mapping. Enable with 'io.NavFlags |= ImGuiNavFlags_EnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-20: Inputs: Renamed GLFW callbacks exposed in .h to not include GL3 in their name. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Added gamepad support if ImGuiNavFlags_EnableGamepad is set. +// 2018-01-25: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. (Also changed GL context from 3.3 to 3.2 in example's main.cpp) +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2017-05-01: OpenGL: Fixed save and restore of current blend function state. +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-04-30: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. + +#include "imgui.h" +#include "imgui_impl_glfw_gl3.h" + +// GL3W/GLFW +//#include // This example is using gl3w to access OpenGL functions (because it is small). You may use glew/glad/glLoadGen/etc. whatever already works for you. +// DAR Yes, using GLEW here. +#include +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WGL +#include +#endif + +// GLFW data +static GLFWwindow* g_Window = NULL; +static double g_Time = 0.0f; +static bool g_MouseJustPressed[3] = { false, false, false }; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_Count_] = { 0 }; + +// OpenGL3 data +static GLuint g_FontTexture = 0; +static int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; +static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0; +static unsigned int g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0; + +// OpenGL3 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplGlfwGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); + GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); + GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); + + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + const float ortho_projection[4][4] = + { + { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + {-1.0f, 1.0f, 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + glBindVertexArray(g_VaoHandle); + glBindSampler(0, 0); // Rely on combined texture/sampler state. + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawIdx* idx_buffer_offset = 0; + + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + } + idx_buffer_offset += pcmd->ElemCount; + } + } + + // Restore modified GL state + glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindSampler(0, last_sampler); + glActiveTexture(last_active_texture); + glBindVertexArray(last_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplGlfwGL3_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfwGL3_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) +{ + if (action == GLFW_PRESS && button >= 0 && button < 3) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + (void)mods; // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) +{ + ImGuiIO& io = ImGui::GetIO(); + if (c > 0 && c < 0x10000) + io.AddInputCharacter((unsigned short)c); +} + +bool ImGui_ImplGlfwGL3_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +bool ImGui_ImplGlfwGL3_CreateDeviceObjects() +{ + // Backup GL state + GLint last_texture, last_array_buffer, last_vertex_array; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + + const GLchar *vertex_shader = + "#version 150\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader = + "#version 150\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" + "}\n"; + + g_ShaderHandle = glCreateProgram(); + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_VertHandle, 1, &vertex_shader, 0); + glShaderSource(g_FragHandle, 1, &fragment_shader, 0); + glCompileShader(g_VertHandle); + glCompileShader(g_FragHandle); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + glGenBuffers(1, &g_VboHandle); + glGenBuffers(1, &g_ElementsHandle); + + glGenVertexArrays(1, &g_VaoHandle); + glBindVertexArray(g_VaoHandle); + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glEnableVertexAttribArray(g_AttribLocationPosition); + glEnableVertexAttribArray(g_AttribLocationUV); + glEnableVertexAttribArray(g_AttribLocationColor); + + glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); + + ImGui_ImplGlfwGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindVertexArray(last_vertex_array); + + return true; +} + +void ImGui_ImplGlfwGL3_InvalidateDeviceObjects() +{ + if (g_VaoHandle) glDeleteVertexArrays(1, &g_VaoHandle); + if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle); + if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle); + g_VaoHandle = g_VboHandle = g_ElementsHandle = 0; + + if (g_ShaderHandle && g_VertHandle) glDetachShader(g_ShaderHandle, g_VertHandle); + if (g_VertHandle) glDeleteShader(g_VertHandle); + g_VertHandle = 0; + + if (g_ShaderHandle && g_FragHandle) glDetachShader(g_ShaderHandle, g_FragHandle); + if (g_FragHandle) glDeleteShader(g_FragHandle); + g_FragHandle = 0; + + if (g_ShaderHandle) glDeleteProgram(g_ShaderHandle); + g_ShaderHandle = 0; + + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +static void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) +{ + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); +} + +bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks) +{ + g_Window = window; + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.SetClipboardTextFn = ImGui_ImplGlfwGL3_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfwGL3_GetClipboardText; + io.ClipboardUserData = g_Window; +#ifdef _WIN32 + io.ImeWindowHandle = glfwGetWin32Window(g_Window); +#endif + + // Load cursors + // FIXME: GLFW doesn't expose suitable cursors for ResizeAll, ResizeNESW, ResizeNWSE. We revert to arrow cursor for those. + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + + if (install_callbacks) + ImGui_ImplGlfw_InstallCallbacks(window); + + return true; +} + +void ImGui_ImplGlfwGL3_Shutdown() +{ + // Destroy GLFW mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_Count_; cursor_n++) + glfwDestroyCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + + // Destroy OpenGL objects + ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); +} + +void ImGui_ImplGlfwGL3_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplGlfwGL3_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) + { + if (io.WantMoveMouse) + { + glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y); // Set mouse position if requested by io.WantMoveMouse flag (used when io.NavMovesTrue is enabled by user and using directional navigation) + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } + else + { + io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX); + } + + for (int i = 0; i < 3; i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update OS/hardware mouse cursor if imgui isn't drawing a software cursor + ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || cursor == ImGuiMouseCursor_None) + { + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + glfwSetCursor(g_Window, g_MouseCursors[cursor] ? g_MouseCursors[cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + // Gamepad navigation mapping [BETA] + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if (io.NavFlags & ImGuiNavFlags_EnableGamepad) + { + // Update gamepad inputs + #define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; } + #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; } + int axes_count = 0, buttons_count = 0; + const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + MAP_BUTTON(ImGuiNavInput_Activate, 0); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, 1); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, 2); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, 3); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, 13); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, 11); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, 10); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, 12); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, 5); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, 0, -0.3f, -0.9f); + MAP_ANALOG(ImGuiNavInput_LStickRight,0, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickUp, 1, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickDown, 1, -0.3f, -0.9f); + #undef MAP_BUTTON + #undef MAP_ANALOG + } + + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. + ImGui::NewFrame(); +} diff --git a/apps/MDL_sdf/imgui/imgui_impl_glfw_gl3.h b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl3.h new file mode 100644 index 00000000..71ea4122 --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_impl_glfw_gl3.h @@ -0,0 +1,31 @@ +// ImGui GLFW binding with OpenGL3 + shaders +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (GL3W is a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Gamepad navigation mapping. Enable with 'io.NavFlags |= ImGuiNavFlags_EnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct GLFWwindow; + +IMGUI_API bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks); +IMGUI_API void ImGui_ImplGlfwGL3_Shutdown(); +IMGUI_API void ImGui_ImplGlfwGL3_NewFrame(); +IMGUI_API void ImGui_ImplGlfwGL3_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplGlfwGL3_CreateDeviceObjects(); + +// GLFW callbacks (installed by default if you enable 'install_callbacks' during initialization) +// Provided here if you want to chain callbacks. +// You can also handle inputs yourself and use those as a reference. +IMGUI_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/apps/MDL_sdf/imgui/imgui_internal.h b/apps/MDL_sdf/imgui/imgui_internal.h new file mode 100644 index 00000000..77d621bb --- /dev/null +++ b/apps/MDL_sdf/imgui/imgui_internal.h @@ -0,0 +1,1156 @@ +// dear imgui, v1.60 WIP +// (internals) + +// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! +// Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +#pragma once + +#ifndef IMGUI_VERSION +#error Must include imgui.h before imgui_internal.h +#endif + +#include // FILE* +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf +#include // INT_MIN, INT_MAX + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +//----------------------------------------------------------------------------- +// Forward Declarations +//----------------------------------------------------------------------------- + +struct ImRect; +struct ImGuiColMod; +struct ImGuiStyleMod; +struct ImGuiGroupData; +struct ImGuiMenuColumns; +struct ImGuiDrawContext; +struct ImGuiTextEditState; +struct ImGuiPopupRef; +struct ImGuiWindow; +struct ImGuiWindowSettings; + +typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_ +typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_ +typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_ +typedef int ImGuiItemStatusFlags; // flags: storage for DC.LastItemXXX // enum ImGuiItemStatusFlags_ +typedef int ImGuiNavHighlightFlags; // flags: for RenderNavHighlight() // enum ImGuiNavHighlightFlags_ +typedef int ImGuiNavDirSourceFlags; // flags: for GetNavInputAmount2d() // enum ImGuiNavDirSourceFlags_ +typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_ +typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_ + +//------------------------------------------------------------------------- +// STB libraries +//------------------------------------------------------------------------- + +namespace ImGuiStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiTextEditState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f +#include "stb_textedit.h" + +} // namespace ImGuiStb + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +#ifndef GImGui +extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +#define IM_PI 3.14159265358979323846f + +// Helpers: UTF-8 <> wchar +IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string as UTF-8 code-points + +// Helpers: Misc +IMGUI_API ImU32 ImHash(const void* data, int data_size, ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size = NULL, int padding_bytes = 0); +IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); +static inline bool ImCharIsSpace(int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } + +// Helpers: Geometry +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); + +// Helpers: String +IMGUI_API int ImStricmp(const char* str1, const char* str2); +IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); +IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); +IMGUI_API char* ImStrdup(const char* str); +IMGUI_API char* ImStrchrRange(const char* str_begin, const char* str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar* str); +IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line +IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); +IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); + +// Helpers: Math +// We are keeping those not leaking to the user by default, in the case the user has implicit cast operators between ImVec2 and its own types (when IM_VEC2_CLASS_EXTRA is defined) +#ifdef IMGUI_DEFINE_MATH_OPERATORS +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x/rhs, lhs.y/rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x*rhs.x, lhs.y*rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x/rhs.x, lhs.y/rhs.y); } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); } +#endif + +static inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } +static inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } +static inline float ImMin(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; } +static inline float ImMax(float lhs, float rhs) { return lhs >= rhs ? lhs : rhs; } +static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +static inline int ImClamp(int v, int mn, int mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline ImVec2 ImClamp(const ImVec2& f, const ImVec2& mn, ImVec2 mx) { return ImVec2(ImClamp(f.x,mn.x,mx.x), ImClamp(f.y,mn.y,mx.y)); } +static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline void ImSwap(int& a, int& b) { int tmp = a; a = b; b = tmp; } +static inline void ImSwap(float& a, float& b) { float tmp = a; a = b; b = tmp; } +static inline int ImLerp(int a, int b, float t) { return (int)(a + (b - a) * t); } +static inline float ImLerp(float a, float b, float t) { return a + (b - a) * t; } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } +static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / sqrtf(d); return fail_value; } +static inline float ImFloor(float f) { return (float)(int)f; } +static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } +static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + +// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. +// Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. +struct ImNewPlacementDummy {}; +inline void* operator new(size_t, ImNewPlacementDummy, void* ptr) { return ptr; } +inline void operator delete(void*, ImNewPlacementDummy, void*) {} // This is only required so we can use the symetrical new() +#define IM_PLACEMENT_NEW(_PTR) new(ImNewPlacementDummy(), _PTR) +#define IM_NEW(_TYPE) new(ImNewPlacementDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +template void IM_DELETE(T*& p) { if (p) { p->~T(); ImGui::MemFree(p); p = NULL; } } + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +enum ImGuiButtonFlags_ +{ + ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat + ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] + ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) + ImGuiButtonFlags_FlattenChildren = 1 << 5, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowItemOverlap = 1 << 6, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() + ImGuiButtonFlags_DontClosePopups = 1 << 7, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 8, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveID = 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12, // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) + ImGuiButtonFlags_NoNavFocus = 1 << 13 // don't override navigation focus when activated +}; + +enum ImGuiSliderFlags_ +{ + ImGuiSliderFlags_Vertical = 1 << 0 +}; + +enum ImGuiColumnsFlags_ +{ + // Default: 0 + ImGuiColumnsFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiColumnsFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + ImGuiColumnsFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns + ImGuiColumnsFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window + ImGuiColumnsFlags_GrowParentContentsSize= 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. +}; + +enum ImGuiSelectableFlagsPrivate_ +{ + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_Menu = 1 << 3, // -> PressedOnClick + ImGuiSelectableFlags_MenuItem = 1 << 4, // -> PressedOnRelease + ImGuiSelectableFlags_Disabled = 1 << 5, + ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 6 +}; + +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1 +}; + +// Storage for LastItem data +enum ImGuiItemStatusFlags_ +{ + ImGuiItemStatusFlags_HoveredRect = 1 << 0, + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1 +}; + +// FIXME: this is in development, not exposed/functional as a generic feature yet. +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Vertical, + ImGuiLayoutType_Horizontal +}; + +enum ImGuiAxis +{ + ImGuiAxis_None = -1, + ImGuiAxis_X = 0, + ImGuiAxis_Y = 1 +}; + +enum ImGuiPlotType +{ + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram +}; + +enum ImGuiDataType +{ + ImGuiDataType_Int, + ImGuiDataType_Float, + ImGuiDataType_Float2 +}; + +enum ImGuiDir +{ + ImGuiDir_None = -1, + ImGuiDir_Left = 0, + ImGuiDir_Right = 1, + ImGuiDir_Up = 2, + ImGuiDir_Down = 3, + ImGuiDir_Count_ +}; + +enum ImGuiInputSource +{ + ImGuiInputSource_None = 0, + ImGuiInputSource_Mouse, + ImGuiInputSource_Nav, + ImGuiInputSource_NavKeyboard, // Only used occasionally for storage, not tested/handled by most code + ImGuiInputSource_NavGamepad, // " + ImGuiInputSource_Count_, +}; + +// FIXME-NAV: Clarify/expose various repeat delay/rate +enum ImGuiInputReadMode +{ + ImGuiInputReadMode_Down, + ImGuiInputReadMode_Pressed, + ImGuiInputReadMode_Released, + ImGuiInputReadMode_Repeat, + ImGuiInputReadMode_RepeatSlow, + ImGuiInputReadMode_RepeatFast +}; + +enum ImGuiNavHighlightFlags_ +{ + ImGuiNavHighlightFlags_TypeDefault = 1 << 0, + ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, + ImGuiNavHighlightFlags_NoRounding = 1 << 3 +}; + +enum ImGuiNavDirSourceFlags_ +{ + ImGuiNavDirSourceFlags_Keyboard = 1 << 0, + ImGuiNavDirSourceFlags_PadDPad = 1 << 1, + ImGuiNavDirSourceFlags_PadLStick = 1 << 2 +}; + +enum ImGuiNavForward +{ + ImGuiNavForward_None, + ImGuiNavForward_ForwardQueued, + ImGuiNavForward_ForwardActive +}; + +// 2D axis aligned bounding-box +// NB: we can't rely on ImVec2 math operators being available here +struct IMGUI_API ImRect +{ + ImVec2 Min; // Upper-left + ImVec2 Max; // Lower-right + + ImRect() : Min(FLT_MAX,FLT_MAX), Max(-FLT_MAX,-FLT_MAX) {} + ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + + ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } + ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } + float GetWidth() const { return Max.x - Min.x; } + float GetHeight() const { return Max.y - Min.y; } + ImVec2 GetTL() const { return Min; } // Top-left + ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right + ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left + ImVec2 GetBR() const { return Max; } // Bottom-right + bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } + bool Contains(const ImRect& r) const { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; } + bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } + void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } + void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } + void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } + void Translate(const ImVec2& v) { Min.x += v.x; Min.y += v.y; Max.x += v.x; Max.y += v.y; } + void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. + void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. + void Floor() { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; } + void FixInverted() { if (Min.x > Max.x) ImSwap(Min.x, Max.x); if (Min.y > Max.y) ImSwap(Min.y, Max.y); } + bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } + bool IsFinite() const { return Min.x != FLT_MAX; } +}; + +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +// Stacked data for BeginGroup()/EndGroup() +struct ImGuiGroupData +{ + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + float BackupIndentX; + float BackupGroupOffsetX; + float BackupCurrentLineHeight; + float BackupCurrentLineTextBaseOffset; + float BackupLogLinePosY; + bool BackupActiveIdIsAlive; + bool AdvanceCursor; +}; + +// Simple column measurement currently used for MenuItem() only. This is very short-sighted/throw-away code and NOT a generic helper. +struct IMGUI_API ImGuiMenuColumns +{ + int Count; + float Spacing; + float Width, NextWidth; + float Pos[4], NextWidths[4]; + + ImGuiMenuColumns(); + void Update(int count, float spacing, bool clear); + float DeclColumns(float w0, float w1, float w2); + float CalcExtraSpace(float avail_w); +}; + +// Internal state of the currently focused/edited text input box +struct IMGUI_API ImGuiTextEditState +{ + ImGuiID Id; // widget id owning the text state + ImVector Text; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempTextBuffer; + int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. + int BufSizeA; // end-user buffer size + float ScrollX; + ImGuiStb::STB_TexteditState StbState; + float CursorAnim; + bool CursorFollow; + bool SelectedAllMouseLock; + + ImGuiTextEditState() { memset(this, 0, sizeof(*this)); } + void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); } + bool HasSelection() const { return StbState.select_start != StbState.select_end; } + void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } + void SelectAll() { StbState.select_start = 0; StbState.cursor = StbState.select_end = CurLenW; StbState.has_preferred_x = false; } + void OnKeyPressed(int key); +}; + +// Data saved in imgui.ini file +struct ImGuiWindowSettings +{ + char* Name; + ImGuiID Id; + ImVec2 Pos; + ImVec2 Size; + bool Collapsed; + + ImGuiWindowSettings() { Name = NULL; Id = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } +}; + +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) + void* (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name); + void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); + void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); + void* UserData; + + ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } +}; + +// Storage for current popup stack +struct ImGuiPopupRef +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* ParentWindow; // Set on OpenPopup() + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differenciate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup +}; + +struct ImGuiColumnData +{ + float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) + float OffsetNormBeforeResize; + ImGuiColumnsFlags Flags; // Not exposed + ImRect ClipRect; + + ImGuiColumnData() { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = 0; } +}; + +struct ImGuiColumnsSet +{ + ImGuiID ID; + ImGuiColumnsFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float MinX, MaxX; + float StartPosY; + float StartMaxPosX; // Backup of CursorMaxPos + float CellMinY, CellMaxY; + ImVector Columns; + + ImGuiColumnsSet() { Clear(); } + void Clear() + { + ID = 0; + Flags = 0; + IsFirstFrame = false; + IsBeingResized = false; + Current = 0; + Count = 1; + MinX = MaxX = 0.0f; + StartPosY = 0.0f; + StartMaxPosX = 0.0f; + CellMinY = CellMaxY = 0.0f; + Columns.clear(); + } +}; + +struct IMGUI_API ImDrawListSharedData +{ + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + ImFont* Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + + // Const data + // FIXME: Bake rounded corners fill/borders in atlas + ImVec2 CircleVtx12[12]; + + ImDrawListSharedData(); +}; + +struct ImDrawDataBuilder +{ + ImVector Layers[2]; // Global layers for: regular, tooltip + + void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); } + void ClearFreeMemory() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); } + IMGUI_API void FlattenIntoSingleLayer(); +}; + +struct ImGuiNavMoveResult +{ + ImGuiID ID; // Best candidate + ImGuiID ParentID; // Best candidate window->IDStack.back() - to compare context + ImGuiWindow* Window; // Best candidate window + float DistBox; // Best candidate box distance to current NavId + float DistCenter; // Best candidate center distance to current NavId + float DistAxial; + ImRect RectRel; // Best candidate bounding box in window relative space + + ImGuiNavMoveResult() { Clear(); } + void Clear() { ID = ParentID = 0; Window = NULL; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } +}; + +// Storage for SetNexWindow** functions +struct ImGuiNextWindowData +{ + ImGuiCond PosCond; + ImGuiCond SizeCond; + ImGuiCond ContentSizeCond; + ImGuiCond CollapsedCond; + ImGuiCond SizeConstraintCond; + ImGuiCond FocusCond; + ImGuiCond BgAlphaCond; + ImVec2 PosVal; + ImVec2 PosPivotVal; + ImVec2 SizeVal; + ImVec2 ContentSizeVal; + bool CollapsedVal; + ImRect SizeConstraintRect; // Valid if 'SetNextWindowSizeConstraint' is true + ImGuiSizeCallback SizeCallback; + void* SizeCallbackUserData; + float BgAlphaVal; + + ImGuiNextWindowData() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f); + ContentSizeVal = ImVec2(0.0f, 0.0f); + CollapsedVal = false; + SizeConstraintRect = ImRect(); + SizeCallback = NULL; + SizeCallbackUserData = NULL; + BgAlphaVal = FLT_MAX; + } + + void Clear() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + } +}; + +// Main state for ImGui +struct ImGuiContext +{ + bool Initialized; + bool FontAtlasOwnedByContext; // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. + ImGuiIO IO; + ImGuiStyle Style; + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + ImDrawListSharedData DrawListSharedData; + + float Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + ImVector Windows; + ImVector WindowsSortBuffer; + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; + int WindowsActiveCount; + ImGuiWindow* CurrentWindow; // Being drawn into + ImGuiWindow* HoveredWindow; // Will catch mouse inputs + ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) + ImGuiID HoveredId; // Hovered widget + bool HoveredIdAllowOverlap; + ImGuiID HoveredIdPreviousFrame; + float HoveredIdTimer; + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdPreviousFrame; + float ActiveIdTimer; + bool ActiveIdIsAlive; // Active widget has been seen this frame + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and move away from it) + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow* ActiveIdWindow; + ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actually window that is moved is generally MovingWindow->RootWindow. + ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() + ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() + ImVector FontStack; // Stack for PushFont()/PopFont() + ImVector OpenPopupStack; // Which popups are open (persistent) + ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + bool NextTreeNodeOpenVal; // Storage for SetNextTreeNode** functions + ImGuiCond NextTreeNodeOpenCond; + + // Navigation data (for gamepad/keyboard) + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' + ImGuiID NavId; // Focused item for navigation + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 + ImGuiID NavJustTabbedId; // Just tabbed to this id. + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest) + ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring. + int NavScoringCount; // Metrics for debugging + ImGuiWindow* NavWindowingTarget; // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) this window is temporarily displayed front-most. + float NavWindowingHighlightTimer; + float NavWindowingHighlightAlpha; + bool NavWindowingToggleLayer; + ImGuiInputSource NavWindowingInputSource; // Gamepad or keyboard mode + int NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. + int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRefRectRel is valid + bool NavMousePosDirty; // When set we will update mouse position if (NavFlags & ImGuiNavFlags_MoveMouse) if set (NB: this not enabled by default) + bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (nb: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) + bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest + bool NavInitRequest; // Init request for appearing window to select first item + bool NavInitRequestFromMove; + ImGuiID NavInitResultId; + ImRect NavInitResultRectRel; + bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items + bool NavMoveRequest; // Move request for this frame + ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) + ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request + ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow + ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using the NavFlattened flag) + + // Render + ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user + ImDrawDataBuilder DrawDataBuilder; + float ModalWindowDarkeningRatio; + ImDrawList OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays + ImGuiMouseCursor MouseCursor; + + // Drag and Drop + bool DragDropActive; + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; + ImGuiID DragDropTargetId; + float DragDropAcceptIdCurrRectSurface; + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly + unsigned char DragDropPayloadBufLocal[8]; + + // Widget state + ImGuiTextEditState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + ImVec4 ColorPickerRef; + float DragCurrentValue; // Currently dragged value, always float, not rounded by end-user precision settings + ImVec2 DragLastMouseDelta; + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + float DragSpeedScaleSlow; + float DragSpeedScaleFast; + ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + int TooltipOverrideCount; + ImVector PrivateClipboard; // If no custom clipboard handler is defined + ImVec2 OsImePosRequest, OsImePosSet; // Cursor position request & last passed to the OS Input Method Editor + + // Settings + bool SettingsLoaded; + float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero + ImVector SettingsWindows; // .ini settings for ImGuiWindow + ImVector SettingsHandlers; // List of .ini settings handlers + + // Logging + bool LogEnabled; + FILE* LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer* LogClipboard; // Else log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + int LogStartDepth; + int LogAutoExpandMaxDepth; + + // Misc + float FramerateSecPerFrame[120]; // calculate estimate of framerate for user + int FramerateSecPerFrameIdx; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // explicit capture via CaptureInputs() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; + char TempBuffer[1024*3+1]; // temporary text buffer + + ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL) + { + Initialized = false; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredRootWindow = NULL; + HoveredId = 0; + HoveredIdAllowOverlap = false; + HoveredIdPreviousFrame = 0; + HoveredIdTimer = 0.0f; + ActiveId = 0; + ActiveIdPreviousFrame = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsAlive = false; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdAllowNavDirFlags = 0; + ActiveIdClickOffset = ImVec2(-1,-1); + ActiveIdWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + MovingWindow = NULL; + NextTreeNodeOpenVal = false; + NextTreeNodeOpenCond = 0; + + NavWindow = NULL; + NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; + NavScoringRectScreen = ImRect(); + NavScoringCount = 0; + NavWindowingTarget = NULL; + NavWindowingHighlightTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + NavWindowingInputSource = ImGuiInputSource_None; + NavLayer = 0; + NavIdTabCounter = INT_MAX; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavInitResultId = 0; + NavMoveFromClampedRefRect = false; + NavMoveRequest = false; + NavMoveRequestForward = ImGuiNavForward_None; + NavMoveDir = NavMoveDirLast = ImGuiDir_None; + + ModalWindowDarkeningRatio = 0.0f; + OverlayDrawList._Data = &DrawListSharedData; + OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging + MouseCursor = ImGuiMouseCursor_Arrow; + + DragDropActive = false; + DragDropSourceFlags = 0; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ScalarAsInputTextId = 0; + ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + DragCurrentValue = 0.0f; + DragLastMouseDelta = ImVec2(0.0f, 0.0f); + DragSpeedDefaultRatio = 1.0f / 100.0f; + DragSpeedScaleSlow = 1.0f / 100.0f; + DragSpeedScaleFast = 10.0f; + ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); + TooltipOverrideCount = 0; + OsImePosRequest = OsImePosSet = ImVec2(-1.0f, -1.0f); + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + + LogEnabled = false; + LogFile = NULL; + LogClipboard = NULL; + LogStartDepth = 0; + LogAutoExpandMaxDepth = 2; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempBuffer, 0, sizeof(TempBuffer)); + } +}; + +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // FIXME-WIP: Disable interactions but doesn't affect visuals. Should be: grey out and disable interactions with widgets that affect data + view widgets (WIP) + ImGuiItemFlags_NoNav = 1 << 3, // false + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus +}; + +// Transient per-window data, reset at the beginning of the frame +// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiDrawContext is quite tenuous and could be reconsidered. +struct IMGUI_API ImGuiDrawContext +{ + ImVec2 CursorPos; + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; + ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame + float CurrentLineHeight; + float CurrentLineTextBaseOffset; + float PrevLineHeight; + float PrevLineTextBaseOffset; + float LogLinePosY; + int TreeDepth; + ImU32 TreeDepthMayCloseOnPop; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31 + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; // Interaction rect + ImRect LastItemDisplayRect; // End-user display rect (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) + bool NavHideHighlightOneFrame; + bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) + int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) + int NavLayerCurrentMask; // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping. + int NavLayerActiveMask; // Which layer have been written to (result from previous frame) + int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame) + bool MenuBarAppending; // FIXME: Remove this + float MenuBarOffsetX; + ImVector ChildWindows; + ImGuiStorage* StateStorage; + ImGuiLayoutType LayoutType; + ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] + float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window + float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] + ImVectorItemFlagsStack; + ImVector ItemWidthStack; + ImVector TextWrapPosStack; + ImVectorGroupStack; + int StackSizesBackup[6]; // Store size of various stacks for asserting + + float IndentX; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + float GroupOffsetX; + float ColumnsOffsetX; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + ImGuiColumnsSet* ColumnsSet; // Current columns set + + ImGuiDrawContext() + { + CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); + CurrentLineHeight = PrevLineHeight = 0.0f; + CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; + LogLinePosY = -1.0f; + TreeDepth = 0; + TreeDepthMayCloseOnPop = 0x00; + LastItemId = 0; + LastItemStatusFlags = 0; + LastItemRect = LastItemDisplayRect = ImRect(); + NavHideHighlightOneFrame = false; + NavHasScroll = false; + NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; + NavLayerCurrent = 0; + NavLayerCurrentMask = 1 << 0; + MenuBarAppending = false; + MenuBarOffsetX = 0.0f; + StateStorage = NULL; + LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical; + ItemWidth = 0.0f; + ItemFlags = ImGuiItemFlags_Default_; + TextWrapPos = -1.0f; + memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); + + IndentX = 0.0f; + GroupOffsetX = 0.0f; + ColumnsOffsetX = 0.0f; + ColumnsSet = NULL; + } +}; + +// Windows data +struct IMGUI_API ImGuiWindow +{ + char* Name; + ImGuiID ID; // == ImHash(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImVec2 PosFloat; + ImVec2 Pos; // Position rounded-up to nearest pixel + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars. + ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc. + ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() + ImRect ContentsRegionRect; // Maximum visible content position in window coordinates. ~~ (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis + ImVec2 WindowPadding; // Window padding at the time of begin. + float WindowRounding; // Window rounding at the time of begin. + float WindowBorderSize; // Window border size at the time of begin. + ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID ChildId; // Id of corresponding item in parent window (for child windows) + ImVec2 Scroll; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + bool ScrollbarX, ScrollbarY; + ImVec2 ScrollbarSizes; + bool Active; // Set to true on Begin(), unless Collapsed + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool CollapseToggleWanted; + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool CloseButton; // Set when the window has a close button (p_open != NULL) + int BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. + int BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order related issues. + int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + int AutoFitFramesX, AutoFitFramesY; + bool AutoFitOnlyGrows; + int AutoFitChildAxises; + ImGuiDir AutoPosLastDirection; + int HiddenFrames; + ImGuiCond SetWindowPosAllowFlags; // store condition flags for next SetWindowPos() call. + ImGuiCond SetWindowSizeAllowFlags; // store condition flags for next SetWindowSize() call. + ImGuiCond SetWindowCollapsedAllowFlags; // store condition flags for next SetWindowCollapsed() call. + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + + ImGuiDrawContext DC; // Temporary per-window data, reset at the beginning of the frame + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack + ImRect ClipRect; // = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2. + ImRect WindowRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. + ImRect InnerRect; + int LastFrameActive; + float ItemWidthDefault; + ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // Scale multiplier per-window + ImDrawList* DrawList; + ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. + ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. + ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. + ImGuiWindow* RootWindowForTabbing; // Point to ourself or first ancestor which can be CTRL-Tabbed into. + ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. + + ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) + ImGuiID NavLastIds[2]; // Last known NavId for this window, per layer (0/1) + ImRect NavRectRel[2]; // Reference rectangle, in window relative space + + // Navigation / Focus + // FIXME-NAV: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext + int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() + int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through) + int FocusIdxAllRequestCurrent; // Item being requested for focus + int FocusIdxTabRequestCurrent; // Tab-able item being requested for focus + int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame) + int FocusIdxTabRequestNext; // " + +public: + ImGuiWindow(ImGuiContext* context, const char* name); + ~ImGuiWindow(); + + ImGuiID GetID(const char* str, const char* str_end = NULL); + ImGuiID GetID(const void* ptr); + ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); + ImGuiID GetIDFromRectangle(const ImRect& r_abs); + + // We don't use g.FontSize because the window may be != g.CurrentWidow. + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } + float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } + float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } + float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } +}; + +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiItemHoveredDataBackup +{ + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; + ImRect LastItemDisplayRect; + + ImGuiItemHoveredDataBackup() { Backup(); } + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; } + void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } +}; + +//----------------------------------------------------------------------------- +// Internal API +// No guarantee of forward compatibility here. +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) + // If this ever crash because g.CurrentWindow is NULL it means that either + // - ImGui::NewFrame() has never been called, which is illegal. + // - You are calling ImGui functions after ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } + inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByName(const char* name); + IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void BringWindowToFront(ImGuiWindow* window); + IMGUI_API void BringWindowToBack(ImGuiWindow* window); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); + IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); + + IMGUI_API void Initialize(ImGuiContext* context); + IMGUI_API void Shutdown(ImGuiContext* context); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); + IMGUI_API ImGuiID GetActiveID(); + IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void ClearActiveID(); + IMGUI_API void SetHoveredID(ImGuiID id); + IMGUI_API ImGuiID GetHoveredID(); + IMGUI_API void KeepAliveID(ImGuiID id); + + IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); + IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested + IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); + IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); + IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + + IMGUI_API void SetCurrentFont(ImFont* font); + + IMGUI_API void OpenPopupEx(ImGuiID id); + IMGUI_API void ClosePopup(ImGuiID id); + IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window); + IMGUI_API bool IsPopupOpen(ImGuiID id); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); + + IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); + IMGUI_API void ActivateItem(ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. + + IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode); + IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); + IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); + + IMGUI_API void Scrollbar(ImGuiLayoutType direction); + IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). not exposed because it is misleading what it doesn't have an effect on regular layout. + IMGUI_API bool SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f); + + IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API void ClearDragDrop(); + IMGUI_API bool IsDragDropPayloadBeingAccepted(); + + // FIXME-WIP: New Columns API + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index = -1); + + // NB: All position are in absolute pixels coordinates (never using window coordinates internally) + // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. + IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); + IMGUI_API void RenderTriangle(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); + IMGUI_API void RenderBullet(ImVec2 pos); + IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. + + IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); + IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0); + IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); + IMGUI_API bool ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags = 0); + + IMGUI_API bool SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format); + + IMGUI_API bool DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power); + IMGUI_API bool DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format); + + IMGUI_API bool InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision); + + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); + + IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging + IMGUI_API void TreePushRawID(ImGuiID id); + + IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size); + + IMGUI_API int ParseFormatPrecision(const char* fmt, int default_value); + IMGUI_API float RoundScalar(float value, int decimal_precision); + + // Shade functions + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x); + IMGUI_API void ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); + +} // namespace ImGui + +// ImFontAtlas internals +IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); +IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif diff --git a/apps/MDL_sdf/imgui/stb_rect_pack.h b/apps/MDL_sdf/imgui/stb_rect_pack.h new file mode 100644 index 00000000..2b07dcc8 --- /dev/null +++ b/apps/MDL_sdf/imgui/stb_rect_pack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v0.11 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// +// Version history: +// +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/apps/MDL_sdf/imgui/stb_textedit.h b/apps/MDL_sdf/imgui/stb_textedit.h new file mode 100644 index 00000000..4b731a0c --- /dev/null +++ b/apps/MDL_sdf/imgui/stb_textedit.h @@ -0,0 +1,1322 @@ +// [ImGui] this is a slightly modified version of stb_truetype.h 1.9. Those changes would need to be pushed into nothings/sb +// [ImGui] - fixed linestart handler when over last character of multi-line buffer + simplified existing code (#588, #815) +// [ImGui] - fixed a state corruption/crash bug in stb_text_redo and stb_textedit_discard_redo (#715) +// [ImGui] - fixed a crash bug in stb_textedit_discard_redo (#681) +// [ImGui] - fixed some minor warnings + +// stb_textedit.h - v1.9 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Todo: +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + short insert_length; + short delete_length; + short char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + short undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = stb_text_locate_coord(str, x, y); + if (state->select_start == state->select_end) + state->select_start = state->cursor; + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + i = 0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicity clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + STB_TEXTEDIT_CHARTYPE *text = (STB_TEXTEDIT_CHARTYPE *) ctext; + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // remove the undo since we didn't actually insert the characters + if (state->undostate.undo_point) + --state->undostate.undo_point; + return 0; +} + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicity clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str,state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // now find character position down a row + if (find.length) { + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + int start = find.first_char + find.length; + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // can only go up if there's a previous row + if (find.prev_first != find.first_char) { + // now find character position up a row + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + +// @TODO: +// STB_TEXTEDIT_K_PGUP - move cursor up a page +// STB_TEXTEDIT_K_PGDOWN - move cursor down a page + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) ((size_t)state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) ((size_t)state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // delete n characters from all other records + state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((size_t)(STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05 + } + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point, state->undo_rec + state->redo_point-1, (size_t) ((size_t)(STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (short) insert_len; + r->delete_length = (short) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point = state->undo_char_point + (short) insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - (short) u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} +#endif//STB_TEXTEDIT_IMPLEMENTATION diff --git a/apps/MDL_sdf/imgui/stb_truetype.h b/apps/MDL_sdf/imgui/stb_truetype.h new file mode 100644 index 00000000..a08e929f --- /dev/null +++ b/apps/MDL_sdf/imgui/stb_truetype.h @@ -0,0 +1,4853 @@ +// stb_truetype.h - v1.19 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/apps/MDL_sdf/inc/Application.h b/apps/MDL_sdf/inc/Application.h new file mode 100644 index 00000000..d2dda756 --- /dev/null +++ b/apps/MDL_sdf/inc/Application.h @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef APPLICATION_H +#define APPLICATION_H + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS 1 +#include "imgui_internal.h" +#include "imgui_impl_glfw_gl3.h" + +#include +#if defined( _WIN32 ) +#include +#endif + +#include "inc/Camera.h" +#include "inc/Options.h" +#include "inc/Rasterizer.h" +#include "inc/Raytracer.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/Timer.h" + +#include "inc/MaterialMDL.h" + +#include + +// This include gl.h and needs to be done after glew.h +#include + +#include +#include +#include +#include + + +constexpr int APP_EXIT_SUCCESS = 0; +constexpr int APP_ERROR_UNKNOWN = -1; +constexpr int APP_ERROR_CREATE_WINDOW = -2; +constexpr int APP_ERROR_GLFW_INIT = -3; +constexpr int APP_ERROR_GLEW_INIT = -4; +constexpr int APP_ERROR_APP_INIT = -5; + + +enum GuiState +{ + GUI_STATE_NONE, + GUI_STATE_ORBIT, + GUI_STATE_PAN, + GUI_STATE_DOLLY, + GUI_STATE_FOCUS +}; + + +enum KeywordScene +{ + KS_LENS_SHADER, + KS_CENTER, + KS_CAMERA, + KS_GAMMA, + KS_COLOR_BALANCE, + KS_WHITE_POINT, + KS_BURN_HIGHLIGHTS, + KS_CRUSH_BLACKS, + KS_SATURATION, + KS_BRIGHTNESS, + KS_EMISSION, + KS_EMISSION_MULTIPLIER, + KS_EMISSION_PROFILE, + KS_EMISSION_TEXTURE, + KS_SPOT_ANGLE, + KS_SPOT_EXPONENT, + KS_IDENTITY, + KS_PUSH, + KS_POP, + KS_ROTATE, + KS_SCALE, + KS_TRANSLATE, + //KS_PLANE, + //KS_BOX, + //KS_SPHERE, + //KS_TORUS, + //KS_ASSIMP, + KS_MDL, + KS_LIGHT, + KS_MODEL, + KS_SDF +}; + + +struct SceneState +{ + void reset() + { + matrix = dp::math::cIdentity44f; + matrixInv = dp::math::cIdentity44f; + + orientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); + orientationInv = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); + + nameEmission.clear(); + nameProfile.clear(); + + colorEmission = make_float3(0.0f); // Default black + multiplierEmission = 1.0f; + + spotAngle = 45.0f; + spotExponent = 0.0f; // No cosine falloff. + } + + // Transformation state + dp::math::Mat44f matrix; + dp::math::Mat44f matrixInv; + // The orientation (the pure rotational part of the above matrices). + dp::math::Quatf orientation; + dp::math::Quatf orientationInv; + + std::string nameEmission; // The filename of the emission texture. Empty when none. + std::string nameProfile; // The filename of the IES light profile. Empty when none. + + float3 colorEmission; // The emission base color. + float multiplierEmission; // A multiplier on top of colorEmission to get HDR lights. + + float spotAngle; // Full cone angle in degrees, means max. 180 degrees is a hemispherical distribution. + float spotExponent; // Exponent on the cosine of the sotAngle, used to generate intensity falloff from spot cone center to outer angle. Set to 0.0 for no falloff. +}; + + +class Application +{ +public: + + Application(GLFWwindow* window, const Options& options); + ~Application(); + + bool isValid() const; + + void reshape(const int w, const int h); + bool render(); + void benchmark(); + + void display(); + + void guiNewFrame(); + void guiWindow(); + void guiEventHandler(); + void guiReferenceManual(); // The ImGui "programming manual" in form of a live window. + void guiRender(); + +private: + bool loadSystemDescription(const std::string& filename); + bool saveSystemDescription(); + + //TypeBXDF determineTypeBXDF(const std::string& token) const; + //TypeEDF determineTypeEDF(const std::string& token) const; + bool loadSceneDescription(const std::string& filename); + + void restartRendering(); + + bool screenshot(const bool tonemap); + + void createCameras(); + + int findMaterial(const std::string& reference); + + void appendInstance(std::shared_ptr& group, + std::shared_ptr geometry, // Baseclass Node to be prepared for different geometric primitives. + const dp::math::Mat44f& matrix, + const int indexMaterial, + const int indexLight); + + std::shared_ptr createASSIMP(const std::string& filename); + std::shared_ptr traverseScene(const struct aiScene *scene, const unsigned int indexSceneBase, const struct aiNode* node); + + void calculateTangents(std::vector& attributes, const std::vector& indices); + + void guiRenderingIndicator(const bool isRendering); + + void createMeshLights(); + void traverseGraph(std::shared_ptr node, InstanceData& instanceData, float matrix[12]); + int createMeshLight(const std::shared_ptr geometry, const InstanceData& instanceData, const float matrix[12]); + + bool loadString(const std::string& filename, std::string& text); + bool saveString(const std::string& filename, const std::string& text); + std::string getDateTime(); + void convertPath(std::string& path); + void convertPath(char* path); + + void addSearchPath(const std::string& path); + + bool isEmissiveMaterial(const int indexMaterial) const; + +private: + GLFWwindow* m_window; + bool m_isValid; + + GuiState m_guiState; + bool m_isVisibleGUI; + + // Command line options: + int m_width; // Client window size. + int m_height; + int m_mode; // Application mode 0 = interactive, 1 = batched benchmark (single shot). + bool m_optimize; // Command line option to let the assimp importer optimize the graph (sorts by material). + + // System options: + int m_strategy; // "strategy" // Ignored in this renderer. Always behaves like RS_INTERACTIVE_MULTI_GPU_LOCAL_COPY. + int m_maskDevices; // "devicesMask" // Bitmask with enabled devices, default 0x00FFFFFF for max 24 devices. Only the visible ones will be used. + size_t m_sizeArena; // "arenaSize" // Default size for Arena allocations in mega-bytes. + int m_interop; // "interop" // 0 = none all through host, 1 = register texture image, 2 = register pixel buffer + int m_peerToPeer; // "peerToPeer // Bitfield controlling P2P resource sharing: + // Bit 0 = Allow sharing via PCI-E bus. Only share across NVLINK bridges when off (default off) + // Bit 1 = Allow sharing of texture CUarray or CUmipmappedArray data (legacy and MDL) (fast) (default on) + // Bit 2 = Allow sharing of geometry acceleration structures and vertex attributes (slowest) (default off) + // Bit 3 = Allow sharing of spherical environment light texture and CDFs (slow) (default off) + // Bit 4 = Allow sharing of MDL Measured BSDF and their CDFs (slow) (default off) + // Bit 5 = Allow sharing of MDL Lightprofiles and their CDFs (slow) (default off) + bool m_present; // "present" + bool m_presentNext; // (derived) + double m_presentAtSecond; // (derived) + bool m_previousComplete; // (derived) // Prevents spurious benchmark prints and image updates. + + // GUI Data representing raytracer settings. + TypeLens m_typeLens; // "lensShader" + int2 m_pathLengths; // "pathLengths" // min, max + int m_walkLength; // "walkLength" // Number of volume scattering random walk steps until the maximum distance is to try gettting out of the volumes. Minimum 1 for single scattering. + int2 m_resolution; // "resolution" // The actual size of the rendering, independent of the window's client size. (Preparation for final frame rendering.) + int2 m_tileSize; // "tileSize" // Multi-GPU distribution tile size. Must be power-of-two values. + int m_samplesSqrt; // "sampleSqrt" + float m_epsilonFactor; // "epsilonFactor" + float m_clockFactor; // "clockFactor" + bool m_useDirectLighting; + // SDF intersection program parameters. + int m_sdfIterations; // "sdfIterations" // The maximum number of sphere tracing iteration steps inside the SDF intersection program. + float m_sdfEpsilon; // "sdfEpsilon" // The epsilon distance which counts as surface hit during the sphere tracing. + float m_sdfOffset; // "sdfOffset" // Scale factor on the sdfEpsilon for self-intersection avoidance of continuation and shadow rays on SDFs. Default 2.0f. + + TypeLight m_typeEnv; // The type of the light in m_lightsGUI[0]. Used to determine the miss shader. + float m_rotationEnvironment[3]; // The Euler rotation angles for the spherical environment light. + + std::string m_prefixScreenshot; // "prefixScreenshot", allows to set a path and the prefix for the screenshot filename. spp, data, time and extension will be appended. + + TonemapperGUI m_tonemapperGUI; // "gamma", "whitePoint", "burnHighlights", "crushBlacks", "saturation", "brightness" + + Camera m_camera; // "center", "camera" + + float m_mouseSpeedRatio; // "mouseSpeedRatio" + + Timer m_timer; + + std::map m_mapKeywordScene; + + std::unique_ptr m_rasterizer; + + std::unique_ptr m_raytracer; + + DeviceState m_state; + + // The scene description: + // Unique identifiers per host scene node. + unsigned int m_idGroup; + unsigned int m_idInstance; + unsigned int m_idGeometry; + + std::shared_ptr m_scene; // Root group node of the scene. + + std::vector< std::shared_ptr > m_geometries; // All geometries in the scene. Baseclass Node to be prepared for different geometric primitives. + + // For the runtime generated objects, this allows to find geometries with the same type and construction parameters. + std::map m_mapGeometries; + + // For all model file format loaders. Allows instancing of full models in the host side scene graph. + std::map< std::string, std::shared_ptr > m_mapGroups; + + std::vector m_cameras; + std::vector m_lightsGUI; + + // Map picture file paths to their loaded data on the host. + // (This is not including the texture images loaded by MDL, only the ones usded for hardcoded lights.) + std::map m_mapPictures; + + // A temporary vector of mesh indices used inside the ASSIMP loader to handle the triangle meshes inside a model. + std::vector m_remappedMeshIndices; + + // MDL material handling. + + // This vector of search paths defines where MDL is searching for *.mdl and resource files. + // This is set via the "searchPath" option in the system description file. + // Multiple searchPaths entries are possible and define the search order. + std::vector m_searchPaths; + + // This map stores all declared materials under their reference name. + // Only the ones which are referenced inside the scene will be loaded + // into the m_materialsMDL vector and tracked inside m_mapReferenceToMaterialIndex. + std::map m_mapReferenceToDeclaration; + + // This maps a material reference name to an index into the m_materialsMDL vector. + std::map m_mapReferenceToMaterialIndex; + + // Vector of MaterialMDL pointers, one per unique material reference name inside the scene description. + std::vector m_materialsMDL; + + // This tracks which material is selected inside the GUI combo box. + int m_indexMaterialGUI; +}; + + +#endif // APPLICATION_H + diff --git a/apps/MDL_sdf/inc/Arena.h b/apps/MDL_sdf/inc/Arena.h new file mode 100644 index 00000000..61d66b9d --- /dev/null +++ b/apps/MDL_sdf/inc/Arena.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef ARENA_H +#define ARENA_H + +#include + +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include + +namespace cuda +{ + enum Usage + { + USAGE_STATIC, + USAGE_TEMP + }; + + class Arena; + + struct Block + { + Block(cuda::Arena* arena = nullptr, const CUdeviceptr addr = 0, const size_t size = 0, const CUdeviceptr ptr = 0); + + bool isValid() const; + bool operator<(const cuda::Block& rhs); + + cuda::Arena* m_arena; // The arena which owns this block. + CUdeviceptr m_addr; // The internal address inside the arena of the memory containing the aligned block at ptr. + size_t m_size; // The internal size of the allocation starting at m_addr. + CUdeviceptr m_ptr; // The aligned pointer to size bytes the user requested. + }; + + + class Arena + { + public: + Arena(const size_t size); + ~Arena(); + + bool allocBlock(cuda::Block& block, const size_t size, const size_t alignment, const cuda::Usage usage); + void freeBlock(const cuda::Block& block); + + private: + CUdeviceptr m_addr; // The start pointer of the CUDA memory for this arena. This is 256 bytes aligned due to cuMalloc(). + size_t m_size; // The overall size of the arena in bytes. + std::list m_blocksFree; // A list of free blocks inside this arena. This is normally very short because static and temporary allocations are not interleaved. + }; + + + class ArenaAllocator + { + public: + ArenaAllocator(const size_t sizeArenaBytes); + ~ArenaAllocator(); + + CUdeviceptr alloc(const size_t size, const size_t alignment, const cuda::Usage usage = cuda::USAGE_STATIC); + void free(const CUdeviceptr ptr); + + size_t getSizeMemoryAllocated() const; + + private: + size_t m_sizeArenaBytes; // The minimum size an arena is allocated with. If calls alloc() with a bigger size, that will be used. + size_t m_sizeMemoryAllocated; // The number of bytes which have been allocated (with alignment). + std::vector m_arenas; // A number of Arenas managing a CUdeviceptr with at least m_sizeArenaBytes each. + std::map m_blocksAllocated; // Map the user pointer to the internal block. This abstracts Arena and Block from the user. (Kind of expensive.) + }; + +} // namespace cuda + +#endif // ARENA_H diff --git a/apps/MDL_sdf/inc/Camera.h b/apps/MDL_sdf/inc/Camera.h new file mode 100644 index 00000000..15f7b851 --- /dev/null +++ b/apps/MDL_sdf/inc/Camera.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CAMERA_H +#define CAMERA_H + +#include + + +class Camera +{ +public: + Camera(); + //~Camera(); + + void setResolution(int w, int h); + void setBaseCoordinates(int x, int y); + void setSpeedRatio(float f); + void setFocusDistance(float f); + + void orbit(int x, int y); + void pan(int x, int y); + void dolly(int x, int y); + void focus(int x, int y); + void zoom(float x); + + bool getFrustum(float3& p, float3& u, float3& v, float3& w, bool force = false); + float getAspectRatio() const; + void markDirty(); + +public: // public just to be able to load and save them easily. + float3 m_center; // Center of interest point, around which is orbited (and the sharp plane of a depth of field camera). + float m_distance; // Distance of the camera from the center of interest. + float m_phi; // Range [0.0f, 1.0f] from positive x-axis 360 degrees around the latitudes. + float m_theta; // Range [0.0f, 1.0f] from negative to positive y-axis. + float m_fov; // In degrees. Default is 60.0f + +private: + bool setDelta(int x, int y); + +private: + int m_widthResolution; + int m_heightResolution; + float m_aspect; // width / height + int m_baseX; + int m_baseY; + float m_speedRatio; + + // Derived values: + int m_dx; + int m_dy; + bool m_changed; + + float3 m_cameraP; + float3 m_cameraU; + float3 m_cameraV; + float3 m_cameraW; +}; + +#endif // CAMERA_H diff --git a/apps/MDL_sdf/inc/CheckMacros.h b/apps/MDL_sdf/inc/CheckMacros.h new file mode 100644 index 00000000..91966eff --- /dev/null +++ b/apps/MDL_sdf/inc/CheckMacros.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CHECK_MACROS_H +#define CHECK_MACROS_H + +#include +#include + +#include "inc/MyAssert.h" + +#define CU_CHECK(call) \ +{ \ + const CUresult result = call; \ + if (result != CUDA_SUCCESS) \ + { \ + const char* name; \ + cuGetErrorName(result, &name); \ + const char *error; \ + cuGetErrorString(result, &error); \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ") " << name << ": " << error; \ + MY_ASSERT(!"CU_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +#define CU_CHECK_NO_THROW(call) \ +{ \ + const CUresult result = call; \ + if (result != CUDA_SUCCESS) \ + { \ + const char* name; \ + cuGetErrorName(result, &name); \ + const char *error; \ + cuGetErrorString(result, &error); \ + std::cerr << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ") " << name << ": " << error << '\n'; \ + MY_ASSERT(!"CU_CHECK_NO_THROW"); \ + } \ +} + +#define OPTIX_CHECK(call) \ +{ \ + const OptixResult result = call; \ + if (result != OPTIX_SUCCESS) \ + { \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")"; \ + MY_ASSERT(!"OPTIX_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +#define OPTIX_CHECK_NO_THROW(call) \ +{ \ + const OptixResult result = call; \ + if (result != OPTIX_SUCCESS) \ + { \ + std::cerr << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")\n"; \ + MY_ASSERT(!"OPTIX_CHECK_NO_THROW"); \ + } \ +} + + +#endif // CHECK_MACROS_H diff --git a/apps/MDL_sdf/inc/CompileResult.h b/apps/MDL_sdf/inc/CompileResult.h new file mode 100644 index 00000000..97cd7a7d --- /dev/null +++ b/apps/MDL_sdf/inc/CompileResult.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef COMPILE_RESULT_H +#define COMPILE_RESULT_H + +#include +#include + +#include +#include + + +/// Result of a material compilation. +struct Compile_result +{ + /// The compiled material. + mi::base::Handle compiled_material; + + /// The generated target code object. + mi::base::Handle target_code; + + /// The argument block for the compiled material. + mi::base::Handle argument_block; + + /// Information required to load a texture. + struct Texture_info + { + std::string db_name; + mi::neuraylib::ITarget_code::Texture_shape shape; + + Texture_info() + : shape(mi::neuraylib::ITarget_code::Texture_shape_invalid) + { + } + + Texture_info(char const* db_name, + mi::neuraylib::ITarget_code::Texture_shape shape) + : db_name(db_name) + , shape(shape) + { + } + }; + + /// Information required to load a light profile. + struct Light_profile_info + { + std::string db_name; + + Light_profile_info() + { + } + + Light_profile_info(char const* db_name) + : db_name(db_name) + { + } + }; + + /// Information required to load a BSDF measurement. + struct Bsdf_measurement_info + { + std::string db_name; + + Bsdf_measurement_info() + { + } + + Bsdf_measurement_info(char const* db_name) + : db_name(db_name) + { + } + }; + + /// Textures used by the compile result. + std::vector textures; + + /// Light profiles used by the compile result. + std::vector light_profiles; + + /// Bsdf_measurements used by the compile result. + std::vector bsdf_measurements; + + /// Constructor. + Compile_result() + { + // Add invalid resources. + textures.emplace_back(); + light_profiles.emplace_back(); + bsdf_measurements.emplace_back(); + } +}; + +#endif // COMPILE_RESULT_H diff --git a/apps/MDL_sdf/inc/Device.h b/apps/MDL_sdf/inc/Device.h new file mode 100644 index 00000000..3c0805d7 --- /dev/null +++ b/apps/MDL_sdf/inc/Device.h @@ -0,0 +1,605 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef DEVICE_H +#define DEVICE_H + +#include "shaders/config.h" + +#include +// This is needed for the __align__ only. +#include + +#include + +// OptiX 7 function table structure. +#include + +#include "inc/Arena.h" +#include "inc/LightGUI.h" +//#include "inc/MaterialGUI.h" +#include "inc/Picture.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/MyAssert.h" + +#include "inc/CompileResult.h" +#include "inc/MaterialMDL.h" +#include "inc/ShaderConfiguration.h" +#include "shaders/shader_configuration.h" // Device side of the the shader configuration. + +#include "shaders/system_data.h" +#include "shaders/per_ray_data.h" +#include "shaders/texture_handler.h" +#include "shaders/half_common.h" + +#include +#include +#include + +struct DeviceAttribute +{ + int maxThreadsPerBlock; + int maxBlockDimX; + int maxBlockDimY; + int maxBlockDimZ; + int maxGridDimX; + int maxGridDimY; + int maxGridDimZ; + int maxSharedMemoryPerBlock; + int sharedMemoryPerBlock; + int totalConstantMemory; + int warpSize; + int maxPitch; + int maxRegistersPerBlock; + int registersPerBlock; + int clockRate; + int textureAlignment; + int gpuOverlap; + int multiprocessorCount; + int kernelExecTimeout; + int integrated; + int canMapHostMemory; + int computeMode; + int maximumTexture1dWidth; + int maximumTexture2dWidth; + int maximumTexture2dHeight; + int maximumTexture3dWidth; + int maximumTexture3dHeight; + int maximumTexture3dDepth; + int maximumTexture2dLayeredWidth; + int maximumTexture2dLayeredHeight; + int maximumTexture2dLayeredLayers; + int maximumTexture2dArrayWidth; + int maximumTexture2dArrayHeight; + int maximumTexture2dArrayNumslices; + int surfaceAlignment; + int concurrentKernels; + int eccEnabled; + int pciBusId; + int pciDeviceId; + int tccDriver; + int memoryClockRate; + int globalMemoryBusWidth; + int l2CacheSize; + int maxThreadsPerMultiprocessor; + int asyncEngineCount; + int unifiedAddressing; + int maximumTexture1dLayeredWidth; + int maximumTexture1dLayeredLayers; + int canTex2dGather; + int maximumTexture2dGatherWidth; + int maximumTexture2dGatherHeight; + int maximumTexture3dWidthAlternate; + int maximumTexture3dHeightAlternate; + int maximumTexture3dDepthAlternate; + int pciDomainId; + int texturePitchAlignment; + int maximumTexturecubemapWidth; + int maximumTexturecubemapLayeredWidth; + int maximumTexturecubemapLayeredLayers; + int maximumSurface1dWidth; + int maximumSurface2dWidth; + int maximumSurface2dHeight; + int maximumSurface3dWidth; + int maximumSurface3dHeight; + int maximumSurface3dDepth; + int maximumSurface1dLayeredWidth; + int maximumSurface1dLayeredLayers; + int maximumSurface2dLayeredWidth; + int maximumSurface2dLayeredHeight; + int maximumSurface2dLayeredLayers; + int maximumSurfacecubemapWidth; + int maximumSurfacecubemapLayeredWidth; + int maximumSurfacecubemapLayeredLayers; + int maximumTexture1dLinearWidth; + int maximumTexture2dLinearWidth; + int maximumTexture2dLinearHeight; + int maximumTexture2dLinearPitch; + int maximumTexture2dMipmappedWidth; + int maximumTexture2dMipmappedHeight; + int computeCapabilityMajor; + int computeCapabilityMinor; + int maximumTexture1dMipmappedWidth; + int streamPrioritiesSupported; + int globalL1CacheSupported; + int localL1CacheSupported; + int maxSharedMemoryPerMultiprocessor; + int maxRegistersPerMultiprocessor; + int managedMemory; + int multiGpuBoard; + int multiGpuBoardGroupId; + int hostNativeAtomicSupported; + int singleToDoublePrecisionPerfRatio; + int pageableMemoryAccess; + int concurrentManagedAccess; + int computePreemptionSupported; + int canUseHostPointerForRegisteredMem; + int canUse64BitStreamMemOps; + int canUseStreamWaitValueNor; + int cooperativeLaunch; + int cooperativeMultiDeviceLaunch; + int maxSharedMemoryPerBlockOptin; + int canFlushRemoteWrites; + int hostRegisterSupported; + int pageableMemoryAccessUsesHostPageTables; + int directManagedMemAccessFromHost; +}; + +struct DeviceProperty +{ + unsigned int rtcoreVersion; + unsigned int limitMaxTraceDepth; + unsigned int limitMaxTraversableGraphDepth; + unsigned int limitMaxPrimitivesPerGas; + unsigned int limitMaxInstancesPerIas; + unsigned int limitMaxInstanceId; + unsigned int limitNumBitsInstanceVisibilityMask; + unsigned int limitMaxSbtRecordsPerGas; + unsigned int limitMaxSbtOffset; + unsigned int shaderExecutionReordering; +}; + + +// None of the programs is using SBT data! +struct SbtRecordHeader +{ + __align__(OPTIX_SBT_RECORD_ALIGNMENT) char header[OPTIX_SBT_RECORD_HEADER_SIZE]; +}; + + +enum ModuleIdentifier +{ + MODULE_ID_RAYGENERATION, + MODULE_ID_EXCEPTION, + MODULE_ID_MISS, + MODULE_ID_INTERSECTION, + MODULE_ID_HIT, + MODULE_ID_LENS_SHADER, + MODULE_ID_LIGHT_SAMPLE, + + NUM_MODULE_IDENTIFIERS +}; + + +enum ProgramGroupId +{ + PGID_RAYGENERATION, + + PGID_EXCEPTION, + + PGID_MISS_RADIANCE, + PGID_MISS_SHADOW, + + // Hit records for triangles: + // 0 = no emission, no cutout + PGID_HIT_RADIANCE_0, + PGID_HIT_SHADOW_0, + // 1 = emission, no cutout + PGID_HIT_RADIANCE_1, + PGID_HIT_SHADOW_1, + // 2 = no emission, cutout + PGID_HIT_RADIANCE_2, + PGID_HIT_SHADOW_2, + // 3 = emission, cutout + PGID_HIT_RADIANCE_3, + PGID_HIT_SHADOW_3, + // 4 = cubic B-spline curves. + PGID_HIT_CURVES, + PGID_HIT_CURVES_SHADOW, + // 5 = SDF 3D texture inside an AABB. + PGID_HIT_SDF, + PGID_HIT_SDF_SHADOW, + // Direct Callables + // Lens shader + PGID_LENS_PINHOLE, + PGID_LENS_FISHEYE, + PGID_LENS_SPHERE, + // Light sample + PGID_LIGHT_ENV_CONSTANT, + PGID_LIGHT_ENV_SPHERE, + PGID_LIGHT_MESH, + PGID_LIGHT_POINT, // singular + PGID_LIGHT_SPOT, // singular + PGID_LIGHT_IES, // singular (same as point light but with additional IES light profile luminance texture.) + + // Number of all hardcoded program group entries. + // There are more direct callables appended by the MDL materials. + NUM_PROGRAM_GROUP_IDS +}; + +// This is in preparation for more than one geometric primitive type. +enum PrimitiveType +{ + PT_UNKNOWN, // It's an error when this is still set. + PT_TRIANGLES, + PT_CURVES, + PT_SDF +}; + + +struct GeometryData +{ + GeometryData() + : primitiveType(PT_UNKNOWN) + , owner(-1) + , traversable(0) + , d_attributes(0) + , d_indices(0) + , numAttributes(0) + , numIndices(0) + , d_gas(0) + { + info = {}; + } + + PrimitiveType primitiveType; // Triangles or cubic B-spline curves. Used to pick the correct hit record inside the SBT. + int owner; // The device index which originally allocated all device side memory below. Needed when sharing GeometryData, resp. when freeing it. + OptixTraversableHandle traversable; // The traversable handle for this GAS. Assigned to the Instance above it. + CUdeviceptr d_attributes; // Array of VertexAttribute structs. + CUdeviceptr d_indices; // Array of unsigned int indices. + size_t numAttributes; // Count of attributes structs. + size_t numIndices; // Count of unsigned int indices (not triplets for triangles). + CUdeviceptr d_gas; // The geometry AS for the traversable handle. +#if (OPTIX_VERSION >= 70600) + OptixRelocationInfo info; // The relocation info allows to check if the AS is compatible across OptiX contexts. + // (In the NVLINK case that isn't really required, because the GPU configuration must be homogeneous inside an NVLINK island.) +#else + OptixAccelRelocationInfo info; +#endif +}; + +struct InstanceData +{ + InstanceData(const unsigned int geometry, + const int material, + const int light, + const int object) + : idGeometry(geometry) + , idMaterial(material) + , idLight(light) + , idObject(object) + { + } + + unsigned int idGeometry; + int idMaterial; // Negative is an error. + int idLight; // Negative means no light. + int idObject; // Application-specific identifier used in state.object_id. It's the sg::Instance ID. +}; + +// GUI controllable settings in the device. +struct DeviceState +{ + int2 resolution; + int2 tileSize; + int2 pathLengths; + int walkLength; + int samplesSqrt; + TypeLens typeLens; + float epsilonFactor; + float clockFactor; + int directLighting; + int sdfIterations; + float sdfEpsilon; + float sdfOffset; +}; + + +class Device; + +// To be able to share texture CUarrays among devices inside a CUDA peer-to-peer island, +// the owner device and all data required to create a texture object from the shared CUarray are required. +struct TextureMDLHost +{ + Device* m_owner; // The device which created this Texture. Needed for peer-to-peer sharing, resp. for proper destruction. + + int m_index; // The index inside the per-device cache vector. + // Used to maintain the MaterialMDL indices vector with the indices into that cache. + + CUDA_ARRAY3D_DESCRIPTOR m_descArray3D; // FIXME This is not actually required for the sharing. + CUDA_RESOURCE_DESC m_resourceDescription; + CUDA_TEXTURE_DESC m_textureDescription; + + CUarray m_d_array; // FIXME This is also in host.m_resourceDescription.res.array.hArray + + // How much memory the CUarray required in bytes, input to cuMemcpy3d() + // (without m_deviceAttribute.textureAlignment or potential row padding on the device). + size_t m_sizeBytesArray; + + // The per-device texture objects and sizes are tracked in this struct + // which is used inside the Texture_handler for the texture lookup functions. + TextureMDL m_texture; +}; + + +struct MbsdfHost +{ + Device* m_owner; // The device which created this Texture. Needed for peer-to-peer sharing, resp. for proper destruction. + + int m_index; // The index inside the per-device cache vector. + + CUDA_ARRAY3D_DESCRIPTOR m_descArray3D[2]; // FIXME This is not actually required for the sharing. + CUDA_RESOURCE_DESC m_resourceDescription[2]; + CUDA_TEXTURE_DESC m_textureDescription; // Same settings for both parts! + + CUarray m_d_array[2]; // FIXME This is also in host.m_resourceDescription.res.array.hArray + // How much memory the CUarray required in bytes, input to cuMemcpy3d() + // (without m_deviceAttribute.textureAlignment or potential row padding on the device). + size_t m_sizeBytesArray[2]; + + Mbsdf m_mbsdf; +}; + + +struct LightprofileHost +{ + Device* m_owner; // The device which created this Texture. Needed for peer-to-peer sharing, resp. for proper destruction. + + int m_index; // The index inside the per-device cache vector. + + CUDA_ARRAY3D_DESCRIPTOR m_descArray3D; // FIXME This is not actually required for the sharing. + CUDA_RESOURCE_DESC m_resourceDescription; + CUDA_TEXTURE_DESC m_textureDescription; + + CUarray m_d_array; // FIXME This is also in host.m_resourceDescription.res.array.hArray + // How much memory the CUarray required in bytes, input to cuMemcpy3d() + // (without m_deviceAttribute.textureAlignment or potential row padding on the device). + size_t m_sizeBytesArray; + + Lightprofile m_profile; +}; + + + +class Device +{ +public: + Device(const int ordinal, // The original CUDA ordinal ID. + const int index, // The zero based index of this device, required for multi-GPU work distribution. + const int count , // The number of active devices, required for multi-GPU work distribution. + const TypeLight typeEnv, // Controls which miss shader to use. + const int interop, // The interop mode to use. + const unsigned int tex, // OpenGL HDR texture object handle + const unsigned int pbo, // OpenGL PBO handle. + const size_t sizeArena); // The default Arena size in mega-bytes. + ~Device(); + + bool matchUUID(const char* uuid); + bool matchLUID(const char* luid, const unsigned int nodeMask); + + void initCameras(const std::vector& cameras); + void initLights(const std::vector& lightsGUI, const std::vector& geometryData, const unsigned int stride, const unsigned int index); // Must be called after initScene()! + + GeometryData createGeometry(std::shared_ptr geometry); + GeometryData createGeometry(std::shared_ptr geometry); + GeometryData createGeometry(std::shared_ptr geometry); + + void destroyGeometry(GeometryData& data); + void createInstance(const GeometryData& geometryData, const InstanceData& data, const float matrix[12]); + void createTLAS(); + void createGeometryInstanceData(const std::vector& geometryData, const unsigned int stride, const unsigned int index); + + void updateCamera(const int idCamera, const CameraDefinition& camera); + void updateLight(const int idLight, const LightGUI& lightsGUI); + //void updateLight(const int idLight, const LightDefinition& light); + void updateMaterial(const int idMaterial, const MaterialMDL* materialMDL); + + void setState(const DeviceState& state); + void compositor(Device* other); + + void activateContext() const ; + void synchronizeStream() const; + void render(const unsigned int iterationIndex, void** buffer, const int mode); + void updateDisplayTexture(); + const void* getOutputBufferHost(); + + CUdeviceptr memAlloc(const size_t size, const size_t alignment, const cuda::Usage usage = cuda::USAGE_STATIC); + void memFree(const CUdeviceptr ptr); + + size_t getMemoryFree() const; + size_t getMemoryAllocated() const; + + Texture* initTexture(const std::string& name, const Picture* picture, const unsigned int flags); + void shareTexture(const std::string & name, const Texture* shared); + + unsigned int appendProgramGroupMDL(const int indexModule, const std::string& nameFunction); + + void compileMaterial(mi::neuraylib::ITransaction* transaction, + MaterialMDL* material, + const Compile_result& res, + const ShaderConfiguration& config); + + const TextureMDLHost* prepareTextureMDL(mi::neuraylib::ITransaction* transaction, + mi::base::Handle image_api, + char const* texture_db_name, + mi::neuraylib::ITarget_code::Texture_shape texture_shape); + void shareTextureMDL(const TextureMDLHost* shared, + char const* texture_db_name, + mi::neuraylib::ITarget_code::Texture_shape texture_shape); + + MbsdfHost* prepareMBSDF(mi::neuraylib::ITransaction* transaction, + const mi::neuraylib::ITarget_code* code, + const int index); + void shareMBSDF(const MbsdfHost* shared); + + LightprofileHost* prepareLightprofile(mi::neuraylib::ITransaction* transaction, + const mi::neuraylib::ITarget_code* code, + int index); + void shareLightprofile(const LightprofileHost* shared); + + void initTextureHandler(std::vector& materialsMDL); + +private: + OptixResult initFunctionTable(); + void initDeviceAttributes(); + void initDeviceProperties(); + void initPipeline(); + + bool prepare_mbsdfs_part(mi::neuraylib::Mbsdf_part part, + MbsdfHost& host, + const mi::neuraylib::IBsdf_measurement* bsdf_measurement); + +public: + // Constructor arguments: + int m_ordinal; // The ordinal number of this CUDA device. Used to get the CUdevice handle m_cudaDevice. + int m_index; // The index inside the m_devicesActive vector. + int m_count; // The number of active devices. + TypeLight m_typeEnv; // Type of environment miss shader to use. + int m_interop; // The interop mode to use. + unsigned int m_tex; // The OpenGL HDR texture object. + unsigned int m_pbo; // The OpenGL PixelBufferObject handle when interop should be used. 0 when not. + + float m_clockFactor; // Clock Factor scaled by CLOCK_FACTOR_SCALE (1.0e-9f) for USE_TIME_VIEW + + CUuuid m_deviceUUID; + + // Not actually used because this only works under Windows WDDM mode, not in TCC mode! + // (Though using LUID is recommended under Windows when possible because you can potentially + // get the same UUID for MS hybrid configurations (like when running under Remote Desktop). + char m_deviceLUID[8]; + unsigned int m_nodeMask; + + std::string m_deviceName; + std::string m_devicePciBusId; // domain:bus:device.function, required to find matching CUDA device via NVML. + + DeviceAttribute m_deviceAttribute; // CUDA + DeviceProperty m_deviceProperty; // OptiX + + CUdevice m_cudaDevice; // The CUdevice handle of the CUDA device ordinal. (Usually identical to m_ordinal.) + CUcontext m_cudaContext; + CUstream m_cudaStream; + + OptixFunctionTable m_api; + OptixDeviceContext m_optixContext; + + std::vector m_moduleFilenames; + + // These are used in different Device functions. Set them once inside the constructor. + OptixModuleCompileOptions m_mco; + OptixPipelineCompileOptions m_pco; + OptixPipelineLinkOptions m_plo; + OptixProgramGroupOptions m_pgo; // This is a just placeholder. + + OptixPipeline m_pipeline; + + OptixShaderBindingTable m_sbt; + + CUdeviceptr m_d_sbtRecordHeaders; + + CUdeviceptr m_d_ias; + + std::vector m_instances; + + std::vector m_instanceData; // idGeometry, idMaterial, idLight, idObject + + std::vector m_geometryInstanceData; + GeometryInstanceData* m_d_geometryInstanceData; + + SystemData m_systemData; // This contains the root traversable handle as well. + SystemData* m_d_systemData; // Device side CUdeviceptr of the system data. + + std::vector m_subFrames; // A host array with all sub-frame indices, used to update the device side sysData.iterationIndex fully asynchronously. + + int m_launchWidth; + + bool m_isDirtySystemData; + bool m_isDirtyOutputBuffer; + bool m_ownsSharedBuffer; // DEBUG This flag is only evaluated in asserts. + + std::map m_mapTextures; + + std::vector m_lights; // Staging data for the device side sysData.lightDefinitions + + CUgraphicsResource m_cudaGraphicsResource; // The handle for the registered OpenGL PBO or texture image resource when using interop. + + CUmodule m_moduleCompositor; + CUfunction m_functionCompositor; + CUdeviceptr m_d_compositorData; + +#if USE_FP32_OUTPUT + std::vector m_bufferHost; +#else + std::vector m_bufferHost; +#endif + + cuda::ArenaAllocator* m_allocator; + + size_t m_sizeMemoryTextureArrays; // The sum of all texture CUarray resp. CUmipmappedArray. + + // The modules and device shader configurations are both indexed by the shader index. + std::vector m_modulesMDL; + std::vector m_deviceShaderConfigurations; + + // These are all direct callable program groups initialized by all different MDL shaders in the scene. + // (The shader configuration idxCall* fields are indexing into the full set of program groups though, + // already including the lens shader plus light sampling count offset.) + std::vector m_programGroupsMDL; + + // The Device needs to track these to be able to get the shader index from a material index + // which is only stored in the MaterialDefinitionMDL struct on device side. + std::vector m_materialDefinitions; // One element per material reference, indexed by indexMaterial. + + // MDL Resource handling, textures, measured BSDFs, light profiles. + // All different resources (textures, MBSDFs, light profiles) used by the MDL materials inside the scene are stored per device. + // The MaterialMDL only holds vectors with indices into these caches. That index is the same on each device. + // It gets expanded into the Texture_handler structure in Device::initTextureHandler(). + + std::map m_mapTextureNameToIndex; // Texture cache. + std::vector m_textureMDLHosts; // All textures used by the materials inside the scene. + + // There is no cache implemented for measured BSDFs and light profiles. + // The assumption is that measured BSDFs or light profiles are not used by multiple material shaders. + std::vector m_mbsdfHosts; // All MBSDFs used by the materials inside the scene. + + std::vector m_lightprofileHosts; // All light profiles used by materials inside the scene +}; + +#endif // DEVICE_H diff --git a/apps/MDL_sdf/inc/Hair.h b/apps/MDL_sdf/inc/Hair.h new file mode 100644 index 00000000..fcec5dfe --- /dev/null +++ b/apps/MDL_sdf/inc/Hair.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef HAIR_H +#define HAIR_H + +// Hair file format and models courtesy of Cem Yuksel: http://www.cemyuksel.com/research/hairmodels/ + +#include + +#include +#include + +#define HAS_SEGMENTS_ARRAY (1 << 0) +#define HAS_POINTS_ARRAY (1 << 1) +#define HAS_THICKNESS_ARRAY (1 << 2) +#define HAS_TRANSPARENCY_ARRAY (1 << 3) +#define HAS_COLOR_ARRAY (1 << 4) + +// Note that the Hair class setter interfaces are unused in this example but allow generating *.hair files easily. +// +// At minimum that needs an array of float3 points and the matching header values. +// This renderer doesn't care about the transparency and color inside the *.hair model file. +// The material is defined by the assigned MDL hair BSDF material. +// Not setting the color might result in black in other applications. +// +// Example for setting individual cubic B-spline curves with four control points each. +// ... // Build your std::vector pointsArray here. +//Hair hair; +//hair.setNumStrands(numStrands); // Number of hair strands inside the file. +//hair.setNumSegments(3); // Cubic B-Splines with 4 control points each are 3 segments. +//hair.setPointsArray(pointsArray); // This defines the m_header.numPoints as well. +//hair.setThickness(thickness); // Constant thickness. Use setThicknessArray() if values differ along strands. +//hair.save(filename); + +class Hair +{ + // HAIR File Header (128 Bytes) + struct Header + { + char signature[4]; // Bytes 0-3 Must be "HAIR" in ascii code (48 41 49 52) + unsigned int numStrands; // Bytes 4-7 Number of hair strands as unsigned int + unsigned int numPoints; // Bytes 8-11 Total number of points of all strands as unsigned int + unsigned int bits; // Bytes 12-15 Bit array of data in the file + // Bit-0 is 1 if the file has segments array. + // Bit-1 is 1 if the file has points array (this bit must be 1). + // Bit-2 is 1 if the file has thickness array. + // Bit-3 is 1 if the file has transparency array. + // Bit-4 is 1 if the file has color array. + // Bit-5 to Bit-31 are reserved for future extension (must be 0). + unsigned int numSegments; // Bytes 16-19 Default number of segments of hair strands as unsigned int + // If the file does not have a segments array, this default value is used. + float thickness; // Bytes 20-23 Default thickness hair strands as float + // If the file does not have a thickness array, this default value is used. + float transparency; // Bytes 24-27 Default transparency hair strands as float + // If the file does not have a transparency array, this default value is used. + float3 color; // Bytes 28-39 Default color hair strands as float array of size 3 + // If the file does not have a thickness array, this default value is used. + char information[88]; // Bytes 40-127 File information as char array of size 88 in ascii + }; + +public: + Hair(); + //~Hair(); + + bool load(const std::string& filename); + bool save(const std::string& filename); + + void setNumStrands(const unsigned int num); + unsigned int getNumStrands() const; + + void setNumSegments(const unsigned int num); + void setSegmentsArray(const std::vector& segments); + unsigned int getNumSegments(const unsigned int idxStrand) const; + + void setPointsArray(const std::vector& points); // This also sets m_header.numPoints! + float3 getPoint(const unsigned int idx) const; + + void setThickness(const float thickness); + void setThicknessArray(const std::vector& thickness); + float getThickness(const unsigned int idx) const; + + void setTransparency(const float transparency); + void setTransparencyArray(const std::vector& transparency); + float getTransparency(const unsigned int idx) const; + + void setColor(const float3 color); + void setColorArray(const std::vector& color); + float3 getColor(const unsigned int idx) const; + +private: + Header m_header; + + std::vector m_segmentsArray; // Empty or size numStrands. + std::vector m_pointsArray; // Size numPoints. + std::vector m_thicknessArray; // Empty or size numPoints. + std::vector m_transparencyArray; // Empty or size numPoints. + std::vector m_colorArray; // Empty or size numPoints. +}; + +#endif // HAIR_H diff --git a/apps/MDL_sdf/inc/LightGUI.h b/apps/MDL_sdf/inc/LightGUI.h new file mode 100644 index 00000000..20f70ae6 --- /dev/null +++ b/apps/MDL_sdf/inc/LightGUI.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef LIGHT_GUI_H +#define LIGHT_GUI_H + +#include + +#include + +#include "shaders/function_indices.h" + +#include + +// Host side GUI light parameters, only stores the values not held inside the material already. +struct LightGUI +{ + TypeLight typeLight; // Zero-based light type to select the light sampling and evaluation callables. + + dp::math::Mat44f matrix; // object to world + dp::math::Mat44f matrixInv; // world to object + + dp::math::Quatf orientation; // object to world, rotation only + dp::math::Quatf orientationInv; // world to object, rotation only + + unsigned int idGeometry; // Geometry data index for mesh lights. (Supports GAS sharing!) + int idMaterial; // Needed for explicit light sampling to be able to determine the used shader. + int idObject; // Application-specific ID used for state.object_id in explicit light mesh sampling. Matches GeometryInstanceData::idObject. + + std::string nameEmission; // The filename of the emission texture. Empty when none. + std::string nameProfile; // The filename of the IES light profile when typeEDF is TYPE_EDF_IES, otherwise unused. Empty when none. + + float3 colorEmission; // The emission base color. + float multiplierEmission; // A multiplier on top of colorEmission to get HDR lights. + + float spotAngle; // Full cone angle in degrees, means max. 180 degrees is a hemispherical distribution. + float spotExponent; // Exponent on the cosine of the sotAngle, used to generate intensity falloff from spot cone center to outer angle. Set to 0.0 for no falloff. + + std::vector cdfAreas; // CDF over the areas of the mesh triangles used for uniform sampling. + float area; // Overall surface area of the mesh light in world space. +}; + +#endif // LIGHT_GUI_H diff --git a/apps/MDL_sdf/inc/LoaderIES.h b/apps/MDL_sdf/inc/LoaderIES.h new file mode 100644 index 00000000..9270e170 --- /dev/null +++ b/apps/MDL_sdf/inc/LoaderIES.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef LOADER_IES_H +#define LOADER_IES_H + +#include +#include +#include +#include + +enum IESFormat +{ + IESNA_86, // LM-63-1986 // Default when there is no format identifier. + IESNA_91, // LM-63-1991 + IESNA_95, // LM-63-1995 + IESNA_02 // LM-63-2002 // DEBUG This implementation was against the LM-63-1995 specs, but contents for LM-63-2002 files looked identical so far. +}; + +struct IESFile +{ + std::string name; + IESFormat format; +}; + +enum IESLampOrientation // Lamp-to-luminaire geometry +{ + LO_VERTICAL = 1, // Lamp vertical base up or down + LO_HORIZONTAL = 2, // Lamp horizontal + LO_TILT = 3 // Lamp tilted +}; + +struct IESTilt +{ + IESLampOrientation orientation; + int numPairs; + std::vector angles; + std::vector factors; +}; + +struct IESLamp +{ + int numLamps; + float lumenPerLamp; + float multiplier; + bool hasTilt; // false if TILT=NONE, true for TILT=INCLUDE and TILT= + std::string tiltFilename; + IESTilt tilt; +}; + +enum IESUnits +{ + U_FEET = 1, + U_METERS = 2 +}; + +struct IESCavityDimension +{ + float width; // Opening width + float length; // Opening length + float height; // Cavity height +}; + +struct IESElectricalData +{ + float ballastFactor; + float ballastLampPhotometricFactor; // "future use" in IESNA:LM-63-1995 + float inputWatts; +}; + +enum IESGoniometerType +{ + TYPE_A = 3, + TYPE_B = 2, + TYPE_C = 1 +}; + +struct IESPhotometricData +{ + IESGoniometerType goniometerType; + int numVerticalAngles; + int numHorizontalAngles; + std::vector verticalAngles; + std::vector horizontalAngles; + std::vector candela; // numHorizontalAngles * numVerticalAngles values. 2D index [h][v] <==> linear index [h * numVerticalAngles + v] +}; + +struct IESData +{ + IESFile file; + std::vector label; + IESLamp lamp; + IESUnits units; + IESCavityDimension dimension; + IESElectricalData electrical; + IESPhotometricData photometric; +}; + + +class LoaderIES +{ +public: + enum IESTokenType + { + ITT_LINE, + ITT_IDENTIFIER, // Not really used. Only "TILT=" would be an identifier and that is handled with getNextLine(). Everything not a label is an ITT_VALUE. + ITT_VALUE, + ITT_EOL, + ITT_EOF, + ITT_UNKNOWN + }; + +public: + LoaderIES(); + //~LoaderIES(); + + bool load(const std::string& iesFilename); + bool parse(); + IESData const& getData(); + +private: + IESTokenType getNextLine(std::string& token); + IESTokenType getNextToken(std::string& token); + + bool loadTilt(std::string const& tiltFilename); + +private: + IESData m_iesData; + + std::string m_source; // Parameters file contents. + std::string::size_type m_index; // Parser's current character index into m_source. + std::string::size_type m_lookbackIndex; // To be able to handle filenames with spaces it's necessary to look back at the last token's start index. + // Memorized in getNextToken() used in getFilename(); + unsigned int m_line; // Current source code line, one based for error messages. +}; + +#endif // LOADER_IES_H diff --git a/apps/MDL_sdf/inc/MaterialMDL.h b/apps/MDL_sdf/inc/MaterialMDL.h new file mode 100644 index 00000000..0c0d7de7 --- /dev/null +++ b/apps/MDL_sdf/inc/MaterialMDL.h @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MATERIAL_MDL_H +#define MATERIAL_MDL_H + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "shaders/texture_handler.h" + + + // Possible enum values if any. +struct Enum_value +{ + std::string name; + int value; + + Enum_value(const std::string& name, int value) + : name(name) + , value(value) + { + } +}; + + +// Info for an enum type. +struct Enum_type_info +{ + std::vector values; + + // Adds a enum value and its integer value to the enum type info. + void add(const std::string& name, int value) + { + values.push_back(Enum_value(name, value)); + } +}; + + +// Material parameter information structure. +class Param_info +{ +public: + enum Param_kind + { + PK_UNKNOWN, + PK_FLOAT, + PK_FLOAT2, + PK_FLOAT3, + PK_COLOR, + PK_ARRAY, + PK_BOOL, + PK_INT, + PK_ENUM, + PK_STRING, + PK_TEXTURE, + PK_LIGHT_PROFILE, + PK_BSDF_MEASUREMENT + }; + + Param_info(size_t index, + char const* name, + char const* display_name, + char const* group_name, + Param_kind kind, + Param_kind array_elem_kind, + mi::Size array_size, + mi::Size array_pitch, + char* data_ptr, + const Enum_type_info* enum_info = nullptr) + : m_index(index) + , m_name(name) + , m_display_name(display_name) + , m_group_name(group_name) + , m_kind(kind) + , m_array_elem_kind(array_elem_kind) + , m_array_size(array_size) + , m_array_pitch(array_pitch) + , m_data_ptr(data_ptr) + , m_range_min(0.0f) + , m_range_max(1.0f) + , m_enum_info(enum_info) + { + } + + // Get data as T&. + template + T& data() + { + return *reinterpret_cast(m_data_ptr); + } + + // Get data as const T&. + template + const T& data() const + { + return *reinterpret_cast(m_data_ptr); + } + + const char* display_name() const + { + return m_display_name.c_str(); + } + void set_display_name(const char* display_name) + { + m_display_name = display_name; + } + + const char* group_name() const + { + return m_group_name.c_str(); + } + void set_group_name(const char* group_name) + { + m_group_name = group_name; + } + + Param_kind kind() const + { + return m_kind; + } + + Param_kind array_elem_kind() const + { + return m_array_elem_kind; + } + mi::Size array_size() const + { + return m_array_size; + } + mi::Size array_pitch() const + { + return m_array_pitch; + } + + float& range_min() + { + return m_range_min; + } + float range_min() const + { + return m_range_min; + } + float& range_max() + { + return m_range_max; + } + float range_max() const + { + return m_range_max; + } + + template + void update_range() + { + T* val_ptr = &data(); + for (int i = 0; i < N; ++i) + { + float val = float(val_ptr[i]); + if (val < m_range_min) + m_range_min = val; + if (m_range_max < val) + m_range_max = val; + } + } + + const Enum_type_info* enum_info() const + { + return m_enum_info; + } + +private: + size_t m_index; + std::string m_name; + std::string m_display_name; + std::string m_group_name; + Param_kind m_kind; + Param_kind m_array_elem_kind; + mi::Size m_array_size; + mi::Size m_array_pitch; // the distance between two array elements + char* m_data_ptr; + float m_range_min; + float m_range_max; + const Enum_type_info* m_enum_info; +}; + + +struct MaterialDeclaration +{ + std::string nameReference; + std::string nameMaterial; + std::string pathMaterial; +}; + + +// This gets store per material reference because the same shader can be reused among multiple references. +struct MaterialMDL +{ + MaterialMDL(const MaterialDeclaration& materialDeclaration); + + bool getIsValid() const; + void setIsValid(bool value); + std::string getReference() const; + std::string getName() const; + std::string getPath() const; + + void storeMaterialInfo(const int indexShader, + mi::neuraylib::IFunction_definition const* mat_def, + mi::neuraylib::ICompiled_material const* comp_mat, + mi::neuraylib::ITarget_value_layout const* arg_block_layout, + mi::neuraylib::ITarget_argument_block const* arg_block); + void add_sorted_by_group(const Param_info& info); + void add_enum_type(const std::string name, std::shared_ptr enum_info); + const Enum_type_info* get_enum_type(const std::string name); + int getShaderIndex() const; + char const* name() const; + std::list& params(); + char* getArgumentBlockData() const; + size_t getArgumentBlockSize() const; + std::vector const& getReferencedSceneDataNames() const; + + bool m_isValid; + + MaterialDeclaration m_declaration; + + // Index into the m_shaders and m_shaderConfigurations vectors. + int m_indexShader; + // Name of the material. + std::string m_name; + // Modifiable argument block. + mi::base::Handle m_arg_block; + // Parameters of the material. + std::list m_params; + + // Used enum types of the material + std::map > m_mapEnumTypes; + + // Scene data names referenced by the material + std::vector m_referencedSceneDataNames; + + // These are indices to the Device class' resource caches. + std::vector m_indicesToTextures; + std::vector m_indicesToMBSDFs; + std::vector m_indicesToLightprofiles; +}; + +#endif // MATERIAL_MDL_H diff --git a/apps/MDL_sdf/inc/MyAssert.h b/apps/MDL_sdf/inc/MyAssert.h new file mode 100644 index 00000000..9ac7b937 --- /dev/null +++ b/apps/MDL_sdf/inc/MyAssert.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MY_ASSERT_H +#define MY_ASSERT_H + +#undef MY_STATIC_ASSERT + +#define MY_STATIC_ASSERT(exp) static_assert(exp, #exp); + +#undef MY_ASSERT + +#if !defined(NDEBUG) + #if defined(_WIN32) // Windows 32/64-bit + #include + #define DBGBREAK() DebugBreak(); + #else + #define DBGBREAK() __builtin_trap(); + #endif + #define MY_ASSERT(expr) if (!(expr)) DBGBREAK() +#else + #define MY_ASSERT(expr) +#endif + +#if !defined(NDEBUG) + #define MY_VERIFY(expr) MY_ASSERT(expr) +#else + #define MY_VERIFY(expr) (static_cast(expr)) +#endif + +#endif // MY_ASSERT_H diff --git a/apps/MDL_sdf/inc/NVMLImpl.h b/apps/MDL_sdf/inc/NVMLImpl.h new file mode 100644 index 00000000..48507e7c --- /dev/null +++ b/apps/MDL_sdf/inc/NVMLImpl.h @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef NVML_IMPL_H +#define NVML_IMPL_H + +// This header ships with the CUDA Toolkit. +#include + + +#define NVML_CHECK(call) \ +{ \ + const nvmlReturn_t result = call; \ + if (result != NVML_SUCCESS) \ + { \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")"; \ + MY_ASSERT(!"NVML_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +// These function entry point types and names support the automatic NVML API versioning inside nvml.h. +// Some of the NVML functions are versioned by a #define adding a version suffix (_v2, _v3) to the name, +// which requires a set of two macros to resolve the unversioned function name to the versioned one. + +#define FUNC_T_V(name) name##_t +#define FUNC_T(name) FUNC_T_V(name) + +#define FUNC_P_V(name) name##_t name +#define FUNC_P(name) FUNC_P_V(name) + +typedef nvmlReturn_t (*FUNC_T(nvmlInit))(void); +//typedef nvmlReturn_t (*FUNC_T(nvmlInitWithFlags))(unsigned int flags); +typedef nvmlReturn_t (*FUNC_T(nvmlShutdown))(void); +//typedef const char* (*FUNC_T(nvmlErrorString))(nvmlReturn_t result); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetDriverVersion))(char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetNVMLVersion))(char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetCudaDriverVersion))(int *cudaDriverVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetCudaDriverVersion_v2))(int *cudaDriverVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetProcessName))(unsigned int pid, char *name, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetCount))(unsigned int *unitCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetHandleByIndex))(unsigned int index, nvmlUnit_t *unit); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetUnitInfo))(nvmlUnit_t unit, nvmlUnitInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetLedState))(nvmlUnit_t unit, nvmlLedState_t *state); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetPsuInfo))(nvmlUnit_t unit, nvmlPSUInfo_t *psu); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetTemperature))(nvmlUnit_t unit, unsigned int type, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetFanSpeedInfo))(nvmlUnit_t unit, nvmlUnitFanSpeeds_t *fanSpeeds); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetDevices))(nvmlUnit_t unit, unsigned int *deviceCount, nvmlDevice_t *devices); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetHicVersion))(unsigned int *hwbcCount, nvmlHwbcEntry_t *hwbcEntries); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCount))(unsigned int *deviceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAttributes))(nvmlDevice_t device, nvmlDeviceAttributes_t *attributes); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByIndex))(unsigned int index, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleBySerial))(const char *serial, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByUUID))(const char *uuid, nvmlDevice_t *device); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByPciBusId))(const char *pciBusId, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetName))(nvmlDevice_t device, char *name, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBrand))(nvmlDevice_t device, nvmlBrandType_t *type); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetIndex))(nvmlDevice_t device, unsigned int *index); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSerial))(nvmlDevice_t device, char *serial, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryAffinity))(nvmlDevice_t device, unsigned int nodeSetSize, unsigned long *nodeSet, nvmlAffinityScope_t scope); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCpuAffinityWithinScope))(nvmlDevice_t device, unsigned int cpuSetSize, unsigned long *cpuSet, nvmlAffinityScope_t scope); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCpuAffinity))(nvmlDevice_t device, unsigned int cpuSetSize, unsigned long *cpuSet); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetCpuAffinity))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearCpuAffinity))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTopologyCommonAncestor))(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuTopologyLevel_t *pathInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTopologyNearestGpus))(nvmlDevice_t device, nvmlGpuTopologyLevel_t level, unsigned int *count, nvmlDevice_t *deviceArray); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetTopologyGpuSet))(unsigned int cpuNumber, unsigned int *count, nvmlDevice_t *deviceArray); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetP2PStatus))(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuP2PCapsIndex_t p2pIndex,nvmlGpuP2PStatus_t *p2pStatus); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetUUID))(nvmlDevice_t device, char *uuid, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetMdevUUID))(nvmlVgpuInstance_t vgpuInstance, char *mdevUuid, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMinorNumber))(nvmlDevice_t device, unsigned int *minorNumber); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBoardPartNumber))(nvmlDevice_t device, char* partNumber, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomVersion))(nvmlDevice_t device, nvmlInforomObject_t object, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomImageVersion))(nvmlDevice_t device, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomConfigurationChecksum))(nvmlDevice_t device, unsigned int *checksum); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceValidateInforom))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDisplayMode))(nvmlDevice_t device, nvmlEnableState_t *display); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDisplayActive))(nvmlDevice_t device, nvmlEnableState_t *isActive); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPersistenceMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPciInfo))(nvmlDevice_t device, nvmlPciInfo_t *pci); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxPcieLinkGeneration))(nvmlDevice_t device, unsigned int *maxLinkGen); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxPcieLinkWidth))(nvmlDevice_t device, unsigned int *maxLinkWidth); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrPcieLinkGeneration))(nvmlDevice_t device, unsigned int *currLinkGen); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrPcieLinkWidth))(nvmlDevice_t device, unsigned int *currLinkWidth); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPcieThroughput))(nvmlDevice_t device, nvmlPcieUtilCounter_t counter, unsigned int *value); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPcieReplayCounter))(nvmlDevice_t device, unsigned int *value); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetClockInfo))(nvmlDevice_t device, nvmlClockType_t type, unsigned int *clock); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxClockInfo))(nvmlDevice_t device, nvmlClockType_t type, unsigned int *clock); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetApplicationsClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDefaultApplicationsClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetApplicationsClocks))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetClock))(nvmlDevice_t device, nvmlClockType_t clockType, nvmlClockId_t clockId, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxCustomerBoostClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedMemoryClocks))(nvmlDevice_t device, unsigned int *count, unsigned int *clocksMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedGraphicsClocks))(nvmlDevice_t device, unsigned int memoryClockMHz, unsigned int *count, unsigned int *clocksMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t *isEnabled, nvmlEnableState_t *defaultIsEnabled); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t enabled); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetDefaultAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t enabled, unsigned int flags); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFanSpeed))(nvmlDevice_t device, unsigned int *speed); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFanSpeed_v2))(nvmlDevice_t device, unsigned int fan, unsigned int * speed); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTemperature))(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTemperatureThreshold))(nvmlDevice_t device, nvmlTemperatureThresholds_t thresholdType, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPerformanceState))(nvmlDevice_t device, nvmlPstates_t *pState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrentClocksThrottleReasons))(nvmlDevice_t device, unsigned long long *clocksThrottleReasons); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedClocksThrottleReasons))(nvmlDevice_t device, unsigned long long *supportedClocksThrottleReasons); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerState))(nvmlDevice_t device, nvmlPstates_t *pState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementLimit))(nvmlDevice_t device, unsigned int *limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementLimitConstraints))(nvmlDevice_t device, unsigned int *minLimit, unsigned int *maxLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementDefaultLimit))(nvmlDevice_t device, unsigned int *defaultLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerUsage))(nvmlDevice_t device, unsigned int *power); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTotalEnergyConsumption))(nvmlDevice_t device, unsigned long long *energy); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEnforcedPowerLimit))(nvmlDevice_t device, unsigned int *limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuOperationMode))(nvmlDevice_t device, nvmlGpuOperationMode_t *current, nvmlGpuOperationMode_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryInfo))(nvmlDevice_t device, nvmlMemory_t *memory); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeMode))(nvmlDevice_t device, nvmlComputeMode_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCudaComputeCapability))(nvmlDevice_t device, int *major, int *minor); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEccMode))(nvmlDevice_t device, nvmlEnableState_t *current, nvmlEnableState_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBoardId))(nvmlDevice_t device, unsigned int *boardId); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMultiGpuBoard))(nvmlDevice_t device, unsigned int *multiGpuBool); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTotalEccErrors))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, unsigned long long *eccCounts); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDetailedEccErrors))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, nvmlEccErrorCounts_t *eccCounts); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryErrorCounter))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, nvmlMemoryLocation_t locationType, unsigned long long *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetUtilizationRates))(nvmlDevice_t device, nvmlUtilization_t *utilization); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderUtilization))(nvmlDevice_t device, unsigned int *utilization, unsigned int *samplingPeriodUs); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderCapacity))(nvmlDevice_t device, nvmlEncoderType_t encoderQueryType, unsigned int *encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderStats))(nvmlDevice_t device, unsigned int *sessionCount, unsigned int *averageFps, unsigned int *averageLatency); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderSessions))(nvmlDevice_t device, unsigned int *sessionCount, nvmlEncoderSessionInfo_t *sessionInfos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDecoderUtilization))(nvmlDevice_t device, unsigned int *utilization, unsigned int *samplingPeriodUs); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFBCStats))(nvmlDevice_t device, nvmlFBCStats_t *fbcStats); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFBCSessions))(nvmlDevice_t device, unsigned int *sessionCount, nvmlFBCSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDriverModel))(nvmlDevice_t device, nvmlDriverModel_t *current, nvmlDriverModel_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVbiosVersion))(nvmlDevice_t device, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBridgeChipInfo))(nvmlDevice_t device, nvmlBridgeChipHierarchy_t *bridgeHierarchy); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeRunningProcesses))(nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGraphicsRunningProcesses))(nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceOnSameBoard))(nvmlDevice_t device1, nvmlDevice_t device2, int *onSameBoard); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAPIRestriction))(nvmlDevice_t device, nvmlRestrictedAPI_t apiType, nvmlEnableState_t *isRestricted); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSamples))(nvmlDevice_t device, nvmlSamplingType_t type, unsigned long long lastSeenTimeStamp, nvmlValueType_t *sampleValType, unsigned int *sampleCount, nvmlSample_t *samples); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBAR1MemoryInfo))(nvmlDevice_t device, nvmlBAR1Memory_t *bar1Memory); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetViolationStatus))(nvmlDevice_t device, nvmlPerfPolicyType_t perfPolicyType, nvmlViolationTime_t *violTime); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingStats))(nvmlDevice_t device, unsigned int pid, nvmlAccountingStats_t *stats); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingPids))(nvmlDevice_t device, unsigned int *count, unsigned int *pids); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingBufferSize))(nvmlDevice_t device, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPages))(nvmlDevice_t device, nvmlPageRetirementCause_t cause, unsigned int *pageCount, unsigned long long *addresses); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPages_v2))(nvmlDevice_t device, nvmlPageRetirementCause_t cause, unsigned int *pageCount, unsigned long long *addresses, unsigned long long *timestamps); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPagesPendingStatus))(nvmlDevice_t device, nvmlEnableState_t *isPending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRemappedRows))(nvmlDevice_t device, unsigned int *corrRows, unsigned int *uncRows, unsigned int *isPending, unsigned int *failureOccurred); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetArchitecture))(nvmlDevice_t device, nvmlDeviceArchitecture_t *arch); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitSetLedState))(nvmlUnit_t unit, nvmlLedColor_t color); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetPersistenceMode))(nvmlDevice_t device, nvmlEnableState_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetComputeMode))(nvmlDevice_t device, nvmlComputeMode_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetEccMode))(nvmlDevice_t device, nvmlEnableState_t ecc); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearEccErrorCounts))(nvmlDevice_t device, nvmlEccCounterType_t counterType); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetDriverModel))(nvmlDevice_t device, nvmlDriverModel_t driverModel, unsigned int flags); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetGpuLockedClocks))(nvmlDevice_t device, unsigned int minGpuClockMHz, unsigned int maxGpuClockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetGpuLockedClocks))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetApplicationsClocks))(nvmlDevice_t device, unsigned int memClockMHz, unsigned int graphicsClockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetPowerManagementLimit))(nvmlDevice_t device, unsigned int limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetGpuOperationMode))(nvmlDevice_t device, nvmlGpuOperationMode_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAPIRestriction))(nvmlDevice_t device, nvmlRestrictedAPI_t apiType, nvmlEnableState_t isRestricted); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAccountingMode))(nvmlDevice_t device, nvmlEnableState_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearAccountingPids))(nvmlDevice_t device); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkState))(nvmlDevice_t device, unsigned int link, nvmlEnableState_t *isActive); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkVersion))(nvmlDevice_t device, unsigned int link, unsigned int *version); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkCapability))(nvmlDevice_t device, unsigned int link, nvmlNvLinkCapability_t capability, unsigned int *capResult); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkRemotePciInfo))(nvmlDevice_t device, unsigned int link, nvmlPciInfo_t *pci); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkErrorCounter))(nvmlDevice_t device, unsigned int link, nvmlNvLinkErrorCounter_t counter, unsigned long long *counterValue); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetNvLinkErrorCounters))(nvmlDevice_t device, unsigned int link); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetNvLinkUtilizationControl))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlNvLinkUtilizationControl_t *control, unsigned int reset); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkUtilizationControl))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlNvLinkUtilizationControl_t *control); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter, unsigned long long *rxcounter, unsigned long long *txcounter); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceFreezeNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlEnableState_t freeze); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetCreate))(nvmlEventSet_t *set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceRegisterEvents))(nvmlDevice_t device, unsigned long long eventTypes, nvmlEventSet_t set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedEventTypes))(nvmlDevice_t device, unsigned long long *eventTypes); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetWait))(nvmlEventSet_t set, nvmlEventData_t * data, unsigned int timeoutms); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetFree))(nvmlEventSet_t set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceModifyDrainState))(nvmlPciInfo_t *pciInfo, nvmlEnableState_t newState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceQueryDrainState))(nvmlPciInfo_t *pciInfo, nvmlEnableState_t *currentState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceRemoveGpu))(nvmlPciInfo_t *pciInfo, nvmlDetachGpuState_t gpuState, nvmlPcieLinkState_t linkState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceDiscoverGpus))(nvmlPciInfo_t *pciInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFieldValues))(nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVirtualizationMode))(nvmlDevice_t device, nvmlGpuVirtualizationMode_t *pVirtualMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHostVgpuMode))(nvmlDevice_t device, nvmlHostVgpuMode_t *pHostVgpuMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetVirtualizationMode))(nvmlDevice_t device, nvmlGpuVirtualizationMode_t virtualMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGridLicensableFeatures))(nvmlDevice_t device, nvmlGridLicensableFeatures_t *pGridLicensableFeatures); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetProcessUtilization))(nvmlDevice_t device, nvmlProcessUtilizationSample_t *utilization, unsigned int *processSamplesCount, unsigned long long lastSeenTimeStamp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCreatableVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetClass))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeClass, unsigned int *size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetName))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeName, unsigned int *size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetDeviceID))(nvmlVgpuTypeId_t vgpuTypeId, unsigned long long *deviceID, unsigned long long *subsystemID); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetFramebufferSize))(nvmlVgpuTypeId_t vgpuTypeId, unsigned long long *fbSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetNumDisplayHeads))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *numDisplayHeads); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetResolution))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int displayIndex, unsigned int *xdim, unsigned int *ydim); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetLicense))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeLicenseString, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetFrameRateLimit))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *frameRateLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetMaxInstances))(nvmlDevice_t device, nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetMaxInstancesPerVm))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCountPerVm); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetActiveVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuInstance_t *vgpuInstances); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetVmID))(nvmlVgpuInstance_t vgpuInstance, char *vmId, unsigned int size, nvmlVgpuVmIdType_t *vmIdType); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetUUID))(nvmlVgpuInstance_t vgpuInstance, char *uuid, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetVmDriverVersion))(nvmlVgpuInstance_t vgpuInstance, char* version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFbUsage))(nvmlVgpuInstance_t vgpuInstance, unsigned long long *fbUsage); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetLicenseStatus))(nvmlVgpuInstance_t vgpuInstance, unsigned int *licensed); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetType))(nvmlVgpuInstance_t vgpuInstance, nvmlVgpuTypeId_t *vgpuTypeId); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFrameRateLimit))(nvmlVgpuInstance_t vgpuInstance, unsigned int *frameRateLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEccMode))(nvmlVgpuInstance_t vgpuInstance, nvmlEnableState_t *eccMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderCapacity))(nvmlVgpuInstance_t vgpuInstance, unsigned int *encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceSetEncoderCapacity))(nvmlVgpuInstance_t vgpuInstance, unsigned int encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderStats))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, unsigned int *averageFps, unsigned int *averageLatency); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderSessions))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, nvmlEncoderSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFBCStats))(nvmlVgpuInstance_t vgpuInstance, nvmlFBCStats_t *fbcStats); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFBCSessions))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, nvmlFBCSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetMetadata))(nvmlVgpuInstance_t vgpuInstance, nvmlVgpuMetadata_t *vgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuMetadata))(nvmlDevice_t device, nvmlVgpuPgpuMetadata_t *pgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetVgpuCompatibility))(nvmlVgpuMetadata_t *vgpuMetadata, nvmlVgpuPgpuMetadata_t *pgpuMetadata, nvmlVgpuPgpuCompatibility_t *compatibilityInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPgpuMetadataString))(nvmlDevice_t device, char *pgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetVgpuVersion))(nvmlVgpuVersion_t *supported, nvmlVgpuVersion_t *current); +//typedef nvmlReturn_t (*FUNC_T(nvmlSetVgpuVersion))(nvmlVgpuVersion_t *vgpuVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuUtilization))(nvmlDevice_t device, unsigned long long lastSeenTimeStamp, nvmlValueType_t *sampleValType, unsigned int *vgpuInstanceSamplesCount, nvmlVgpuInstanceUtilizationSample_t *utilizationSamples); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuProcessUtilization))(nvmlDevice_t device, unsigned long long lastSeenTimeStamp, unsigned int *vgpuProcessSamplesCount, nvmlVgpuProcessUtilizationSample_t *utilizationSamples); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingMode))(nvmlVgpuInstance_t vgpuInstance, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingPids))(nvmlVgpuInstance_t vgpuInstance, unsigned int *count, unsigned int *pids); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingStats))(nvmlVgpuInstance_t vgpuInstance, unsigned int pid, nvmlAccountingStats_t *stats); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceClearAccountingPids))(nvmlVgpuInstance_t vgpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetBlacklistDeviceCount))(unsigned int *deviceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetBlacklistDeviceInfoByIndex))(unsigned int index, nvmlBlacklistDeviceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetMigMode))(nvmlDevice_t device, unsigned int mode, nvmlReturn_t *activationStatus); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMigMode))(nvmlDevice_t device, unsigned int *currentMode, unsigned int *pendingMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceProfileInfo))(nvmlDevice_t device, unsigned int profile, nvmlGpuInstanceProfileInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstancePossiblePlacements))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstancePlacement_t *placements, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceRemainingCapacity))(nvmlDevice_t device, unsigned int profileId, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceCreateGpuInstance))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstance_t *gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceDestroy))(nvmlGpuInstance_t gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstances))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstance_t *gpuInstances, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceById))(nvmlDevice_t device, unsigned int id, nvmlGpuInstance_t *gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetInfo))(nvmlGpuInstance_t gpuInstance, nvmlGpuInstanceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceProfileInfo))(nvmlGpuInstance_t gpuInstance, unsigned int profile, unsigned int engProfile, nvmlComputeInstanceProfileInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceRemainingCapacity))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceCreateComputeInstance))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, nvmlComputeInstance_t *computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlComputeInstanceDestroy))(nvmlComputeInstance_t computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstances))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, nvmlComputeInstance_t *computeInstances, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceById))(nvmlGpuInstance_t gpuInstance, unsigned int id, nvmlComputeInstance_t *computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlComputeInstanceGetInfo))(nvmlComputeInstance_t computeInstance, nvmlComputeInstanceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceIsMigDeviceHandle))(nvmlDevice_t device, unsigned int *isMigDevice); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceId))(nvmlDevice_t device, unsigned int *id); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeInstanceId))(nvmlDevice_t device, unsigned int *id); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxMigDeviceCount))(nvmlDevice_t device, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMigDeviceHandleByIndex))(nvmlDevice_t device, unsigned int index, nvmlDevice_t *migDevice); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDeviceHandleFromMigDeviceHandle))(nvmlDevice_t migDevice, nvmlDevice_t *device); + + + +struct NVMLFunctionTable +{ + FUNC_P(nvmlInit); + //FUNC_P(nvmlInitWithFlags); + FUNC_P(nvmlShutdown); + //FUNC_P(nvmlErrorString); + //FUNC_P(nvmlSystemGetDriverVersion); + //FUNC_P(nvmlSystemGetNVMLVersion); + //FUNC_P(nvmlSystemGetCudaDriverVersion); + //FUNC_P(nvmlSystemGetCudaDriverVersion_v2); + //FUNC_P(nvmlSystemGetProcessName); + //FUNC_P(nvmlUnitGetCount); + //FUNC_P(nvmlUnitGetHandleByIndex); + //FUNC_P(nvmlUnitGetUnitInfo); + //FUNC_P(nvmlUnitGetLedState); + //FUNC_P(nvmlUnitGetPsuInfo); + //FUNC_P(nvmlUnitGetTemperature); + //FUNC_P(nvmlUnitGetFanSpeedInfo); + //FUNC_P(nvmlUnitGetDevices); + //FUNC_P(nvmlSystemGetHicVersion); + //FUNC_P(nvmlDeviceGetCount); + //FUNC_P(nvmlDeviceGetAttributes); + //FUNC_P(nvmlDeviceGetHandleByIndex); + //FUNC_P(nvmlDeviceGetHandleBySerial); + //FUNC_P(nvmlDeviceGetHandleByUUID); + FUNC_P(nvmlDeviceGetHandleByPciBusId); + //FUNC_P(nvmlDeviceGetName); + //FUNC_P(nvmlDeviceGetBrand); + //FUNC_P(nvmlDeviceGetIndex); + //FUNC_P(nvmlDeviceGetSerial); + //FUNC_P(nvmlDeviceGetMemoryAffinity); + //FUNC_P(nvmlDeviceGetCpuAffinityWithinScope); + //FUNC_P(nvmlDeviceGetCpuAffinity); + //FUNC_P(nvmlDeviceSetCpuAffinity); + //FUNC_P(nvmlDeviceClearCpuAffinity); + //FUNC_P(nvmlDeviceGetTopologyCommonAncestor); + //FUNC_P(nvmlDeviceGetTopologyNearestGpus); + //FUNC_P(nvmlSystemGetTopologyGpuSet); + //FUNC_P(nvmlDeviceGetP2PStatus); + //FUNC_P(nvmlDeviceGetUUID); + //FUNC_P(nvmlVgpuInstanceGetMdevUUID); + //FUNC_P(nvmlDeviceGetMinorNumber); + //FUNC_P(nvmlDeviceGetBoardPartNumber); + //FUNC_P(nvmlDeviceGetInforomVersion); + //FUNC_P(nvmlDeviceGetInforomImageVersion); + //FUNC_P(nvmlDeviceGetInforomConfigurationChecksum); + //FUNC_P(nvmlDeviceValidateInforom); + //FUNC_P(nvmlDeviceGetDisplayMode); + //FUNC_P(nvmlDeviceGetDisplayActive); + //FUNC_P(nvmlDeviceGetPersistenceMode); + //FUNC_P(nvmlDeviceGetPciInfo); + //FUNC_P(nvmlDeviceGetMaxPcieLinkGeneration); + //FUNC_P(nvmlDeviceGetMaxPcieLinkWidth); + //FUNC_P(nvmlDeviceGetCurrPcieLinkGeneration); + //FUNC_P(nvmlDeviceGetCurrPcieLinkWidth); + //FUNC_P(nvmlDeviceGetPcieThroughput); + //FUNC_P(nvmlDeviceGetPcieReplayCounter); + //FUNC_P(nvmlDeviceGetClockInfo); + //FUNC_P(nvmlDeviceGetMaxClockInfo); + //FUNC_P(nvmlDeviceGetApplicationsClock); + //FUNC_P(nvmlDeviceGetDefaultApplicationsClock); + //FUNC_P(nvmlDeviceResetApplicationsClocks); + //FUNC_P(nvmlDeviceGetClock); + //FUNC_P(nvmlDeviceGetMaxCustomerBoostClock); + //FUNC_P(nvmlDeviceGetSupportedMemoryClocks); + //FUNC_P(nvmlDeviceGetSupportedGraphicsClocks); + //FUNC_P(nvmlDeviceGetAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceSetAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceSetDefaultAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceGetFanSpeed); + //FUNC_P(nvmlDeviceGetFanSpeed_v2); + //FUNC_P(nvmlDeviceGetTemperature); + //FUNC_P(nvmlDeviceGetTemperatureThreshold); + //FUNC_P(nvmlDeviceGetPerformanceState); + //FUNC_P(nvmlDeviceGetCurrentClocksThrottleReasons); + //FUNC_P(nvmlDeviceGetSupportedClocksThrottleReasons); + //FUNC_P(nvmlDeviceGetPowerState); + //FUNC_P(nvmlDeviceGetPowerManagementMode); + //FUNC_P(nvmlDeviceGetPowerManagementLimit); + //FUNC_P(nvmlDeviceGetPowerManagementLimitConstraints); + //FUNC_P(nvmlDeviceGetPowerManagementDefaultLimit); + //FUNC_P(nvmlDeviceGetPowerUsage); + //FUNC_P(nvmlDeviceGetTotalEnergyConsumption); + //FUNC_P(nvmlDeviceGetEnforcedPowerLimit); + //FUNC_P(nvmlDeviceGetGpuOperationMode); + //FUNC_P(nvmlDeviceGetMemoryInfo); + //FUNC_P(nvmlDeviceGetComputeMode); + //FUNC_P(nvmlDeviceGetCudaComputeCapability); + //FUNC_P(nvmlDeviceGetEccMode); + //FUNC_P(nvmlDeviceGetBoardId); + //FUNC_P(nvmlDeviceGetMultiGpuBoard); + //FUNC_P(nvmlDeviceGetTotalEccErrors); + //FUNC_P(nvmlDeviceGetDetailedEccErrors); + //FUNC_P(nvmlDeviceGetMemoryErrorCounter); + //FUNC_P(nvmlDeviceGetUtilizationRates); + //FUNC_P(nvmlDeviceGetEncoderUtilization); + //FUNC_P(nvmlDeviceGetEncoderCapacity); + //FUNC_P(nvmlDeviceGetEncoderStats); + //FUNC_P(nvmlDeviceGetEncoderSessions); + //FUNC_P(nvmlDeviceGetDecoderUtilization); + //FUNC_P(nvmlDeviceGetFBCStats); + //FUNC_P(nvmlDeviceGetFBCSessions); + //FUNC_P(nvmlDeviceGetDriverModel); + //FUNC_P(nvmlDeviceGetVbiosVersion); + //FUNC_P(nvmlDeviceGetBridgeChipInfo); + //FUNC_P(nvmlDeviceGetComputeRunningProcesses); + //FUNC_P(nvmlDeviceGetGraphicsRunningProcesses); + //FUNC_P(nvmlDeviceOnSameBoard); + //FUNC_P(nvmlDeviceGetAPIRestriction); + //FUNC_P(nvmlDeviceGetSamples); + //FUNC_P(nvmlDeviceGetBAR1MemoryInfo); + //FUNC_P(nvmlDeviceGetViolationStatus); + //FUNC_P(nvmlDeviceGetAccountingMode); + //FUNC_P(nvmlDeviceGetAccountingStats); + //FUNC_P(nvmlDeviceGetAccountingPids); + //FUNC_P(nvmlDeviceGetAccountingBufferSize); + //FUNC_P(nvmlDeviceGetRetiredPages); + //FUNC_P(nvmlDeviceGetRetiredPages_v2); + //FUNC_P(nvmlDeviceGetRetiredPagesPendingStatus); + //FUNC_P(nvmlDeviceGetRemappedRows); + //FUNC_P(nvmlDeviceGetArchitecture); + //FUNC_P(nvmlUnitSetLedState); + //FUNC_P(nvmlDeviceSetPersistenceMode); + //FUNC_P(nvmlDeviceSetComputeMode); + //FUNC_P(nvmlDeviceSetEccMode); + //FUNC_P(nvmlDeviceClearEccErrorCounts); + //FUNC_P(nvmlDeviceSetDriverModel); + //FUNC_P(nvmlDeviceSetGpuLockedClocks); + //FUNC_P(nvmlDeviceResetGpuLockedClocks); + //FUNC_P(nvmlDeviceSetApplicationsClocks); + //FUNC_P(nvmlDeviceSetPowerManagementLimit); + //FUNC_P(nvmlDeviceSetGpuOperationMode); + //FUNC_P(nvmlDeviceSetAPIRestriction); + //FUNC_P(nvmlDeviceSetAccountingMode); + //FUNC_P(nvmlDeviceClearAccountingPids); + FUNC_P(nvmlDeviceGetNvLinkState); + //FUNC_P(nvmlDeviceGetNvLinkVersion); + FUNC_P(nvmlDeviceGetNvLinkCapability); + FUNC_P(nvmlDeviceGetNvLinkRemotePciInfo); + //FUNC_P(nvmlDeviceGetNvLinkErrorCounter); + //FUNC_P(nvmlDeviceResetNvLinkErrorCounters); + //FUNC_P(nvmlDeviceSetNvLinkUtilizationControl); + //FUNC_P(nvmlDeviceGetNvLinkUtilizationControl); + //FUNC_P(nvmlDeviceGetNvLinkUtilizationCounter); + //FUNC_P(nvmlDeviceFreezeNvLinkUtilizationCounter); + //FUNC_P(nvmlDeviceResetNvLinkUtilizationCounter); + //FUNC_P(nvmlEventSetCreate); + //FUNC_P(nvmlDeviceRegisterEvents); + //FUNC_P(nvmlDeviceGetSupportedEventTypes); + //FUNC_P(nvmlEventSetWait); + //FUNC_P(nvmlEventSetFree); + //FUNC_P(nvmlDeviceModifyDrainState); + //FUNC_P(nvmlDeviceQueryDrainState); + //FUNC_P(nvmlDeviceRemoveGpu); + //FUNC_P(nvmlDeviceDiscoverGpus); + //FUNC_P(nvmlDeviceGetFieldValues); + //FUNC_P(nvmlDeviceGetVirtualizationMode); + //FUNC_P(nvmlDeviceGetHostVgpuMode); + //FUNC_P(nvmlDeviceSetVirtualizationMode); + //FUNC_P(nvmlDeviceGetGridLicensableFeatures); + //FUNC_P(nvmlDeviceGetProcessUtilization); + //FUNC_P(nvmlDeviceGetSupportedVgpus); + //FUNC_P(nvmlDeviceGetCreatableVgpus); + //FUNC_P(nvmlVgpuTypeGetClass); + //FUNC_P(nvmlVgpuTypeGetName); + //FUNC_P(nvmlVgpuTypeGetDeviceID); + //FUNC_P(nvmlVgpuTypeGetFramebufferSize); + //FUNC_P(nvmlVgpuTypeGetNumDisplayHeads); + //FUNC_P(nvmlVgpuTypeGetResolution); + //FUNC_P(nvmlVgpuTypeGetLicense); + //FUNC_P(nvmlVgpuTypeGetFrameRateLimit); + //FUNC_P(nvmlVgpuTypeGetMaxInstances); + //FUNC_P(nvmlVgpuTypeGetMaxInstancesPerVm); + //FUNC_P(nvmlDeviceGetActiveVgpus); + //FUNC_P(nvmlVgpuInstanceGetVmID); + //FUNC_P(nvmlVgpuInstanceGetUUID); + //FUNC_P(nvmlVgpuInstanceGetVmDriverVersion); + //FUNC_P(nvmlVgpuInstanceGetFbUsage); + //FUNC_P(nvmlVgpuInstanceGetLicenseStatus); + //FUNC_P(nvmlVgpuInstanceGetType); + //FUNC_P(nvmlVgpuInstanceGetFrameRateLimit); + //FUNC_P(nvmlVgpuInstanceGetEccMode); + //FUNC_P(nvmlVgpuInstanceGetEncoderCapacity); + //FUNC_P(nvmlVgpuInstanceSetEncoderCapacity); + //FUNC_P(nvmlVgpuInstanceGetEncoderStats); + //FUNC_P(nvmlVgpuInstanceGetEncoderSessions); + //FUNC_P(nvmlVgpuInstanceGetFBCStats); + //FUNC_P(nvmlVgpuInstanceGetFBCSessions); + //FUNC_P(nvmlVgpuInstanceGetMetadata); + //FUNC_P(nvmlDeviceGetVgpuMetadata); + //FUNC_P(nvmlGetVgpuCompatibility); + //FUNC_P(nvmlDeviceGetPgpuMetadataString); + //FUNC_P(nvmlGetVgpuVersion); + //FUNC_P(nvmlSetVgpuVersion); + //FUNC_P(nvmlDeviceGetVgpuUtilization); + //FUNC_P(nvmlDeviceGetVgpuProcessUtilization); + //FUNC_P(nvmlVgpuInstanceGetAccountingMode); + //FUNC_P(nvmlVgpuInstanceGetAccountingPids); + //FUNC_P(nvmlVgpuInstanceGetAccountingStats); + //FUNC_P(nvmlVgpuInstanceClearAccountingPids); + //FUNC_P(nvmlGetBlacklistDeviceCount); + //FUNC_P(nvmlGetBlacklistDeviceInfoByIndex); + //FUNC_P(nvmlDeviceSetMigMode); + //FUNC_P(nvmlDeviceGetMigMode); + //FUNC_P(nvmlDeviceGetGpuInstanceProfileInfo); + //FUNC_P(nvmlDeviceGetGpuInstancePossiblePlacements); + //FUNC_P(nvmlDeviceGetGpuInstanceRemainingCapacity); + //FUNC_P(nvmlDeviceCreateGpuInstance); + //FUNC_P(nvmlGpuInstanceDestroy); + //FUNC_P(nvmlDeviceGetGpuInstances); + //FUNC_P(nvmlDeviceGetGpuInstanceById); + //FUNC_P(nvmlGpuInstanceGetInfo); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceProfileInfo); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceRemainingCapacity); + //FUNC_P(nvmlGpuInstanceCreateComputeInstance); + //FUNC_P(nvmlComputeInstanceDestroy); + //FUNC_P(nvmlGpuInstanceGetComputeInstances); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceById); + //FUNC_P(nvmlComputeInstanceGetInfo); + //FUNC_P(nvmlDeviceIsMigDeviceHandle); + //FUNC_P(nvmlDeviceGetGpuInstanceId); + //FUNC_P(nvmlDeviceGetComputeInstanceId); + //FUNC_P(nvmlDeviceGetMaxMigDeviceCount); + //FUNC_P(nvmlDeviceGetMigDeviceHandleByIndex); + //FUNC_P(nvmlDeviceGetDeviceHandleFromMigDeviceHandle); +}; + +#undef FUNC_T_V +#undef FUNC_T + +#undef FUNC_P_V +#undef FUNC_P + +class NVMLImpl +{ +public: + NVMLImpl(); + //~NVMLImpl(); + + bool initFunctionTable(); + +public: + NVMLFunctionTable m_api; + +private: + void* m_handle; // nullptr when the library could not be found +}; + + +#endif // NVML_IMPL_H + diff --git a/apps/MDL_sdf/inc/Options.h b/apps/MDL_sdf/inc/Options.h new file mode 100644 index 00000000..7df288f9 --- /dev/null +++ b/apps/MDL_sdf/inc/Options.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include + +class Options +{ +public: + Options(); + //~Options(); + + bool parseCommandLine(int argc, char *argv[]); + + int getWidth() const; + int getHeight() const; + int getMode() const; + bool getOptimize() const; + std::string getSystem() const; + std::string getScene() const; + +private: + void printUsage(const std::string& argv); + +private: + int m_width; + int m_height; + int m_mode; + bool m_optimize; + std::string m_filenameSystem; + std::string m_filenameScene; +}; + +#endif // OPTIONS_H diff --git a/apps/MDL_sdf/inc/Parser.h b/apps/MDL_sdf/inc/Parser.h new file mode 100644 index 00000000..96ef27ee --- /dev/null +++ b/apps/MDL_sdf/inc/Parser.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef PARSER_H +#define PARSER_H + +#include + +enum ParserTokenType +{ + PTT_UNKNOWN, // Unknown, normally indicates an error. + PTT_ID, // Keywords and identifiers (not a number). + PTT_VAL, // Immediate floating point value. + PTT_STRING, // Filenames and any other identifier in quotation marks. + PTT_EOL, // End of line. + PTT_EOF // End of file. +}; + + +// System and scene file parsing information. +class Parser +{ +public: + Parser(); + //~Parser(); + + bool load(const std::string& filename); + + ParserTokenType getNextToken(std::string& token); + + size_t getSize() const; + std::string::size_type getIndex() const; + unsigned int getLine() const; + +private: + std::string m_source; // System or scene description file contents. + std::string::size_type m_index; // Parser's current character index into m_source. + unsigned int m_line; // Current source code line, one-based for error messages. +}; + +#endif // PARSER_H diff --git a/apps/MDL_sdf/inc/Picture.h b/apps/MDL_sdf/inc/Picture.h new file mode 100644 index 00000000..459a3f65 --- /dev/null +++ b/apps/MDL_sdf/inc/Picture.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// Code in these classes is based on the ILTexLoader.h/.cpp routines inside the NVIDIA nvpro-pipeline ILTexLoader plugin: +// https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp + +#pragma once + +#ifndef PICTURE_H +#define PICTURE_H + +#include + +#include "inc/LoaderIES.h" + +#include +#include + +// Bits for the image handling and texture creation flags. +#define IMAGE_FLAG_1D 0x00000001 +#define IMAGE_FLAG_2D 0x00000002 +#define IMAGE_FLAG_3D 0x00000004 +#define IMAGE_FLAG_CUBE 0x00000008 +// Modifier bits on the above types. +// Layered image (not applicable to 3D) +#define IMAGE_FLAG_LAYER 0x00000010 +// Mipmapped image (ignored when there are no mipmaps provided). +#define IMAGE_FLAG_MIPMAP 0x00000020 + +// FIXME See if there is a better method to store pictures and flags separately +// to be able to build different samplers (texture objects) from the same input image. + +// Special flags for emission textures. Used with IMAGE_FLAG_2D only. +// Only area lights (env, rect) are importance sampled in this unidirectional path tracer. +// Spherical environment map. Wrap clamp on poles. +#define IMAGE_FLAG_ENV 0x00000100 +// Spherical omnidirectional projection map assigned to point lights. Wrap clamp on poles. +#define IMAGE_FLAG_POINT 0x00000200 +// Spherical cap projection assigned to spot lights (not importance sampled). Wrap clamp. +#define IMAGE_FLAG_SPOT 0x00000400 +// Emission texture on a rectangle light (importance sampled). Wrap clamp. +#define IMAGE_FLAG_RECT 0x00000800 +// Spherical (polar grid) emission texture from an IES light profile, applied to point lights. Wrap clamp on poles. +#define IMAGE_FLAG_IES 0x00001000 +// 3D texture with single float SDF distance values. +#define IMAGE_FLAG_SDF 0x00002000 + +struct Image +{ + Image(unsigned int width, unsigned int height, unsigned int depth, int format, int type); + ~Image(); + + unsigned int m_width; + unsigned int m_height; + unsigned int m_depth; + + int m_format; // DevIL image format. + int m_type; // DevIL image component type. + + // Derived values. + unsigned int m_bpp; // bytes per pixel + unsigned int m_bpl; // bytes per scanline + unsigned int m_bps; // bytes per slice (plane) + unsigned int m_nob; // number of bytes (complete image) + + unsigned char* m_pixels; // The pixel data of one image. +}; + + +class Picture +{ +public: + Picture(); + ~Picture(); + + bool load(const std::string& filename, const unsigned int flags); + + bool loadSDF(const unsigned int width, + const unsigned int height, + const unsigned int depth, + const std::string& filename, + const unsigned int flags); + + void clear(); + + // Add an empty new vector of images. Each vector can hold one mipmap chain. Returns the new image index. + unsigned int addImages(); + + // Add a mipmap chain as new vector of images. Returns the new image index. + // pixels and extents are for the LOD 0. mipmaps can be empty. + unsigned int addImages(const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type, + const std::vector& mipmaps, const unsigned int flags); + + // Append a new image LOD to the existing vector of images building a mipmap chain. (No mipmap consistency check here.) + unsigned int addLevel(const unsigned int index, + const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type); + + unsigned int getFlags() const; + void setFlags(const unsigned int flags); + void addFlags(const unsigned int flags); + + unsigned int getNumberOfImages() const; + unsigned int getNumberOfLevels(unsigned int indexImage) const; + const Image* getImageLevel(unsigned int indexImage, unsigned int indexLevel) const; + bool isCubemap() const; + + // This is needed when generating cubemaps without loading them via DevIL. + void setIsCubemap(const bool isCube); + + bool createIES(const IESData& iesData); + + // DEBUG Function to generate all 14 texture targets with RGBA8 images. + void generateRGBA8(unsigned int width, unsigned int height, unsigned int depth, const unsigned int flags); + +private: + void mirrorX(unsigned int index); + void mirrorY(unsigned int index); + + void clearImages(); // Delete all pixel data, all Image pointers and clear the m_images vector. + + bool generateIES(const IESData& ies, + const std::vector& horizontalAngles, + const std::vector& candelas); + +private: + unsigned int m_flags; // The image flags with which this Picture has been loaded. + bool m_isCube; // Track if the picture is a cube map. + std::vector< std::vector > m_images; +}; + +#endif // PICTURE_H diff --git a/apps/MDL_sdf/inc/Rasterizer.h b/apps/MDL_sdf/inc/Rasterizer.h new file mode 100644 index 00000000..16252242 --- /dev/null +++ b/apps/MDL_sdf/inc/Rasterizer.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RASTERIZER_H +#define RASTERIZER_H + +#include +#if defined( _WIN32 ) +#include +#endif + +#include "inc/Timer.h" +#include "inc/TonemapperGUI.h" + +#include + +typedef struct +{ + float u; // u-coordinate in range [0.0, 1.0] + float c[3]; // color, components in range [0.0, 1.0] +} ColorRampElement; + + +class Rasterizer +{ +public: + Rasterizer(const int w, const int h, const int interop); + ~Rasterizer(); + + void reshape(const int w, const int h); + void display(); + + const int getNumDevices() const; + + const unsigned char* getUUID(const unsigned int index) const; + const unsigned char* getLUID() const; + int getNodeMask() const; + + unsigned int getTextureObject() const; + unsigned int getPixelBufferObject() const; + + void setResolution(const int w, const int h); + void setTonemapper(const TonemapperGUI& tm); + +private: + void checkInfoLog(const char *msg, GLuint object); + void initGLSL(); + void updateProjectionMatrix(); + void updateVertexAttributes(); + +private: + int m_width; + int m_height; + int m_interop; + + int m_widthResolution; + int m_heightResolution; + + GLint m_numDevices; // Number of OpenGL devices. Normally 1, unless multicast is enabled. + GLubyte m_deviceUUID[24][GL_UUID_SIZE_EXT]; // Max. 24 devices supported. 16 bytes identifier. + //GLubyte m_driverUUID[GL_UUID_SIZE_EXT]; // 16 bytes identifier, unused. + GLubyte m_deviceLUID[GL_LUID_SIZE_EXT]; // 8 bytes identifier. + GLint m_nodeMask; // Node mask used together with the LUID to identify OpenGL device uniquely. + + GLuint m_hdrTexture; + GLuint m_pbo; + + GLuint m_colorRampTexture; + + GLuint m_glslProgram; + + GLuint m_vboAttributes; + GLuint m_vboIndices; + + GLint m_locAttrPosition; + GLint m_locAttrTexCoord; + GLint m_locProjection; + + GLint m_locSamplerHDR; + GLint m_locSamplerColorRamp; + + // Rasterizer side of the TonemapperGUI data + GLint m_locInvGamma; + GLint m_locColorBalance; + GLint m_locInvWhitePoint; + GLint m_locBurnHighlights; + GLint m_locCrushBlacks; + GLint m_locSaturation; + + Timer m_timer; +}; + +#endif // RASTERIZER_H diff --git a/apps/MDL_sdf/inc/Raytracer.h b/apps/MDL_sdf/inc/Raytracer.h new file mode 100644 index 00000000..87320f45 --- /dev/null +++ b/apps/MDL_sdf/inc/Raytracer.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RAYTRACER_H +#define RAYTRACER_H + +#include "inc/Device.h" +//#include "inc/MaterialGUI.h" +#include "inc/Picture.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/NVMLImpl.h" + +#include "inc/MaterialMDL.h" +#include "inc/CompileResult.h" +#include "inc/ShaderConfiguration.h" + +#include "shaders/system_data.h" + +#include +#include + + + +#include +#include +#include + +// Bitfield encoding for m_peerToPeer: +#define P2P_PCI 1 +#define P2P_TEX 2 +#define P2P_GAS 4 +#define P2P_ENV 8 +#define P2P_MBSDF 16 +#define P2P_IES 32 + + + +class Raytracer +{ +public: + Raytracer(const int maskDevices, + const TypeLight typeEnv, + const int interop, + const unsigned int tex, + const unsigned int pbo, + const size_t sizeArena, + const int p2p); + ~Raytracer(); + + int matchUUID(const char* uuid); + int matchLUID(const char* luid, const unsigned int nodeMask); + + bool enablePeerAccess(); // Calculates peer-to-peer access bit matrix in m_peerConnections and sets the m_islands. + void disablePeerAccess(); // Clear the peer-to-peer islands. Afterwards each device is its own island. + + void synchronize(); // Needed for the benchmark to wait for all asynchronous rendering to have finished. + + void initTextures(const std::map& mapOfPictures); + void initCameras(const std::vector& cameras); + void initLights(const std::vector& lights); + void initScene(std::shared_ptr root, const unsigned int numGeometries); + void initState(const DeviceState& state); + + // Update functions should be replaced with NOP functions in a derived batch renderer because the device functions are fully asynchronous then. + void updateCamera(const int idCamera, const CameraDefinition& camera); + void updateLight(const int idLight, const LightGUI& lightGUI); + //void updateLight(const int idLight, const LightDefinition& light); + void updateMaterial(const int idMaterial, const MaterialMDL* materialMDL); + void updateState(const DeviceState& state); + + unsigned int render(const int mode = 0); // 0 = interactive, 1 = benchmark (fully asynchronous launches) + void updateDisplayTexture(); + const void* getOutputBufferHost(); + + bool initMDL(const std::vector& searchPaths); + void shutdownMDL(); + void initMaterialsMDL(std::vector& materialsMDL); + + bool isEmissiveShader(const int indexShader) const; + +private: + void selectDevices(); + int getDeviceHome(const std::vector& island) const; + void traverseNode(std::shared_ptr node, InstanceData instanceData, float matrix[12]); + bool activeNVLINK(const int home, const int peer) const; + int findActiveDevice(const unsigned int domain, const unsigned int bus, const unsigned int device) const; + + mi::neuraylib::INeuray* load_and_get_ineuray(const char* filename); + mi::Sint32 load_plugin(mi::neuraylib::INeuray* neuray, const char* path); + bool log_messages(mi::neuraylib::IMdl_execution_context* context); + void determineShaderConfiguration(const Compile_result& res, ShaderConfiguration& config); + + void initMaterialMDL(MaterialMDL* materialMDL); + bool compileMaterial(mi::neuraylib::ITransaction* transaction, MaterialMDL* materialMDL, Compile_result& res); + +public: + // Constructor arguments + unsigned int m_maskDevices; // The bitmask with the devices the user selected. + TypeLight m_typeEnv; // Controls the miss program selection. + int m_interop; // Which CUDA-OpenGL interop to use. + unsigned int m_tex; // The OpenGL texture object used for display. + unsigned int m_pbo; // The pixel buffer object when using INTEROP_MODE_PBO. + size_t m_sizeArena; // The default Arena allocation size in mega-bytes, just routed through to Device class. + int m_peerToPeer; // Bitfield encoding for which resources CUDA P2P sharing is allowed: + // Bit 0: Allow sharing via PCI-E, ignores the NVLINK topology, just checks cuDeviceCanAccessPeer(). + // Bit 1: Share material textures (not the HDR environment). + // Bit 2: Share GAS and vertex attribute data. + // Bit 3: Share (optional) HDR environment and its CDF data. + bool m_isValid; + + int m_numDevicesVisible; // The number of visible CUDA devices. (What you can control via the CUDA_VISIBLE_DEVICES environment variable.) + int m_indexDeviceOGL; // The first device which matches with the OpenGL LUID and node mask. -1 when there was no match. + unsigned int m_maskDevicesActive; // The bitmask marking the actually enabled devices. + std::vector m_devicesActive; + + unsigned int m_iterationIndex; // Tracks which sub-frame is currently raytraced. + unsigned int m_samplesPerPixel; // This is samplesSqrt squared. Rendering end-condition is: m_iterationIndex == m_samplesPerPixel. + + std::vector m_peerConnections; // Bitfield indicating peer-to-peer access between devices. Indexing is m_peerConnections[home] & (1 << peer) + std::vector< std::vector > m_islands; // Vector with vector of active device indices (not ordinals) building a peer-to-peer island. + + std::vector m_geometryData; // The geometry device data. (Either per P2P island when sharing GAS, or per device when not sharing.) + + NVMLImpl m_nvml; + +private: + // MDL specific things. + +#ifdef MI_PLATFORM_WINDOWS + HMODULE m_dso_handle = 0; +#else + void* m_dso_handle = 0; +#endif + + mi::base::Handle m_logger; + + // The last error message from MDL SDK. + std::string m_last_mdl_error; + + mi::base::Handle m_neuray; + mi::base::Handle m_mdl_compiler; + mi::base::Handle m_logging_config; + mi::base::Handle m_mdl_config; + mi::base::Handle m_database; + mi::base::Handle m_global_scope; + mi::base::Handle m_mdl_factory; + mi::base::Handle m_execution_context; + mi::base::Handle m_mdl_backend; + mi::base::Handle m_image_api; + + // Maps a compiled material hash to a shader code cache index == shader configuration index. + std::map m_mapMaterialHashToShaderIndex; + // These two vectors have the same size and implement shader reuse (references with the same MDL material). + std::vector> m_shaders; + std::vector m_shaderConfigurations; +}; + +#endif // RAYTRACER_H diff --git a/apps/MDL_sdf/inc/SceneGraph.h b/apps/MDL_sdf/inc/SceneGraph.h new file mode 100644 index 00000000..32add6c1 --- /dev/null +++ b/apps/MDL_sdf/inc/SceneGraph.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SCENEGRAPH_H +#define SCENEGRAPH_H + +#include + +#include "shaders/curve_attributes.h" +#include "shaders/vertex_attributes.h" +#include "shaders/sdf_attributes.h" + +#include "shaders/vector_math.h" + +#include "inc/LightGUI.h" + +#include +#include +#include + +namespace sg +{ + + enum NodeType + { + NT_GROUP, + NT_INSTANCE, + NT_TRIANGLES, + NT_CURVES, + NT_SDF + }; + + class Node + { + public: + Node(const unsigned int id); + //~Node(); + + virtual sg::NodeType getType() const = 0; + + unsigned int getId() const + { + return m_id; + } + + private: + unsigned int m_id; + }; + + + class Triangles : public Node + { + public: + Triangles(const unsigned int id); + //~Triangles(); + + sg::NodeType getType() const; + + void createBox(); + void createPlane(const unsigned int tessU, const unsigned int tessV, const unsigned int upAxis); + void createSphere(const unsigned int tessU, const unsigned int tessV, const float radius, const float maxTheta); + void createTorus(const unsigned int tessU, const unsigned int tessV, const float innerRadius, const float outerRadius); + + void calculateLightArea(LightGUI& lightGUI) const; + + void setAttributes(const std::vector& attributes); + const std::vector& getAttributes() const; + + void setIndices(const std::vector& indices); + const std::vector& getIndices() const; + + private: + std::vector m_attributes; + std::vector m_indices; // If m_indices.size() == 0, m_attributes are independent primitives (not actually supported in this renderer implementation!) + }; + + class Curves : public Node + { + public: + Curves(const unsigned int id); + //~Curves(); + + sg::NodeType getType() const; + + bool createHair(std::string const& filename, const float scale); + + void setAttributes(std::vector const& attributes); + std::vector const& getAttributes() const; + + void setIndices(std::vector const&); + std::vector const& getIndices() const; + + private: + std::vector m_attributes; + std::vector m_indices; + }; + + class SignedDistanceField : public Node + { + public: + SignedDistanceField(const unsigned int id); + //~SignedDistanceField(); + + sg::NodeType getType() const; + + bool createSDF(const float3 minimum, + const float3 maximum, + const float lipschitz, + const unsigned int width, + const unsigned int height, + const unsigned int depth, + const std::string& filename); + + void setAttributes(std::vector const& attributes); + std::vector const& getAttributes() const; + + std::string getFilename() const; + + //void setIndices(std::vector const&); + //std::vector const& getIndices() const; + + private: + std::vector m_attributes; + //std::vector m_indices; + std::string m_filename; // Filename of the SDF 3D texture. Used to lookup in m_mapPictures. + }; + + + class Instance : public Node + { + public: + Instance(const unsigned int id); + //~Instance(); + + sg::NodeType getType() const; + + void setTransform(const float m[12]); + const float* getTransform() const; + + void setChild(std::shared_ptr node); + std::shared_ptr getChild(); + + void setMaterial(const int index); + int getMaterial() const; + + void setLight(const int index); + int getLight() const; + + private: + int m_material; + int m_light; + float m_matrix[12]; + std::shared_ptr m_child; // An Instance can either hold a Group or Triangles as child. + }; + + class Group : public Node + { + public: + Group(const unsigned int id); + //~Group(); + + sg::NodeType getType() const; + + void addChild(std::shared_ptr instance); // Groups can only hold Instances. + + size_t getNumChildren() const; + std::shared_ptr getChild(size_t index); + + private: + std::vector< std::shared_ptr > m_children; + }; + +} // namespace sg + +#endif // SCENEGRAPH_H diff --git a/apps/MDL_sdf/inc/ShaderConfiguration.h b/apps/MDL_sdf/inc/ShaderConfiguration.h new file mode 100644 index 00000000..fd59689a --- /dev/null +++ b/apps/MDL_sdf/inc/ShaderConfiguration.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SHADER_CONFIGURATION_H +#define SHADER_CONFIGURATION_H + +#include +#include + +// This defines the host side shader configuration and is used for code reuse. +// There are as many shader configurations as there are different MDL compiled material hash values. +// This holds all boolean values and constants a renderer will need. +// This will be converted to a DeviceShaderConfiguration structure +// which contains all direct callable indices and the constant parameter values. +struct ShaderConfiguration +{ + // The state of the expressions: + bool is_thin_walled_constant; + bool is_surface_bsdf_valid; + bool is_backface_bsdf_valid; + bool is_surface_edf_valid; + bool is_surface_intensity_constant; + bool is_surface_intensity_mode_constant; + bool is_backface_edf_valid; + bool is_backface_intensity_constant; + bool is_backface_intensity_mode_constant; + bool use_backface_edf; + bool use_backface_intensity; + bool use_backface_intensity_mode; + bool is_ior_constant; + bool is_vdf_valid; + bool is_absorption_coefficient_constant; + bool use_volume_absorption; + bool is_scattering_coefficient_constant; + bool is_directional_bias_constant; + bool use_volume_scattering; + bool is_cutout_opacity_constant; + bool use_cutout_opacity; + bool is_hair_bsdf_valid; + + // The constant expression values: + bool thin_walled; + mi::math::Color surface_intensity; + mi::Sint32 surface_intensity_mode; + mi::math::Color backface_intensity; + mi::Sint32 backface_intensity_mode; + mi::math::Color ior; + mi::math::Color absorption_coefficient; + mi::math::Color scattering_coefficient; + mi::Float32 directional_bias; + mi::Float32 cutout_opacity; + + bool isEmissive() const + { + const bool surfaceEmissive = is_surface_edf_valid && (!is_surface_intensity_constant || (is_surface_intensity_constant && (0.0f < surface_intensity[0] || 0.0f < surface_intensity[1] || 0.0f < surface_intensity[2]))); + const bool backfaceEmissive = is_backface_edf_valid && (!is_backface_intensity_constant || (is_backface_intensity_constant && (0.0f < backface_intensity[0] || 0.0f < backface_intensity[1] || 0.0f < backface_intensity[2]))); + const bool thinWalled = !is_thin_walled_constant || (is_thin_walled_constant && thin_walled); + + return surfaceEmissive || (thinWalled && backfaceEmissive); + } + +}; + +#endif // SHADER_CONFIGURATION_H diff --git a/apps/MDL_sdf/inc/Texture.h b/apps/MDL_sdf/inc/Texture.h new file mode 100644 index 00000000..f298ff98 --- /dev/null +++ b/apps/MDL_sdf/inc/Texture.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TEXTURE_H +#define TEXTURE_H + +// Always include this before any OptiX headers. +#include +#include + +#include "inc/Picture.h" + +#include +#include + +// Bitfield encoding of the texture channels. +// These are used to remap user format and user data to the internal format. +// Each four bits hold the channel index of red, green, blue, alpha, and luminance. +// (encoding >> ENC_*_SHIFT) & ENC_MASK gives the channel index if the result is less than 4. +// That encoding allows to automatically swap red and blue, map luminance to RGB (not the other way round though!), +// fill in alpha with input data or force it to one if required. +// 49 remapper functions take care to convert the data types including fixed-point adjustments. + +#define ENC_MASK 0xF + +#define ENC_RED_SHIFT 0 +#define ENC_RED_0 ( 0u << ENC_RED_SHIFT) +#define ENC_RED_1 ( 1u << ENC_RED_SHIFT) +#define ENC_RED_2 ( 2u << ENC_RED_SHIFT) +#define ENC_RED_3 ( 3u << ENC_RED_SHIFT) +#define ENC_RED_NONE (15u << ENC_RED_SHIFT) + +#define ENC_GREEN_SHIFT 4 +#define ENC_GREEN_0 ( 0u << ENC_GREEN_SHIFT) +#define ENC_GREEN_1 ( 1u << ENC_GREEN_SHIFT) +#define ENC_GREEN_2 ( 2u << ENC_GREEN_SHIFT) +#define ENC_GREEN_3 ( 3u << ENC_GREEN_SHIFT) +#define ENC_GREEN_NONE (15u << ENC_GREEN_SHIFT) + +#define ENC_BLUE_SHIFT 8 +#define ENC_BLUE_0 ( 0u << ENC_BLUE_SHIFT) +#define ENC_BLUE_1 ( 1u << ENC_BLUE_SHIFT) +#define ENC_BLUE_2 ( 2u << ENC_BLUE_SHIFT) +#define ENC_BLUE_3 ( 3u << ENC_BLUE_SHIFT) +#define ENC_BLUE_NONE (15u << ENC_BLUE_SHIFT) + +#define ENC_ALPHA_SHIFT 12 +#define ENC_ALPHA_0 ( 0u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_1 ( 1u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_2 ( 2u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_3 ( 3u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_NONE (15u << ENC_ALPHA_SHIFT) + +#define ENC_LUM_SHIFT 16 +#define ENC_LUM_0 ( 0u << ENC_LUM_SHIFT) +#define ENC_LUM_1 ( 1u << ENC_LUM_SHIFT) +#define ENC_LUM_2 ( 2u << ENC_LUM_SHIFT) +#define ENC_LUM_3 ( 3u << ENC_LUM_SHIFT) +#define ENC_LUM_NONE (15u << ENC_LUM_SHIFT) + +#define ENC_CHANNELS_SHIFT 20 +#define ENC_CHANNELS_1 (1u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_2 (2u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_3 (3u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_4 (4u << ENC_CHANNELS_SHIFT) + +#define ENC_TYPE_SHIFT 24 +// The image converter's 7x7 remapper functions are indexed with these indices 0 to 6! +#define ENC_TYPE_CHAR ( 0u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_CHAR ( 1u << ENC_TYPE_SHIFT) +#define ENC_TYPE_SHORT ( 2u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_SHORT ( 3u << ENC_TYPE_SHIFT) +#define ENC_TYPE_INT ( 4u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_INT ( 5u << ENC_TYPE_SHIFT) +#define ENC_TYPE_FLOAT ( 6u << ENC_TYPE_SHIFT) +// FIXME Half type is not yet supported by the 7x7 remapper functions! +// Means half input data only works for natively supported 1-, 2-, or 4-components which will take the memcpy() path inside the converter. +// This is currently used for SDF 3D textures which are 1-component half textures. +#define ENC_TYPE_HALF ( 7u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNDEFINED (15u << ENC_TYPE_SHIFT) + +// Flags to indicate that special handling is required. +#define ENC_MISC_SHIFT 28 +#define ENC_FIXED_POINT (1u << ENC_MISC_SHIFT) +#define ENC_ALPHA_ONE (2u << ENC_MISC_SHIFT) +// Highest bit set means invalid encoding. +#define ENC_INVALID (8u << ENC_MISC_SHIFT) + + +class Device; + +class Texture +{ +public: + Texture(Device* device); + ~Texture(); + + size_t destroy(Device* device); + + void setTextureDescription(const CUDA_TEXTURE_DESC& descr); + + void setAddressMode(CUaddress_mode s, CUaddress_mode t, CUaddress_mode r); + void setFilterMode(CUfilter_mode filter, CUfilter_mode filterMipmap); + void setReadMode(bool asInteger); + void setSRGB(bool srgb); + void setBorderColor(float r, float g, float b, float a); + void setNormalizedCoords(bool normalized); + void setMaxAnisotropy(unsigned int aniso); + void setMipmapLevelBiasMinMax(float bias, float minimum, float maximum); + + bool create(const Picture* picture, const unsigned int flags); + bool create(const Texture* shared); + + bool update(const Picture* picture); + + Device* getOwner() const; + + unsigned int getWidth() const; + unsigned int getHeight() const; + unsigned int getDepth() const; + + cudaTextureObject_t getTextureObject() const; + + size_t getSizeBytes() const; // Texture memory tracking of CUarrays and CUmipmappedArrays in bytes sent to the cuMemCpy3D(). + + // Specific to emission texture handling. + void calculateSphericalCDF(const float* rgba); // For importance sampling of spherical environment lights. + void calculateRectangularCDF(const float* rgba); // For importance-sampling of textured rectangle lights. + + CUdeviceptr getCDF_U() const; + CUdeviceptr getCDF_V() const; + float getIntegral() const; + +private: + bool create1D(const Picture* picture); + bool create2D(const Picture* picture); + bool create3D(const Picture* picture); + bool createCube(const Picture* picture); + + bool update1D(const Picture* picture); + bool update2D(const Picture* picture); + bool update3D(const Picture* picture); + bool updateCube(const Picture* picture); + +private: + Device* m_owner; // The device which created this Texture. Needed for peer-to-peer sharing, resp. for proper destruction. + + unsigned int m_width; + unsigned int m_height; + unsigned int m_depth; + + unsigned int m_flags; + + unsigned int m_encodingHost; + unsigned int m_encodingDevice; + + CUDA_ARRAY3D_DESCRIPTOR m_descArray3D; + size_t m_sizeBytesPerElement; + + CUDA_RESOURCE_DESC m_resourceDescription; // For the final texture object creation. + + CUDA_TEXTURE_DESC m_textureDescription; // This contains all texture parameters which can be set individually or as a whole. + + // Note that the CUarray or CUmipmappedArray are shared among peer devices, not the texture object! + // This needs to be created per device, which happens in the two create() functions. + CUtexObject m_textureObject; + + // Only one of these is ever used per texture. + CUarray m_d_array; + CUmipmappedArray m_d_mipmappedArray; + + // How much memory the CUarray or CUmipmappedArray required in bytes input to cuMemcpy3d() + // (without m_deviceAttribute.textureAlignment or potential row padding on the device). + size_t m_sizeBytesArray; + + // Specific to emission textures on spherical environment and rectangular lights. + CUdeviceptr m_d_cdfU; + CUdeviceptr m_d_cdfV; + float m_integral; +}; + +#endif // TEXTURE_H diff --git a/apps/MDL_sdf/inc/Timer.h b/apps/MDL_sdf/inc/Timer.h new file mode 100644 index 00000000..6ed47971 --- /dev/null +++ b/apps/MDL_sdf/inc/Timer.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// This code is part of the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + +#pragma once + +#ifndef TIMER_H +#define TIMER_H + +#if defined(_WIN32) + #include +#else + #include +#endif + + +/*! \brief A simple timer class. + * This timer class can be used on Windows and Linux systems to + * measure time intervals in seconds. + * The timer can be started and stopped several times and accumulates + * time elapsed between the start() and stop() calls. */ +class Timer +{ +public: + //! Default constructor. Constructs a Timer, but does not start it yet. + Timer(); + + //! Default destructor. + ~Timer(); + + //! Starts the timer. + void start(); + + //! Stops the timer. + void stop(); + + //! Resets the timer. + void reset(); + + //! Resets the timer and starts it. + void restart(); + + //! Returns the current time in seconds. + double getTime() const; + + //! Return whether the timer is still running. + bool isRunning() const { return m_running; } + +private: +#if defined(_WIN32) + typedef LARGE_INTEGER Time; +#else + typedef timeval Time; +#endif + +private: + double calcDuration(Time begin, Time end) const; + +private: +#if defined(_WIN32) + LARGE_INTEGER m_freq; +#endif + Time m_begin; + bool m_running; + double m_seconds; +}; + +#endif // TIMER_H diff --git a/apps/MDL_sdf/inc/TonemapperGUI.h b/apps/MDL_sdf/inc/TonemapperGUI.h new file mode 100644 index 00000000..a1dfdb08 --- /dev/null +++ b/apps/MDL_sdf/inc/TonemapperGUI.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TONEMAPPER_GUI_H +#define TONEMAPPER_GUI_H + +struct TonemapperGUI +{ + float gamma; + float whitePoint; + float colorBalance[3]; + float burnHighlights; + float crushBlacks; + float saturation; + float brightness; +}; + +#endif // TONEMAPPER_GUI_H diff --git a/apps/MDL_sdf/shaders/camera_definition.h b/apps/MDL_sdf/shaders/camera_definition.h new file mode 100644 index 00000000..b1a83393 --- /dev/null +++ b/apps/MDL_sdf/shaders/camera_definition.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CAMERA_DEFINITION_H +#define CAMERA_DEFINITION_H + +struct CameraDefinition +{ + float3 P; + float3 U; + float3 V; + float3 W; +}; + +#endif // CAMERA_DEFINITION_H diff --git a/apps/MDL_sdf/shaders/compositor.cu b/apps/MDL_sdf/shaders/compositor.cu new file mode 100644 index 00000000..e6634358 --- /dev/null +++ b/apps/MDL_sdf/shaders/compositor.cu @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "config.h" + +#include "compositor_data.h" +#include "vector_math.h" +#include "half_common.h" + +// Compositor kernel to copy the tiles in the texelBuffer into the final outputBuffer location. +extern "C" __global__ void compositor(CompositorData* args) +{ + const unsigned int xLaunch = blockDim.x * blockIdx.x + threadIdx.x; + const unsigned int yLaunch = blockDim.y * blockIdx.y + threadIdx.y; + + if (yLaunch < args->resolution.y) + { + // First calculate block coordinates of this launch index. + // That is the launch index divided by the tile dimensions. (No operator>>() on vectors?) + const unsigned int xBlock = xLaunch >> args->tileShift.x; + const unsigned int yBlock = yLaunch >> args->tileShift.y; + + // Each device needs to start at a different column and each row should start with a different device. + const unsigned int xTile = xBlock * args->deviceCount + ((args->deviceIndex + yBlock) % args->deviceCount); + + // The horizontal pixel coordinate is: tile coordinate * tile width + launch index % tile width. + const unsigned int xPixel = xTile * args->tileSize.x + (xLaunch & (args->tileSize.x - 1)); // tileSize needs to be power-of-two for this modulo operation. + + if (xPixel < args->resolution.x) + { +#if USE_FP32_OUTPUT + const float4 *src = reinterpret_cast(args->tileBuffer); + float4 *dst = reinterpret_cast(args->outputBuffer); +#else + const Half4 *src = reinterpret_cast(args->tileBuffer); + Half4 *dst = reinterpret_cast(args->outputBuffer); +#endif + // The src location needs to be calculated with the original launch width, because gridDim.x * blockDim.x might be different. + dst[yLaunch * args->resolution.x + xPixel] = src[yLaunch * args->launchWidth + xLaunch]; // Copy one pixel per launch index. + } + } +} diff --git a/apps/MDL_sdf/shaders/compositor_data.h b/apps/MDL_sdf/shaders/compositor_data.h new file mode 100644 index 00000000..becf6257 --- /dev/null +++ b/apps/MDL_sdf/shaders/compositor_data.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef COMPOSITOR_DATA_H +#define COMPOSITOR_DATA_H + +#include + +struct CompositorData +{ + // 8 byte alignment + CUdeviceptr outputBuffer; + CUdeviceptr tileBuffer; + + int2 resolution; // The actual rendering resolution. Independent from the launch dimensions for some rendering strategies. + int2 tileSize; // Example: make_int2(8, 4) for 8x4 tiles. Must be a power of two to make the division a right-shift. + int2 tileShift; // Example: make_int2(3, 2) for the integer division by tile size. That actually makes the tileSize redundant. + + // 4 byte alignment + int launchWidth; // The orignal launch width. Needed to calculate the source data index. The compositor launch gridDim.x * blockDim.x might be different! + int deviceCount; // Number of devices doing the rendering. + int deviceIndex; // Device index to be able to distinguish the individual devices in a multi-GPU environment. +}; + +#endif // COMPOSITOR_DATA_H diff --git a/apps/MDL_sdf/shaders/config.h b/apps/MDL_sdf/shaders/config.h new file mode 100644 index 00000000..bd81cf55 --- /dev/null +++ b/apps/MDL_sdf/shaders/config.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// This header with defines is included in all shaders +// to be able to switch different code paths at a central location. +// Changing any setting here will rebuild the whole solution. + +#pragma once + +#ifndef CONFIG_H +#define CONFIG_H + +// Used to shoot rays without distance limit. +#define RT_DEFAULT_MAX 1.e27f + +// Scales the m_sceneEpsilonFactor to give the effective SystemData::sceneEpsilon. +#define SCENE_EPSILON_SCALE 1.0e-7f + +// Prevent that division by very small floating point values results in huge values, for example dividing by pdf. +#define DENOMINATOR_EPSILON 1.0e-6f + +// 0 == All debug features disabled. Code optimization level on maximum. (Benchmark only in this mode!) +// 1 == All debug features enabled. Code generated with full debug info. (Really only for debugging, big performance hit!) +#define USE_DEBUG_EXCEPTIONS 0 + +// 0 == Disable clock() usage and time view display. +// 1 == Enable clock() usage and time view display. +// Requires USE_SHADER_EXECUTION_REORDERING == 0, otherwise the clock() calls aren't from the same thread. and the result will be blue-white garbage. +#define USE_TIME_VIEW 0 + +// The m_clockFactor GUI value is scaled by this. +// With a default of m_clockFactor = 1000 this means a million clocks will be value 1.0 in the alpha channel +// which is used as 1D texture coordinate inside the GLSL display shader and results in white in the temperature ramp texture. +#define CLOCK_FACTOR_SCALE 1.0e-9f + +// These defines are used in Application, Rasterizer, Raytracer and Device. This is the only header included by all. +#define INTEROP_MODE_OFF 0 +#define INTEROP_MODE_TEX 1 +#define INTEROP_MODE_PBO 2 + +// 0 = RGBA16F format rendering +// 1 = RGBA32F format rendering +#define USE_FP32_OUTPUT 1 + +// The number of supported texture coordinate slots inside MDL shaders (state::texture_*(i)). +// This makes the Mdl_state bigger which will cost performance! +// hair_bsdf() requires two texture coordinates to communicate the intersection results +// and a per fiber texture coordinate which can be used to color each hair individually. +// That's the whole reason for this define. +// HACK The renderer's vertex attributes are not actually managing two distinct texture coordinates. +// The Mdl_state will simply duplicate the data of texture coordinate slot 0 to slot 1 everywhere except for the hair_bsdf(). +#define NUM_TEXTURE_SPACES 2 + +// The number of float4 elements inside the texture_results cache. Default is 16. +// Used to configure the MDL backend's code generation with set_option("num_texture_results", ...) +// This value influences how many things can be precalculated inside the init() function. +// If the number of result elements in this array is lower than what is required, +// the expressions for the remaining results will be compiled into the sample() and evaluate() functions +// which will make the compilation and runtime performance slower. +// For very resource-heavy materials, experiment with bigger values. +#define NUM_TEXTURE_RESULTS 16 + +// Switch the MDL-SDK image plugin between OpenImageIo and FreeImage. +// The open-source MDL-SDK 2023 (367100.2992) release on github.com switched to the OpenImageIO plugin by default +// and doesn't build the FreeImage plugin unless the CMake variable MDL_BUILD_FREEIMAGE_PLUGIN is enabled. +// 0 = Load plugin library nv_freeimage. +// 1 = Load plugin library nv_openimageio. +#define USE_OPENIMAGEIO_PLUGIN 1 + +// PERF Toggle Shader Execution Reordering (SER) feature. +// This affects Ada generation GPUs only at this time (2023). +// 0 == Do not use SER. +// 1 == Use SER when OptiX SDK 8.0.0 or newer is used. +// PERF The MDL_sdf example scene is faster without SER! +#define USE_SHADER_EXECUTION_REORDERING 0 + +#endif // CONFIG_H diff --git a/apps/MDL_sdf/shaders/curve.h b/apps/MDL_sdf/shaders/curve.h new file mode 100644 index 00000000..461119cc --- /dev/null +++ b/apps/MDL_sdf/shaders/curve.h @@ -0,0 +1,438 @@ +// +// Copyright (c) 2021-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once + +#ifndef CURVE_H +#define CURVE_H + +#include "config.h" + +#include "vector_math.h" + +// First order polynomial interpolator +// +struct LinearInterpolator +{ + __device__ __forceinline__ LinearInterpolator() + { + } + + __device__ __forceinline__ void initialize(const float4* q) + { + p[0] = q[0]; + p[1] = q[1] - q[0]; + } + + __device__ __forceinline__ float4 position4(float u) const + { + return p[0] + u * p[1]; // Horner scheme + } + + __device__ __forceinline__ float3 position3(float u) const + { + return make_float3(position4(u)); + } + + __device__ __forceinline__ float radius(const float& u) const + { + return position4(u).w; + } + + __device__ __forceinline__ float4 velocity4(float u) const + { + return p[1]; + } + + __device__ __forceinline__ float3 velocity3(float u) const + { + return make_float3(velocity4(u)); + } + + __device__ __forceinline__ float derivative_of_radius(float u) const + { + return velocity4(u).w; + } + + __device__ __forceinline__ float3 acceleration3(float u) const + { + return make_float3(0.f); + } + __device__ __forceinline__ float4 acceleration4(float u) const + { + return make_float4(0.f); + } + + + float4 p[2]; +}; + + +// +// Second order polynomial interpolator +// +struct QuadraticInterpolator +{ + __device__ __forceinline__ QuadraticInterpolator() + { + } + + __device__ __forceinline__ void initializeFromBSpline(const float4* q) + { + // Bspline-to-Poly = Matrix([[1/2, -1, 1/2], + // [-1, 1, 0], + // [1/2, 1/2, 0]]) + p[0] = (q[0] - 2.0f * q[1] + q[2]) / 2.0f; + p[1] = (-2.0f * q[0] + 2.0f * q[1]) / 2.0f; + p[2] = (q[0] + q[1]) / 2.0f; + } + + __device__ __forceinline__ void export2BSpline(float4 bs[3]) const + { + // inverse of initializeFromBSpline + // Bspline-to-Poly = Matrix([[1/2, -1, 1/2], + // [-1, 1, 0], + // [1/2, 1/2, 0]]) + // invert to get: + // Poly-to-Bspline = Matrix([[0, -1/2, 1], + // [0, 1/2, 1], + // [2, 3/2, 1]]) + bs[0] = p[0] - p[1] / 2; + bs[1] = p[0] + p[1] / 2; + bs[2] = p[0] + 1.5f * p[1] + 2 * p[2]; + } + + __device__ __forceinline__ float4 position4(float u) const + { + return (p[0] * u + p[1]) * u + p[2]; // Horner scheme + } + + __device__ __forceinline__ float3 position3(float u) const + { + return make_float3(position4(u)); + } + + __device__ __forceinline__ float radius(float u) const + { + return position4(u).w; + } + + __device__ __forceinline__ float4 velocity4(float u) const + { + return 2.0f * p[0] * u + p[1]; + } + + __device__ __forceinline__ float3 velocity3(float u) const + { + return make_float3(velocity4(u)); + } + + __device__ __forceinline__ float derivative_of_radius(float u) const + { + return velocity4(u).w; + } + + __device__ __forceinline__ float4 acceleration4(float u) const + { + return 2.0f * p[0]; + } + + __device__ __forceinline__ float3 acceleration3(float u) const + { + return make_float3(acceleration4(u)); + } + + + float4 p[3]; +}; + +// +// Third order polynomial interpolator +// +// Storing {p0, p1, p2, p3} for evaluation: +// P(u) = p0 * u^3 + p1 * u^2 + p2 * u + p3 +// +struct CubicInterpolator +{ + __device__ __forceinline__ CubicInterpolator() + { + } + // TODO: Initialize from polynomial weights. Check that sample doesn't rely on + // legacy behavior. + // __device__ __forceinline__ CubicBSplineSegment( const float4* q ) { initializeFromBSpline( q ); } + + + __device__ __forceinline__ void initializeFromBSpline(const float4* q) + { + // Bspline-to-Poly = Matrix([[-1/6, 1/2, -1/2, 1/6], + // [ 1/2, -1, 1/2, 0], + // [-1/2, 0, 1/2, 0], + // [ 1/6, 2/3, 1/6, 0]]) + // Original: + //p[0] = ( q[0] * ( -1.0f ) + q[1] * ( 3.0f ) + q[2] * ( -3.0f ) + q[3] ) / 6.0f; + //p[1] = ( q[0] * ( 3.0f ) + q[1] * ( -6.0f ) + q[2] * ( 3.0f ) ) / 6.0f; + //p[2] = ( q[0] * ( -3.0f ) + q[2] * ( 3.0f ) ) / 6.0f; + //p[3] = ( q[0] * ( 1.0f ) + q[1] * ( 4.0f ) + q[2] * ( 1.0f ) ) / 6.0f; + // Optimized: + const float o_s = 1.0f / 6.0f; + p[0] = (q[3] - q[0]) * o_s + (q[1] - q[2]) * 0.5f; + p[1] = (q[0] + q[2]) * 0.5f - q[1]; + p[2] = (q[2] - q[0]) * 0.5f; + p[3] = (q[0] + q[1] * 4.0f + q[2]) * o_s; + } + + __device__ __forceinline__ void export2BSpline(float4 bs[4]) const + { + // inverse of initializeFromBSpline + // Bspline-to-Poly = Matrix([[-1/6, 1/2, -1/2, 1/6], + // [ 1/2, -1, 1/2, 0], + // [-1/2, 0, 1/2, 0], + // [ 1/6, 2/3, 1/6, 0]]) + // invert to get: + // Poly-to-Bspline = Matrix([[0, 2/3, -1, 1], + // [0, -1/3, 0, 1], + // [0, 2/3, 1, 1], + // [6, 11/3, 2, 1]]) + bs[0] = (p[1] * (2.0f) + p[2] * (-1.0f) + p[3]) / 3.0f; + bs[1] = (p[1] * (-1.0f) + p[3]) / 3.0f; + bs[2] = (p[1] * (2.0f) + p[2] * (1.0f) + p[3]) / 3.0f; + bs[3] = (p[0] + p[1] * (11.0f) + p[2] * (2.0f) + p[3]) / 3.0f; + } + + + __device__ __forceinline__ void initializeFromCatrom(const float4* q) + { + // Catrom-to-Poly = Matrix([[-1/2, 3/2, -3/2, 1/2], + // [1, -5/2, 2, -1/2], + // [-1/2, 0, 1/2, 0], + // [0, 1, 0, 0]]) + p[0] = (-1.0f * q[0] + (3.0f) * q[1] + (-3.0f) * q[2] + (1.0f) * q[3]) / 2.0f; + p[1] = (2.0f * q[0] + (-5.0f) * q[1] + (4.0f) * q[2] + (-1.0f) * q[3]) / 2.0f; + p[2] = (-1.0f * q[0] + (1.0f) * q[2]) / 2.0f; + p[3] = ((2.0f) * q[1]) / 2.0f; + } + + __device__ __forceinline__ void export2Catrom(float4 cr[4]) const + { + // Catrom-to-Poly = Matrix([[-1/2, 3/2, -3/2, 1/2], + // [1, -5/2, 2, -1/2], + // [-1/2, 0, 1/2, 0], + // [0, 1, 0, 0]]) + // invert to get: + // Poly-to-Catrom = Matrix([[1, 1, -1, 1], + // [0, 0, 0, 1], + // [1, 1, 1, 1], + // [6, 4, 2, 1]]) + cr[0] = (p[0] * 6.f / 6.f) - (p[1] * 5.f / 6.f) + (p[2] * 2.f / 6.f) + (p[3] * 1.f / 6.f); + cr[1] = (p[0] * 6.f / 6.f); + cr[2] = (p[0] * 6.f / 6.f) + (p[1] * 1.f / 6.f) + (p[2] * 2.f / 6.f) + (p[3] * 1.f / 6.f); + cr[3] = (p[0] * 6.f / 6.f) + (p[3] * 6.f / 6.f); + } + + __device__ __forceinline__ float4 position4(float u) const + { + return (((p[0] * u) + p[1]) * u + p[2]) * u + p[3]; // Horner scheme + } + + __device__ __forceinline__ float3 position3(float u) const + { + // rely on compiler and inlining for dead code removal + return make_float3(position4(u)); + } + __device__ __forceinline__ float radius(float u) const + { + return position4(u).w; + } + + __device__ __forceinline__ float4 velocity4(float u) const + { + // adjust u to avoid problems with triple knots. + if (u == 0) + u = 0.000001f; + if (u == 1) + u = 0.999999f; + return ((3.0f * p[0] * u) + 2.0f * p[1]) * u + p[2]; + } + + __device__ __forceinline__ float3 velocity3(float u) const + { + return make_float3(velocity4(u)); + } + + __device__ __forceinline__ float derivative_of_radius(float u) const + { + return velocity4(u).w; + } + + __device__ __forceinline__ float4 acceleration4(float u) const + { + return 6.0f * p[0] * u + 2.0f * p[1]; // Horner scheme + } + + __device__ __forceinline__ float3 acceleration3(float u) const + { + return make_float3(acceleration4(u)); + } + + float4 p[4]; +}; + + +// Compute curve primitive surface normal in object space. +// +// Template parameters: +// CurveType - A B-Spline evaluator class. +// type - 0 ~ cylindrical approximation (correct if radius' == 0) +// 1 ~ conic approximation (correct if curve'' == 0) +// other ~ the bona fide surface normal +// +// Parameters: +// bc - A B-Spline evaluator object. +// u - segment parameter of hit-point. +// ps - hit-point on curve's surface in object space; usually +// computed like this. +// float3 ps = ray_orig + t_hit * ray_dir; +// the resulting point is slightly offset away from the +// surface. For this reason (Warning!) ps gets modified by this +// method, projecting it onto the surface +// in case it is not already on it. (See also inline +// comments.) +// +template +__device__ __forceinline__ float3 surfaceNormal(const CurveType& bc, float u, float3& ps) +{ + float3 normal; + if (u == 0.0f) + { + normal = -bc.velocity3(0); // special handling for flat endcaps + } + else if (u == 1.0f) + { + normal = bc.velocity3(1); // special handling for flat endcaps + } + else + { + // ps is a point that is near the curve's offset surface, + // usually ray.origin + ray.direction * rayt. + // We will push it exactly to the surface by projecting it to the plane(p,d). + // The function derivation: + // we (implicitly) transform the curve into coordinate system + // {p, o1 = normalize(ps - p), o2 = normalize(curve'(t)), o3 = o1 x o2} in which + // curve'(t) = (0, length(d), 0); ps = (r, 0, 0); + float4 p4 = bc.position4(u); + float3 p = make_float3(p4); + float r = p4.w; // == length(ps - p) if ps is already on the surface + float4 d4 = bc.velocity4(u); + float3 d = make_float3(d4); + float dr = d4.w; + float dd = dot(d, d); + + float3 o1 = ps - p; // dot(modified_o1, d) == 0 by design: + o1 -= (dot(o1, d) / dd) * d; // first, project ps to the plane(p,d) + o1 *= r / length(o1); // and then drop it to the surface + ps = p + o1; // fine-tuning the hit point + if (type == 0) + { + normal = o1; // cylindrical approximation + } + else + { + if (type != 1) + { + dd -= dot(bc.acceleration3(u), o1); + } + normal = dd * o1 - (dr * r) * d; + } + } + return normalize(normal); +} + +template +__device__ __forceinline__ float3 surfaceNormal(const LinearInterpolator& bc, float u, float3& ps) +{ + float3 normal; + if (u == 0.0f) + { + normal = ps - (float3&) (bc.p[0]); // special handling for round endcaps + } + else if (u >= 1.0f) + { + // reconstruct second control point (Note: the interpolator pre-transforms + // the control-points to speed up repeated evaluation. + const float3 p1 = (float3&) (bc.p[1]) + (float3&) (bc.p[0]); + normal = ps - p1; // special handling for round endcaps + } + else + { + // ps is a point that is near the curve's offset surface, + // usually ray.origin + ray.direction * rayt. + // We will push it exactly to the surface by projecting it to the plane(p,d). + // The function derivation: + // we (implicitly) transform the curve into coordinate system + // {p, o1 = normalize(ps - p), o2 = normalize(curve'(t)), o3 = o1 x o2} in which + // curve'(t) = (0, length(d), 0); ps = (r, 0, 0); + float4 p4 = bc.position4(u); + float3 p = make_float3(p4); + float r = p4.w; // == length(ps - p) if ps is already on the surface + float4 d4 = bc.velocity4(u); + float3 d = make_float3(d4); + float dr = d4.w; + float dd = dot(d, d); + + float3 o1 = ps - p; // dot(modified_o1, d) == 0 by design: + o1 -= (dot(o1, d) / dd) * d; // first, project ps to the plane(p,d) + o1 *= r / length(o1); // and then drop it to the surface + ps = p + o1; // fine-tuning the hit point + if (type == 0) + { + normal = o1; // cylindrical approximation + } + else + { + normal = dd * o1 - (dr * r) * d; + } + } + return normalize(normal); +} + +// Compute curve primitive tangent in object space. +// +// Template parameters: +// CurveType - A B-Spline evaluator class. +// +// Parameters: +// bc - A B-Spline evaluator object. +// u - segment parameter of tangent location on curve. +// +template +__device__ __forceinline__ float3 curveTangent(const CurveType& bc, float u) +{ + float3 tangent = bc.velocity3(u); + return normalize(tangent); +} + +#endif // CURVE_H diff --git a/apps/MDL_sdf/shaders/curve_attributes.h b/apps/MDL_sdf/shaders/curve_attributes.h new file mode 100644 index 00000000..ff8726bb --- /dev/null +++ b/apps/MDL_sdf/shaders/curve_attributes.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CURVE_ATTRIBUTES_H +#define CURVE_ATTRIBUTES_H + +struct CurveAttributes +{ + float4 vertex; // .xyz = 3D position in object space, .w = radius in world space. + float4 reference; // .xyz = Constant reference vector along the fiber. Used to calculate circular intersection value vFiber [0.0, 1.0] around the hair. .w = unused. + float4 texcoord; // .xyz = 3D texture coordinates. .w = interpolant along the whole fiber. Used to calculate uFiber in range [0.0, 1.0] from root to tip. +}; + +#endif // CURVE_ATTRIBUTES_H diff --git a/apps/MDL_sdf/shaders/exception.cu b/apps/MDL_sdf/shaders/exception.cu new file mode 100644 index 00000000..0c6caef4 --- /dev/null +++ b/apps/MDL_sdf/shaders/exception.cu @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +//#include "half_common.h" + +extern "C" __constant__ SystemData sysData; + +extern "C" __global__ void __exception__all() +{ + //const uint3 theLaunchDim = optixGetLaunchDimensions(); + const uint3 theLaunchIndex = optixGetLaunchIndex(); + const int theExceptionCode = optixGetExceptionCode(); + + printf("Exception %d at (%u, %u)\n", theExceptionCode, theLaunchIndex.x, theLaunchIndex.y); + +// FIXME This debug color writing only works for render strategies where the launch dimension matches the outputBuffer resolution! +// +// const unsigned int index = theLaunchIndex.y * theLaunchDim.x + theLaunchIndex.x; +//#if USE_FP32_OUTPUT +// float4* buffer = reinterpret_cast(sysData.outputBuffer); +// buffer[index] = make_float4(1000000.0f, 0.0f, 1000000.0f, 1.0f); // RGBA32F super magenta +//#else +// Half4* buffer = reinterpret_cast(sysData.outputBuffer); +// buffer[index] = make_Half4(1000000.0f, 0.0f, 1000000.0f, 1.0f); // RGBA16F super magenta +//#endif +} diff --git a/apps/MDL_sdf/shaders/function_indices.h b/apps/MDL_sdf/shaders/function_indices.h new file mode 100644 index 00000000..6e14d0c6 --- /dev/null +++ b/apps/MDL_sdf/shaders/function_indices.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef FUNCTION_INDICES_H +#define FUNCTION_INDICES_H + +enum TypeRay +{ + TYPE_RAY_RADIANCE, + TYPE_RAY_SHADOW, + + NUM_RAY_TYPES +}; + +enum TypeLens +{ + TYPE_LENS_PINHOLE, + TYPE_LENS_FISHEYE, + TYPE_LENS_SPHERE, + + NUM_LENS_TYPES +}; + +enum TypeLight +{ + TYPE_LIGHT_ENV_CONST, + TYPE_LIGHT_ENV_SPHERE, + TYPE_LIGHT_MESH, + TYPE_LIGHT_POINT, + TYPE_LIGHT_SPOT, + TYPE_LIGHT_IES, + + NUM_LIGHT_TYPES +}; + +// The shader configuration's callable indices are zero based. +// Skip the lens and light sampling callable indices with this offset. +#define CALL_OFFSET (NUM_LENS_TYPES + NUM_LIGHT_TYPES) + +#endif // FUNCTION_INDICES_H + \ No newline at end of file diff --git a/apps/MDL_sdf/shaders/half_common.h b/apps/MDL_sdf/shaders/half_common.h new file mode 100644 index 00000000..abd82559 --- /dev/null +++ b/apps/MDL_sdf/shaders/half_common.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef HALF_COMMON_H +#define HALF_COMMON_H + +#include "config.h" + + // Only used when the renderer is switched to RGBA16F rendering. +#if !USE_FP32_OUTPUT + +#include + +// CUDA doesn't implement half4? Run my own class. +// Align the struct to 8 bytes to get vectorized ld.v4.u16 and st.v4.u16 instructions. +struct __align__(8) Half4 +{ + half x; + half y; + half z; + half w; +}; + +__forceinline__ __host__ __device__ Half4 make_Half4(const half x, const half y, const half z, const half w) +{ + Half4 h4; + + h4.x = x; + h4.y = y; + h4.z = z; + h4.w = w; + + return h4; +} + +__forceinline__ __host__ __device__ Half4 make_Half4(const float x, const float y, const float z, float w) +{ + Half4 h4; + + h4.x = __float2half(x); + h4.y = __float2half(y); + h4.z = __float2half(z); + h4.w = __float2half(w); + + return h4; +} + +__forceinline__ __host__ __device__ Half4 make_Half4(float3 const& v, float w) +{ + Half4 h4; + + h4.x = __float2half(v.x); + h4.y = __float2half(v.y); + h4.z = __float2half(v.z); + h4.w = __float2half(w); + + return h4; +} + +#endif + +#endif // HALF_COMMON_H diff --git a/apps/MDL_sdf/shaders/hit.cu b/apps/MDL_sdf/shaders/hit.cu new file mode 100644 index 00000000..562772f3 --- /dev/null +++ b/apps/MDL_sdf/shaders/hit.cu @@ -0,0 +1,1597 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +#include "vertex_attributes.h" +#include "function_indices.h" +#include "material_definition_mdl.h" +#include "light_definition.h" +#include "shader_common.h" +#include "transform.h" +#include "random_number_generators.h" +#include "curve.h" +#include "curve_attributes.h" +#include "sdf_attributes.h" + +// Contained in per_ray_data.h: +//#include + +// The MDL texture runtime functions: texture, MBSDF, light profile, and scene data (dummy) lookup functions. +// These are declared extern and can only appear in one module inside the pipeline or there will be OptiX compilation errors. +// Means all functions potentially accessing any of these MDL runtime functions must be implemented in this module. +// That's the reason why the arbitrary mesh light sampling routine is here and not in light_sample.cu +#define TEX_SUPPORT_NO_VTABLES +#define TEX_SUPPORT_NO_DUMMY_SCENEDATA +#include "texture_lookup.h" + +// This renderer is not implementing support for derivatives (ray differentials). +// It only needs this Shading_state_materialy structure without derivatives support. +typedef mi::neuraylib::Shading_state_material Mdl_state; + + +// DEBUG Helper code. +//uint3 theLaunchIndex = optixGetLaunchIndex(); +//if (theLaunchIndex.x == 256 && theLaunchIndex.y == 256) +//{ +// printf("value = %f\n", value); +//} + +//thePrd->radiance += make_float3(value); +//thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; +//return; + + +extern "C" __constant__ SystemData sysData; + +// This shader handles every supported feature of the renderer. +extern "C" __global__ void __closesthit__radiance() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->flags |= FLAG_HIT; // Required to distinguish surface hits from random walk miss. + + thePrd->distance = optixGetRayTmax(); // Return the current path segment distance, needed for absorption calculations in the integrator. + + // PRECISION Calculate this from the object space vertex positions and transform to world for better accuracy when needed. + // Same as: thePrd->pos = optixGetWorldRayOrigin() + optixGetWorldRayDirection() * optixGetRayTmax(); + thePrd->pos += thePrd->wi * thePrd->distance; + + // If we're inside a volume and hit something, the path throughput needs to be modulated + // with the transmittance along this segment before adding surface or light radiance! + if (0 < thePrd->idxStack) // This assumes the first stack entry is vaccuum. + { + thePrd->throughput *= expf(thePrd->sigma_t * -thePrd->distance); + + // Increment the volume scattering random walk counter. + // Unused when FLAG_VOLUME_SCATTERING is not set. + ++thePrd->walk; + } + + const GeometryInstanceData theData = sysData.geometryInstanceData[optixGetInstanceId()]; + // theData.ids: .x = idMaterial, .y = idLight, .z = idObject + + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + // Cast the CUdeviceptr to the actual format of the Triangles attributes and indices. + const uint3* indices = reinterpret_cast(theData.indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData.attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // .x = beta, .y = gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(optixGetTransformListHandle(0), objectToWorld, worldToObject); // Single instance level transformation list only. + + // Object space vertex attributes at the hit point. + float3 ns = attr0.normal * alpha + attr1.normal * theBarycentrics.x + attr2.normal * theBarycentrics.y; + float3 ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + float3 tg = attr0.tangent * alpha + attr1.tangent * theBarycentrics.x + attr2.tangent * theBarycentrics.y; + + // Transform attributes into internal space == world space. + ns = normalize(transformNormal(worldToObject, ns)); + ng = normalize(transformNormal(worldToObject, ng)); + // This is actually the geometry tangent which for the runtime generated geometry objects + // (plane, box, sphere, torus) match exactly with the texture space tangent. + // FIXME Generate these from the triangle's texture derivatives instead, but that's more expensive. + // Mind that tangents and bitangents are transformed as vectors, not normals, because they lie inside the surface's plane. + tg = normalize(transformVector(objectToWorld, tg)); + // Calculate an ortho-normal system respective to the shading normal. + // Expanding the TBN tbn(tg, ns) constructor because TBN members can't be used as pointers for the Mdl_state with NUM_TEXTURE_SPACES > 1. + float3 bt = normalize(cross(ns, tg)); + tg = cross(bt, ns); // Now the tangent is orthogonal to the shading normal. + + // The Mdl_state holds the texture attributes per texture space in separate arrays. + float3 texture_coordinates[NUM_TEXTURE_SPACES]; + float3 texture_tangents[NUM_TEXTURE_SPACES]; + float3 texture_bitangents[NUM_TEXTURE_SPACES]; + + // NUM_TEXTURE_SPACES is always at least 1. + texture_coordinates[0] = attr0.texcoord * alpha + attr1.texcoord * theBarycentrics.x + attr2.texcoord * theBarycentrics.y; + texture_bitangents[0] = bt; + texture_tangents[0] = tg; + +#if NUM_TEXTURE_SPACES == 2 + // HACK Copy the vertex attributes of texture space 0, simply because there is no second texcoord inside TriangleAttributes. + texture_coordinates[1] = texture_coordinates[0]; + texture_bitangents[1] = bt; + texture_tangents[1] = tg; +#endif + + // Setup the Mdl_state. + Mdl_state state; + + // The result of state::normal(). It represents the shading normal as determined by the renderer. + // This field will be updated to the result of "geometry.normal" by the material or BSDF init functions, + // if requested during code generation with set_option("include_geometry_normal", true) which is the default. + state.normal = ns; + + // The result of state::geometry_normal(). + // It represents the geometry normal as determined by the renderer. + state.geom_normal = ng; + + // The result of state::position(). + // It represents the position where the material should be evaluated. + state.position = thePrd->pos; + + // The result of state::animation_time(). + // It represents the time of the current sample in seconds. + state.animation_time = 0.0f; // This renderer implements no support for animations. + + // An array containing the results of state::texture_coordinate(i). + // The i-th entry represents the texture coordinates of the i-th texture space at the current position. + // Only one element here because "num_texture_spaces" option has been set to 1. + state.text_coords = texture_coordinates; + + // An array containing the results of state::texture_tangent_u(i). + // The i-th entry represents the texture tangent vector of the i-th texture space at the + // current position, which points in the direction of the projection of the tangent to the + // positive u axis of this texture space onto the plane defined by the original surface normal. + state.tangent_u = texture_tangents; + + // An array containing the results of state::texture_tangent_v(i). + // The i-th entry represents the texture bitangent vector of the i-th texture space at the + // current position, which points in the general direction of the positive v axis of this + // texture space, but is orthogonal to both the original surface normal and the tangent + // of this texture space. + state.tangent_v = texture_bitangents; + + // The texture results lookup table. + // The size must match the backend set_option("num_texture_results") value. + // Values will be modified by the init functions to avoid duplicate texture fetches + // and duplicate calculation of values (texture coordinate system). + // This implementation is using the single material init function, not the individual init per distribution function. + // PERF This influences how many things can be precalculated inside the init() function. + // If the number of result elements in this array is lower than what is required, + // the expressions for the remaining results will be compiled into the sample() and eval() functions + // which will make the compilation and runtime performance slower. + float4 texture_results[NUM_TEXTURE_RESULTS]; + + state.text_results = texture_results; + + // A pointer to a read-only data segment. + // For "PTX", "LLVM-IR" and "native" JIT backend. + // For other backends, this should be NULL. + state.ro_data_segment = nullptr; + + // A 4x4 transformation matrix in row-major order transforming from world to object coordinates. + // The last row is always implied to be (0, 0, 0, 1) and does not have to be provided. + // It is used by the state::transform_*() methods. + // This field is only used if the uniform state is included. + state.world_to_object = worldToObject; + + // A 4x4 transformation matrix in row-major order transforming from object to world coordinates. + // The last row is always implied to be (0, 0, 0, 1) and does not have to be provided. + // It is used by the state::transform_*() methods. + // This field is only used if the uniform state is included. + state.object_to_world = objectToWorld; + + // The result of state::object_id(). + // It is an application-specific identifier of the hit object as provided in a scene. + // It can be used to make instanced objects look different in spite of the same used material. + // This field is only used if the uniform state is included. + state.object_id = theData.ids.z; // idObject, this is the sg::Instance node ID. + + // The result of state::meters_per_scene_unit(). + // The field is only used if the "fold_meters_per_scene_unit" option is set to false. + // Otherwise, the value of the "meters_per_scene_unit" option will be used in the code. + state.meters_per_scene_unit = 1.0f; + + const MaterialDefinitionMDL& material = sysData.materialDefinitionsMDL[theData.ids.x]; + + mi::neuraylib::Resource_data res_data = { nullptr, material.texture_handler }; + + const DeviceShaderConfiguration& shaderConfiguration = sysData.shaderConfigurations[material.indexShader]; + + // Using a single material init function instead of per distribution init functions. + // This is always present, even if it just returns. + optixDirectCall(shaderConfiguration.idxCallInit, &state, &res_data, material.arg_block); + + // Explicitly include edge-on cases as frontface condition! + // Keeps the material stack from overflowing at silhouettes. + // Prevents that silhouettes of thin-walled materials use the backface material. + // Using the true geometry normal attribute as originally defined on the frontface! + const bool isFrontFace = (0.0f <= dot(thePrd->wo, state.geom_normal)); + + // thin_walled value in case the expression is a constant. + bool thin_walled = ((shaderConfiguration.flags & IS_THIN_WALLED) != 0); + + if (0 <= shaderConfiguration.idxCallThinWalled) + { + thin_walled = optixDirectCall(shaderConfiguration.idxCallThinWalled, &state, &res_data, material.arg_block); + } + + // IOR value in case the material ior expression is constant. + float3 ior = shaderConfiguration.ior; + + if (0 <= shaderConfiguration.idxCallIor) + { + ior = optixDirectCall(shaderConfiguration.idxCallIor, &state, &res_data, material.arg_block); + } + + // Handle optional surface and backface emission expressions. + // Default to no EDF. + int idxCallEmissionEval = -1; + int idxCallEmissionIntensity = -1; + int idxCallEmissionIntensityMode = -1; + // These are not used when there is no emission, no need to initialize. + float3 emission_intensity; + int emission_intensity_mode; + + // MDL Specs: There is no emission on the back-side unless an EDF is specified with the backface field and thin_walled is set to true. + if (isFrontFace) + { + idxCallEmissionEval = shaderConfiguration.idxCallSurfaceEmissionEval; + idxCallEmissionIntensity = shaderConfiguration.idxCallSurfaceEmissionIntensity; + idxCallEmissionIntensityMode = shaderConfiguration.idxCallSurfaceEmissionIntensityMode; + + emission_intensity = shaderConfiguration.surface_intensity; + emission_intensity_mode = shaderConfiguration.surface_intensity_mode; + } + else if (thin_walled) // && !isFrontFace + { + // These can be the same callable indices if the expressions from surface and backface were identical. + idxCallEmissionEval = shaderConfiguration.idxCallBackfaceEmissionEval; + idxCallEmissionIntensity = shaderConfiguration.idxCallBackfaceEmissionIntensity; + idxCallEmissionIntensityMode = shaderConfiguration.idxCallBackfaceEmissionIntensityMode; + + emission_intensity = shaderConfiguration.backface_intensity; + emission_intensity_mode = shaderConfiguration.backface_intensity_mode; + } + + // Check if the hit geometry contains any emission. + if (0 <= idxCallEmissionEval) + { + if (0 <= idxCallEmissionIntensity) // Emission intensity is not a constant. + { + emission_intensity = optixDirectCall(idxCallEmissionIntensity, &state, &res_data, material.arg_block); + } + if (0 <= idxCallEmissionIntensityMode) // Emission intensity mode is not a constant. + { + emission_intensity_mode = optixDirectCall(idxCallEmissionIntensityMode, &state, &res_data, material.arg_block); + } + if (isNotNull(emission_intensity)) + { + mi::neuraylib::Edf_evaluate_data eval_data; + + eval_data.k1 = thePrd->wo; // input: outgoing direction (-ray.direction) + //eval_data.cos : output: dot(normal, k1) + //eval_data.edf : output: edf + //eval_data.pdf : output: pdf (non-projected hemisphere) + + optixDirectCall(idxCallEmissionEval, &eval_data, &state, &res_data, material.arg_block); + + const float area = sysData.lightDefinitions[theData.ids.y].area; // This must be a mesh light, and then it has a valid idLight. + + eval_data.pdf = thePrd->distance * thePrd->distance / (area * eval_data.cos); // Solid angle measure. + + float weightMIS = 1.0f; + // If the last event was diffuse or glossy, calculate the opposite MIS weight for this implicit light hit. + if (sysData.directLighting && (thePrd->eventType & (mi::neuraylib::BSDF_EVENT_DIFFUSE | mi::neuraylib::BSDF_EVENT_GLOSSY))) + { + weightMIS = balanceHeuristic(thePrd->pdf, eval_data.pdf); + } + + // Power (flux) [W] divided by light area gives radiant exitance [W/m^2]. + const float factor = (emission_intensity_mode == 0) ? 1.0f : 1.0f / area; + + thePrd->radiance += thePrd->throughput * emission_intensity * eval_data.edf * (factor * weightMIS); + } + } + + // Start fresh with the next BSDF sample. + // Save the current path throughput for the direct lighting contribution. + // The path throughput will be modulated with the BSDF sampling results before that. + const float3 throughput = thePrd->throughput; + // The pdf of the previous event was needed for the emission calculation above. + thePrd->pdf = 0.0f; + + // Determine which BSDF to use when the material is thin-walled. + int idxCallScatteringSample = shaderConfiguration.idxCallSurfaceScatteringSample; + int idxCallScatteringEval = shaderConfiguration.idxCallSurfaceScatteringEval; + + // thin-walled and looking at the backface and backface.scattering expression available? + if (thin_walled && !isFrontFace && 0 <= shaderConfiguration.idxCallBackfaceScatteringSample) + { + // Use the backface.scattering BSDF sample and evaluation functions. + // Apparently the MDL code can handle front- and backfacing calculations appropriately with the original state and the properly setup volume IORs. + // No need to flip normals to the ray side. + idxCallScatteringSample = shaderConfiguration.idxCallBackfaceScatteringSample; + idxCallScatteringEval = shaderConfiguration.idxCallBackfaceScatteringEval; // Assumes both are valid. + } + + // Importance sample the BSDF. + if (0 <= idxCallScatteringSample) + { + mi::neuraylib::Bsdf_sample_data sample_data; + + int idx = thePrd->idxStack; + + // If the hit is either on the surface or a thin-walled material, + // the ray is inside the surrounding material and the material ior is on the other side. + if (isFrontFace || thin_walled) + { + sample_data.ior1 = thePrd->stack[idx].ior; // From surrounding medium ior + sample_data.ior2 = ior; // to material ior. + } + else + { + // When hitting the backface of a non-thin-walled material, + // the ray is inside the current material and the surrounding material is on the other side. + // The material's IOR is the current top-of-stack. We need the one further down! + idx = max(0, idx - 1); + + sample_data.ior1 = ior; // From material ior + sample_data.ior2 = thePrd->stack[idx].ior; // to surrounding medium ior + } + sample_data.k1 = thePrd->wo; // == -optixGetWorldRayDirection() + sample_data.xi = rng4(thePrd->seed); + + optixDirectCall(idxCallScatteringSample, &sample_data, &state, &res_data, material.arg_block); + + thePrd->wi = sample_data.k2; // Continuation direction. + thePrd->throughput *= sample_data.bsdf_over_pdf; // Adjust the path throughput for all following incident lighting. + thePrd->pdf = sample_data.pdf; // Note that specular events return pdf == 0.0f! (=> Not a path termination condition.) + thePrd->eventType = sample_data.event_type; // This replaces the PRD flags used inside the other examples. + } + else + { + // If there is no valid scattering BSDF, it's the black bsdf() which ends the path. + // This is usually happening with arbitrary mesh lights when only specifying emission. + thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; + // None of the following code will have any effect in that case. + return; + } + + // Direct lighting if the sampled BSDF was diffuse and any light is in the scene. + const int numLights = sysData.numLights; + + if (sysData.directLighting && 0 < numLights && (thePrd->eventType & (mi::neuraylib::BSDF_EVENT_DIFFUSE | mi::neuraylib::BSDF_EVENT_GLOSSY))) + { + // Sample one of many lights. + // The caller picks the light to sample. Make sure the index stays in the bounds of the sysData.lightDefinitions array. + const int indexLight = (1 < numLights) ? clamp(static_cast(floorf(rng(thePrd->seed) * numLights)), 0, numLights - 1) : 0; + + const LightDefinition& light = sysData.lightDefinitions[indexLight]; + + LightSample lightSample = optixDirectCall(NUM_LENS_TYPES + light.typeLight, light, thePrd); + + if (0.0f < lightSample.pdf && 0 <= idxCallScatteringEval) + { + mi::neuraylib::Bsdf_evaluate_data eval_data; + + int idx = thePrd->idxStack; + + if (isFrontFace || thin_walled) + { + eval_data.ior1 = thePrd->stack[idx].ior; + eval_data.ior2 = ior; + } + else + { + idx = max(0, idx - 1); + + eval_data.ior1 = ior; + eval_data.ior2 = thePrd->stack[idx].ior; + } + + eval_data.k1 = thePrd->wo; + eval_data.k2 = lightSample.direction; + + optixDirectCall(idxCallScatteringEval, &eval_data, &state, &res_data, material.arg_block); + + // This already contains the fabsf(dot(lightSample.direction, state.normal)) factor! + // For a white Lambert material, the bxdf components match the eval_data.pdf + const float3 bxdf = eval_data.bsdf_diffuse + eval_data.bsdf_glossy; + + if (0.0f < eval_data.pdf && isNotNull(bxdf)) + { + // Pass the current payload registers through to the shadow ray. + unsigned int p0 = optixGetPayload_0(); + unsigned int p1 = optixGetPayload_1(); + + thePrd->flags &= ~FLAG_SHADOW; // Clear the shadow flag. + + // Note that the sysData.sceneEpsilon is applied on both sides of the shadow ray [t_min, t_max] interval + // to prevent self-intersections with the actual light geometry in the scene. + optixTrace(sysData.topObject, + thePrd->pos, lightSample.direction, // origin, direction + sysData.sceneEpsilon, lightSample.distance - sysData.sceneEpsilon, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT, // The shadow ray type only uses anyhit programs. + TYPE_RAY_SHADOW, NUM_RAY_TYPES, TYPE_RAY_SHADOW, + p0, p1); // Pass through thePrd to the shadow ray. + + if ((thePrd->flags & FLAG_SHADOW) == 0) // Shadow flag not set? + { + const float weightMIS = (TYPE_LIGHT_POINT <= light.typeLight) ? 1.0f : balanceHeuristic(lightSample.pdf, eval_data.pdf); + + // The sampled emission needs to be scaled by the inverse probability to have selected this light, + // Selecting one of many lights means the inverse of 1.0f / numLights. + // This is using the path throughput before the sampling modulated it above. + thePrd->radiance += throughput * bxdf * lightSample.radiance_over_pdf * (float(numLights) * weightMIS); + } + } + } + } + + // Now after everything has been handled using the current material stack, + // adjust the material stack if there was a transmission crossing a boundary surface. + if (!thin_walled && (thePrd->eventType & mi::neuraylib::BSDF_EVENT_TRANSMISSION) != 0) + { + if (isFrontFace) // Entered a volume. + { + float3 absorption = shaderConfiguration.absorption_coefficient; + if (0 <= shaderConfiguration.idxCallVolumeAbsorptionCoefficient) + { + absorption = optixDirectCall(shaderConfiguration.idxCallVolumeAbsorptionCoefficient, &state, &res_data, material.arg_block); + } + + float3 scattering = shaderConfiguration.scattering_coefficient; + if (0 <= shaderConfiguration.idxCallVolumeScatteringCoefficient) + { + scattering = optixDirectCall(shaderConfiguration.idxCallVolumeScatteringCoefficient, &state, &res_data, material.arg_block); + } + + float bias = shaderConfiguration.directional_bias; + if (0 <= shaderConfiguration.idxCallVolumeDirectionalBias) + { + bias = optixDirectCall(shaderConfiguration.idxCallVolumeDirectionalBias, &state, &res_data, material.arg_block); + } + + const int idx = min(thePrd->idxStack + 1, MATERIAL_STACK_LAST); // Push current medium parameters. + + thePrd->idxStack = idx; + thePrd->stack[idx].ior = ior; + thePrd->stack[idx].sigma_a = absorption; + thePrd->stack[idx].sigma_s = scattering; + thePrd->stack[idx].bias = bias; + + thePrd->sigma_t = absorption + scattering; // Update the current extinction coefficient. + } + else // if !isFrontFace. Left a volume. + { + const int idx = max(0, thePrd->idxStack - 1); // Pop current medium parameters. + + thePrd->idxStack = idx; + + thePrd->sigma_t = thePrd->stack[idx].sigma_a + thePrd->stack[idx].sigma_s; // Update the current extinction coefficient. + } + + thePrd->walk = 0; // Reset the number of random walk steps taken when crossing any volume boundary. + } +} + + +// PERF Identical to radiance shader above, but used for materials without emission, which is the majority of materials. +extern "C" __global__ void __closesthit__radiance_no_emission() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->flags |= FLAG_HIT; // Required to distinguish surface hits from random walk miss. + + thePrd->distance = optixGetRayTmax(); // Return the current path segment distance, needed for absorption calculations in the integrator. + + // PRECISION Calculate this from the object space vertex positions and transform to world for better accuracy when needed. + // Same as: thePrd->pos = optixGetWorldRayOrigin() + optixGetWorldRayDirection() * optixGetRayTmax(); + thePrd->pos += thePrd->wi * thePrd->distance; + + // If we're inside a volume and hit something, the path throughput needs to be modulated + // with the transmittance along this segment before adding surface or light radiance! + if (0 < thePrd->idxStack) // This assumes the first stack entry is vaccuum. + { + thePrd->throughput *= expf(thePrd->sigma_t * -thePrd->distance); + + // Increment the volume scattering random walk counter. + // Unused when FLAG_VOLUME_SCATTERING is not set. + ++thePrd->walk; + } + + const GeometryInstanceData theData = sysData.geometryInstanceData[optixGetInstanceId()]; + // theData.ids: .x = idMaterial, .y = idLight, .z = idObject + + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(optixGetTransformListHandle(0), objectToWorld, worldToObject); // Single instance level transformation list only. + + // Temporary vertex attribute state. + float3 ns; // Shading normal. + float3 ng; // Geometric normal. + float3 tg; // Geometric tangent. + float3 tc; // Texture coordinate. + + const bool isSDF = ((optixGetHitKind() & 0x7f) == 1); + + float sdfSign = 1.0f; // The sdfSign is positive if the ray hit from the outside, and negative when hit from the inside. + + if (!isSDF) // Not a custom primitive, must be indexed triangles. + { + // Cast the CUdeviceptr to the actual format of the Triangles attributes and indices. + const uint3* indices = reinterpret_cast(theData.indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData.attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + // Object space vertex attributes at the hit point. + ns = attr0.normal * alpha + attr1.normal * theBarycentrics.x + attr2.normal * theBarycentrics.y; + ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + tg = attr0.tangent * alpha + attr1.tangent * theBarycentrics.x + attr2.tangent * theBarycentrics.y; + tc = attr0.texcoord * alpha + attr1.texcoord * theBarycentrics.x + attr2.texcoord * theBarycentrics.y; + + // Transform attributes into internal space == world space. + ns = normalize(transformNormal(worldToObject, ns)); + ng = normalize(transformNormal(worldToObject, ng)); + tg = normalize(transformVector(objectToWorld, tg)); + } + else // Custom SDF primitive, defined as AABB containing an SDF 3D texture. + { + const SignedDistanceFieldAttributes attrib = *reinterpret_cast(theData.attributes); + + // Each central difference texture lookup offsets exactly one texel in each dimension. + const float3 d = 1.0f / make_float3(attrib.sdfTextureSize.x, attrib.sdfTextureSize.y, attrib.sdfTextureSize.z); + + sdfSign = __uint_as_float(optixGetAttribute_0()); + + // Use the exact same UVW coordinate as inside the intersection program's iso-surface hit. Can't get more precise than that. + // SDF texture location as texture coordinate. + tc = make_float3(__uint_as_float(optixGetAttribute_1()), + __uint_as_float(optixGetAttribute_2()), + __uint_as_float(optixGetAttribute_3())); + + // Central differencing, 6 texture lookups. + ng.x = tex3D(attrib.sdfTexture, tc.x + d.x, tc.y, tc.z) - tex3D(attrib.sdfTexture, tc.x - d.x, tc.y, tc.z); + ng.y = tex3D(attrib.sdfTexture, tc.x, tc.y + d.y, tc.z) - tex3D(attrib.sdfTexture, tc.x, tc.y - d.y, tc.z); + ng.z = tex3D(attrib.sdfTexture, tc.x, tc.y, tc.z + d.z) - tex3D(attrib.sdfTexture, tc.x, tc.y, tc.z - d.z); + + // Transform attributes into internal space == world space. + // SDF iso-surface only have the geometric normal and the texture coordinate as attribute. + ng = normalize(transformNormal(worldToObject, ng)); + + ns = ng; + + // FIXME This will result in discontinuities for round objects which won't look correctly with anisotropic materials! + if (fabsf(ns.z) < fabsf(ns.x)) + { + tg.x = ns.z; + tg.y = 0.0f; + tg.z = -ns.x; + } + else + { + tg.x = 0.0f; + tg.y = ns.z; + tg.z = -ns.y; + } + } + + // Calculate an ortho-normal system respective to the shading normal. + float3 bt = normalize(cross(ns, tg)); + tg = cross(bt, ns); // Now the tangent is orthogonal to the shading normal. + + // The Mdl_state holds the texture attributes per texture space in separate arrays. + float3 texture_coordinates[NUM_TEXTURE_SPACES]; + float3 texture_tangents[NUM_TEXTURE_SPACES]; + float3 texture_bitangents[NUM_TEXTURE_SPACES]; + + // NUM_TEXTURE_SPACES is always at least 1. + texture_coordinates[0] = tc; + texture_bitangents[0] = bt; + texture_tangents[0] = tg; + +#if NUM_TEXTURE_SPACES == 2 + // HACK Copy the vertex attributes of texture space 0, simply because there is no second texcoord inside TriangleAttributes. + texture_coordinates[1] = texture_coordinates[0]; + texture_bitangents[1] = bt; + texture_tangents[1] = tg; +#endif + + // Setup the Mdl_state. + Mdl_state state; + + float4 texture_results[NUM_TEXTURE_RESULTS]; + + // For explanations of these fields see comments inside __closesthit__radiance above. + state.normal = ns; + state.geom_normal = ng; + state.position = thePrd->pos; + state.animation_time = 0.0f; + state.text_coords = texture_coordinates; + state.tangent_u = texture_tangents; + state.tangent_v = texture_bitangents; + state.text_results = texture_results; + state.ro_data_segment = nullptr; + state.world_to_object = worldToObject; + state.object_to_world = objectToWorld; + state.object_id = theData.ids.z; + state.meters_per_scene_unit = 1.0f; + + const MaterialDefinitionMDL& material = sysData.materialDefinitionsMDL[theData.ids.x]; + + mi::neuraylib::Resource_data res_data = { nullptr, material.texture_handler }; + + const DeviceShaderConfiguration& shaderConfiguration = sysData.shaderConfigurations[material.indexShader]; + + // Using a single material init function instead of per distribution init functions. + // This is always present, even if it just returns. + optixDirectCall(shaderConfiguration.idxCallInit, &state, &res_data, material.arg_block); + + // Explicitly include edge-on cases as frontface condition! + // Keeps the material stack from overflowing at silhouettes. + // Prevents that silhouettes of thin-walled materials use the backface material. + // Using the true geometry normal attribute as originally defined on the frontface! + const bool isFrontFace = (isSDF) ? (0.0f < sdfSign) : (0.0f <= dot(thePrd->wo, state.geom_normal)); + + // thin_walled value in case the expression is a constant. + bool thin_walled = ((shaderConfiguration.flags & IS_THIN_WALLED) != 0); + + if (0 <= shaderConfiguration.idxCallThinWalled) + { + thin_walled = optixDirectCall(shaderConfiguration.idxCallThinWalled, &state, &res_data, material.arg_block); + } + + // IOR value in case the material ior expression is constant. + float3 ior = shaderConfiguration.ior; + + if (0 <= shaderConfiguration.idxCallIor) + { + ior = optixDirectCall(shaderConfiguration.idxCallIor, &state, &res_data, material.arg_block); + } + + // Start fresh with the next BSDF sample. + // Save the current path throughput for the direct lighting contribution. + // The path throughput will be modulated with the BSDF sampling results before that. + const float3 throughput = thePrd->throughput; + // The pdf of the previous event was needed for the emission calculation above. + thePrd->pdf = 0.0f; + + // Determine which BSDF to use when the material is thin-walled. + int idxCallScatteringSample = shaderConfiguration.idxCallSurfaceScatteringSample; + int idxCallScatteringEval = shaderConfiguration.idxCallSurfaceScatteringEval; + + // thin-walled and looking at the backface and backface.scattering expression available? + if (thin_walled && !isFrontFace && 0 <= shaderConfiguration.idxCallBackfaceScatteringSample) + { + // Use the backface.scattering BSDF sample and evaluation functions. + // Apparently the MDL code can handle front- and backfacing calculations appropriately with the original state and the properly setup volume IORs. + // No need to flip normals to the ray side. + idxCallScatteringSample = shaderConfiguration.idxCallBackfaceScatteringSample; + idxCallScatteringEval = shaderConfiguration.idxCallBackfaceScatteringEval; // Assumes both are valid. + } + + // Importance sample the BSDF. + if (0 <= idxCallScatteringSample) + { + mi::neuraylib::Bsdf_sample_data sample_data; + + int idx = thePrd->idxStack; + + // If the hit is either on the surface or a thin-walled material, + // the ray is inside the surrounding material and the material ior is on the other side. + if (isFrontFace || thin_walled) + { + sample_data.ior1 = thePrd->stack[idx].ior; // From surrounding medium ior + sample_data.ior2 = ior; // to material ior. + } + else + { + // When hitting the backface of a non-thin-walled material, + // the ray is inside the current material and the surrounding material is on the other side. + // The material's IOR is the current top-of-stack. We need the one further down! + idx = max(0, idx - 1); + + sample_data.ior1 = ior; // From material ior + sample_data.ior2 = thePrd->stack[idx].ior; // to surrounding medium ior + } + sample_data.k1 = thePrd->wo; // == -optixGetWorldRayDirection() + sample_data.xi = rng4(thePrd->seed); + + optixDirectCall(idxCallScatteringSample, &sample_data, &state, &res_data, material.arg_block); + + thePrd->wi = sample_data.k2; // Continuation direction. + thePrd->throughput *= sample_data.bsdf_over_pdf; // Adjust the path throughput for all following incident lighting. + thePrd->pdf = sample_data.pdf; // Note that specular events return pdf == 0.0f! (=> Not a path termination condition.) + thePrd->eventType = sample_data.event_type; // This replaces the PRD flags used inside the other examples. + } + else + { + // If there is no valid scattering BSDF, it's the black bsdf() which ends the path. + // This is usually happening with arbitrary mesh lights when only specifying emission. + thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; + // None of the following code will have any effect in that case. + return; + } + + // Direct lighting if the sampled BSDF was diffuse and any light is in the scene. + const int numLights = sysData.numLights; + + if (sysData.directLighting && 0 < numLights && (thePrd->eventType & (mi::neuraylib::BSDF_EVENT_DIFFUSE | mi::neuraylib::BSDF_EVENT_GLOSSY))) + { + // Sample one of many lights. + // The caller picks the light to sample. Make sure the index stays in the bounds of the sysData.lightDefinitions array. + const int indexLight = (1 < numLights) ? clamp(static_cast(floorf(rng(thePrd->seed) * numLights)), 0, numLights - 1) : 0; + + const LightDefinition& light = sysData.lightDefinitions[indexLight]; + + LightSample lightSample = optixDirectCall(NUM_LENS_TYPES + light.typeLight, light, thePrd); + + if (0.0f < lightSample.pdf && 0 <= idxCallScatteringEval) + { + mi::neuraylib::Bsdf_evaluate_data eval_data; + + int idx = thePrd->idxStack; + + if (isFrontFace || thin_walled) + { + eval_data.ior1 = thePrd->stack[idx].ior; + eval_data.ior2 = ior; + } + else + { + idx = max(0, idx - 1); + + eval_data.ior1 = ior; + eval_data.ior2 = thePrd->stack[idx].ior; + } + + eval_data.k1 = thePrd->wo; + eval_data.k2 = lightSample.direction; + + optixDirectCall(idxCallScatteringEval, &eval_data, &state, &res_data, material.arg_block); + + // This already contains the fabsf(dot(lightSample.direction, state.normal)) factor! + // For a white Lambert material, the bxdf components match the eval_data.pdf + const float3 bxdf = eval_data.bsdf_diffuse + eval_data.bsdf_glossy; + + if (0.0f < eval_data.pdf && isNotNull(bxdf)) + { + // Pass the current payload registers through to the shadow ray. + unsigned int p0 = optixGetPayload_0(); + unsigned int p1 = optixGetPayload_1(); + + thePrd->flags &= ~FLAG_SHADOW; // Clear the shadow flag. + + float3 origin = thePrd->pos; + + // Offset origin from SDF surfaces by more than sceneEpsilon to get out of the sdfEpsilon environment over the SDF iso-surface. + if (isSDF) + { + origin += (sdfSign * sysData.sdfOffset * sysData.sdfEpsilon) * ng; + } + + // Note that the sysData.sceneEpsilon is applied on both sides of the shadow ray [t_min, t_max] interval + // to prevent self-intersections with the actual light geometry in the scene. + optixTrace(sysData.topObject, + origin, lightSample.direction, + sysData.sceneEpsilon, lightSample.distance - sysData.sceneEpsilon, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT, // The shadow ray type only uses anyhit programs. + TYPE_RAY_SHADOW, NUM_RAY_TYPES, TYPE_RAY_SHADOW, + p0, p1); // Pass through thePrd to the shadow ray. + + if ((thePrd->flags & FLAG_SHADOW) == 0) // Shadow flag not set? + { + const float weightMIS = (TYPE_LIGHT_POINT <= light.typeLight) ? 1.0f : balanceHeuristic(lightSample.pdf, eval_data.pdf); + + // The sampled emission needs to be scaled by the inverse probability to have selected this light, + // Selecting one of many lights means the inverse of 1.0f / numLights. + // This is using the path throughput before the sampling modulated it above. + thePrd->radiance += throughput * bxdf * lightSample.radiance_over_pdf * (float(numLights) * weightMIS); + } + } + } + } + + // Now after everything has been handled using the current material stack, + // adjust the material stack if there was a transmission crossing a boundary surface. + if (!thin_walled && (thePrd->eventType & mi::neuraylib::BSDF_EVENT_TRANSMISSION) != 0) + { + if (isFrontFace) // Entered a volume. + { + float3 absorption = shaderConfiguration.absorption_coefficient; + if (0 <= shaderConfiguration.idxCallVolumeAbsorptionCoefficient) + { + absorption = optixDirectCall(shaderConfiguration.idxCallVolumeAbsorptionCoefficient, &state, &res_data, material.arg_block); + } + + float3 scattering = shaderConfiguration.scattering_coefficient; + if (0 <= shaderConfiguration.idxCallVolumeScatteringCoefficient) + { + scattering = optixDirectCall(shaderConfiguration.idxCallVolumeScatteringCoefficient, &state, &res_data, material.arg_block); + } + + float bias = shaderConfiguration.directional_bias; + if (0 <= shaderConfiguration.idxCallVolumeDirectionalBias) + { + bias = optixDirectCall(shaderConfiguration.idxCallVolumeDirectionalBias, &state, &res_data, material.arg_block); + } + + const int idx = min(thePrd->idxStack + 1, MATERIAL_STACK_LAST); // Push current medium parameters. + + thePrd->idxStack = idx; + thePrd->stack[idx].ior = ior; + thePrd->stack[idx].sigma_a = absorption; + thePrd->stack[idx].sigma_s = scattering; + thePrd->stack[idx].bias = bias; + + thePrd->sigma_t = absorption + scattering; // Update the current extinction coefficient. + } + else // if !isFrontFace. Left a volume. + { + const int idx = max(0, thePrd->idxStack - 1); // Pop current medium parameters. + + thePrd->idxStack = idx; + + thePrd->sigma_t = thePrd->stack[idx].sigma_a + thePrd->stack[idx].sigma_s; // Update the current extinction coefficient. + } + + thePrd->walk = 0; // Reset the number of random walk steps taken when crossing any volume boundary. + } + + // Move the SDF position out of the epsilon environment to be able to continue the path without self-intersections. + // Adjust the SDF offset inside the GUI if the default of 2.0f is insufficient. + if (isSDF) + { + // When there is a transmission the continuation ray origin needs to be placed on the other side of the SDF iso-surface and outside the epsilon environment. + sdfSign = ((thePrd->eventType & mi::neuraylib::BSDF_EVENT_TRANSMISSION) == 0) ? sdfSign : -sdfSign; + + thePrd->pos += (sdfSign * sysData.sdfOffset * sysData.sdfEpsilon) * ng; + } +} + + +// One anyhit program for the radiance ray for all materials with cutout opacity! +extern "C" __global__ void __anyhit__radiance_cutout() +{ + const GeometryInstanceData theData = sysData.geometryInstanceData[optixGetInstanceId()]; + + // Cast the CUdeviceptr to the actual format for Triangles geometry. + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const uint3* indices = reinterpret_cast(theData.indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData.attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(optixGetTransformListHandle(0), objectToWorld, worldToObject); // Single instance level transformation list only. + + // Object space vertex attributes at the hit point. + float3 po = attr0.vertex * alpha + attr1.vertex * theBarycentrics.x + attr2.vertex * theBarycentrics.y; + float3 ns = attr0.normal * alpha + attr1.normal * theBarycentrics.x + attr2.normal * theBarycentrics.y; + float3 ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + float3 tg = attr0.tangent * alpha + attr1.tangent * theBarycentrics.x + attr2.tangent * theBarycentrics.y; + + // Transform attributes into internal space == world space. + po = transformPoint(objectToWorld, po); + ns = normalize(transformNormal(worldToObject, ns)); + ng = normalize(transformNormal(worldToObject, ng)); + // This is actually the geometry tangent which for the runtime generated geometry objects + // (plane, box, sphere, torus) match exactly with the texture space tangent. + // FIXME Generate these from the triangle's texture derivatives instead, but that's more expensive. + // Mind that tangents and bitangents are transformed as vectors, not normals, because they lie inside the surface's plane. + tg = normalize(transformVector(objectToWorld, tg)); + // Calculate an ortho-normal system respective to the shading normal. + // Expanding the TBN tbn(tg, ns) constructor because TBN members can't be used as pointers for the Mdl_state with NUM_TEXTURE_SPACES > 1. + float3 bt = normalize(cross(ns, tg)); + tg = cross(bt, ns); // Now the tangent is orthogonal to the shading normal. + + // The Mdl_state holds the texture attributes per texture space in separate arrays. + float3 texture_coordinates[NUM_TEXTURE_SPACES]; + float3 texture_tangents[NUM_TEXTURE_SPACES]; + float3 texture_bitangents[NUM_TEXTURE_SPACES]; + + // NUM_TEXTURE_SPACES is always at least 1. + texture_coordinates[0] = attr0.texcoord * alpha + attr1.texcoord * theBarycentrics.x + attr2.texcoord * theBarycentrics.y; + texture_bitangents[0] = bt; + texture_tangents[0] = tg; + +#if NUM_TEXTURE_SPACES == 2 + // HACK Copy the vertex attributes of texture space 0, simply because there is no second texcoord inside TriangleAttributes. + texture_coordinates[1] = texture_coordinates[0]; + texture_bitangents[1] = bt; + texture_tangents[1] = tg; +#endif + + // Setup the Mdl_state. + Mdl_state state; + + float4 texture_results[NUM_TEXTURE_RESULTS]; + + // For explanations of these fields see comments inside __closesthit__radiance above. + state.normal = ns; + state.geom_normal = ng; + state.position = po; + state.animation_time = 0.0f; + state.text_coords = texture_coordinates; + state.tangent_u = texture_tangents; + state.tangent_v = texture_bitangents; + state.text_results = texture_results; + state.ro_data_segment = nullptr; + state.world_to_object = worldToObject; + state.object_to_world = objectToWorld; + state.object_id = theData.ids.z; + state.meters_per_scene_unit = 1.0f; + + const MaterialDefinitionMDL& material = sysData.materialDefinitionsMDL[theData.ids.x]; + + mi::neuraylib::Resource_data res_data = { nullptr, material.texture_handler }; + + // The cutout opacity value needs to be determined based on the ShaderConfiguration data and geometry.cutout expression when needed. + const DeviceShaderConfiguration& shaderConfiguration = sysData.shaderConfigurations[material.indexShader]; + + // Using a single material init function instead of per distribution init functions. + // PERF See how that affects cutout opacity which only needs the geometry.cutout expression. + float opacity = shaderConfiguration.cutout_opacity; + + if (0 <= shaderConfiguration.idxCallGeometryCutoutOpacity) + { + // This is always present, even if it just returns. + optixDirectCall(shaderConfiguration.idxCallInit, &state, &res_data, material.arg_block); + + opacity = optixDirectCall(shaderConfiguration.idxCallGeometryCutoutOpacity, &state, &res_data, material.arg_block); + } + + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Stochastic alpha test to get an alpha blend effect. + // No need to calculate an expensive random number if the test is going to fail anyway. + if (opacity < 1.0f && opacity <= rng(thePrd->seed)) + { + optixIgnoreIntersection(); + } +} + + +// The shadow ray program for all materials with no cutout opacity. +extern "C" __global__ void __anyhit__shadow() +{ + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Always set payload values before calling optixIgnoreIntersection or optixTerminateRay because they return immediately! + thePrd->flags |= FLAG_SHADOW; // Visbility check failed. + + optixTerminateRay(); +} + + +extern "C" __global__ void __anyhit__shadow_cutout() // For the radiance ray type. +{ + const GeometryInstanceData theData = sysData.geometryInstanceData[optixGetInstanceId()]; + + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const uint3* indices = reinterpret_cast(theData.indices); + const uint3 tri = indices[thePrimitiveIndex]; + + // Cast the CUdeviceptr to the actual format for Triangles geometry. + const TriangleAttributes* attributes = reinterpret_cast(theData.attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(optixGetTransformListHandle(0), objectToWorld, worldToObject); // Single instance level transformation list only. + + // Object space vertex attributes at the hit point. + float3 po = attr0.vertex * alpha + attr1.vertex * theBarycentrics.x + attr2.vertex * theBarycentrics.y; + float3 ns = attr0.normal * alpha + attr1.normal * theBarycentrics.x + attr2.normal * theBarycentrics.y; + float3 ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + float3 tg = attr0.tangent * alpha + attr1.tangent * theBarycentrics.x + attr2.tangent * theBarycentrics.y; + + // Transform attributes into internal space == world space. + po = transformPoint(objectToWorld, po); + ns = normalize(transformNormal(worldToObject, ns)); + ng = normalize(transformNormal(worldToObject, ng)); + // This is actually the geometry tangent which for the runtime generated geometry objects + // (plane, box, sphere, torus) match exactly with the texture space tangent. + // FIXME Generate these from the triangle's texture derivatives instead, but that's more expensive. + // Mind that tangents and bitangents are transformed as vectors, not normals, because they lie inside the surface's plane. + tg = normalize(transformVector(objectToWorld, tg)); + // Calculate an ortho-normal system respective to the shading normal. + // Expanding the TBN tbn(tg, ns) constructor because TBN members can't be used as pointers for the Mdl_state with NUM_TEXTURE_SPACES > 1. + float3 bt = normalize(cross(ns, tg)); + tg = cross(bt, ns); // Now the tangent is orthogonal to the shading normal. + + // The Mdl_state holds the texture attributes per texture space in separate arrays. + float3 texture_coordinates[NUM_TEXTURE_SPACES]; + float3 texture_tangents[NUM_TEXTURE_SPACES]; + float3 texture_bitangents[NUM_TEXTURE_SPACES]; + + // NUM_TEXTURE_SPACES is always at least 1. + texture_coordinates[0] = attr0.texcoord * alpha + attr1.texcoord * theBarycentrics.x + attr2.texcoord * theBarycentrics.y; + texture_bitangents[0] = bt; + texture_tangents[0] = tg; + +#if NUM_TEXTURE_SPACES == 2 + // HACK Copy the vertex attributes of texture space 0, simply because there is no second texcoord inside TriangleAttributes. + texture_coordinates[1] = texture_coordinates[0]; + texture_bitangents[1] = bt; + texture_tangents[1] = tg; +#endif + + // Setup the Mdl_state. + Mdl_state state; + + float4 texture_results[NUM_TEXTURE_RESULTS]; + + // For explanations of these fields see comments inside __closesthit__radiance above. + state.normal = ns; + state.geom_normal = ng; + state.position = po; + state.animation_time = 0.0f; + state.text_coords = texture_coordinates; + state.tangent_u = texture_tangents; + state.tangent_v = texture_bitangents; + state.text_results = texture_results; + state.ro_data_segment = nullptr; + state.world_to_object = worldToObject; + state.object_to_world = objectToWorld; + state.object_id = theData.ids.z; + state.meters_per_scene_unit = 1.0f; + + const MaterialDefinitionMDL& material = sysData.materialDefinitionsMDL[theData.ids.x]; + + mi::neuraylib::Resource_data res_data = { nullptr, material.texture_handler }; + + // The cutout opacity value needs to be determined based on the ShaderConfiguration data and geometry.cutout expression when needed. + const DeviceShaderConfiguration& shaderConfiguration = sysData.shaderConfigurations[material.indexShader]; + + float opacity = shaderConfiguration.cutout_opacity; + + if (0 <= shaderConfiguration.idxCallGeometryCutoutOpacity) + { + optixDirectCall(shaderConfiguration.idxCallInit, &state, &res_data, material.arg_block); + + opacity = optixDirectCall(shaderConfiguration.idxCallGeometryCutoutOpacity, &state, &res_data, material.arg_block); + } + + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Stochastic alpha test to get an alpha blend effect. + // No need to calculate an expensive random number if the test is going to fail anyway. + if (opacity < 1.0f && opacity <= rng(thePrd->seed)) + { + optixIgnoreIntersection(); + } + else + { + // Always set payload values before calling optixIgnoreIntersection or optixTerminateRay because they return immediately! + thePrd->flags |= FLAG_SHADOW; + + optixTerminateRay(); + } +} + + +// Explicit light sampling of a triangle mesh geometry with an emissive MDL material. +// Defined here to be able to use the MDL runtime functions included via texture_lookup.h. +extern "C" __device__ LightSample __direct_callable__light_mesh(const LightDefinition& light, PerRayData* prd) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; + + const float3 sampleTriangle = rng3(prd->seed); + + // Uniformly sample the triangles over their surface area. + // Note that zero-area triangles (e.g. at the poles of spheres) are automatically never sampled with this method! + // The cdfU is one bigger than light.width. + const float* cdfArea = reinterpret_cast(light.cdfU); + const unsigned int idxTriangle = binarySearchCDF(cdfArea, light.width, sampleTriangle.z); + + // Unit square to triangle via barycentric coordinates. + const float su = sqrtf(sampleTriangle.x); + // Barycentric coordinates. + const float alpha = 1.0f - su; + const float beta = sampleTriangle.y * su; + const float gamma = 1.0f - alpha - beta; + + // This cast works because both unsigned int and uint3 have an alignment of 4 bytes. + const uint3* indices = reinterpret_cast(light.indices); + const uint3 tri = indices[idxTriangle]; + + const TriangleAttributes* attributes = reinterpret_cast(light.attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + // Object space vertex attributes at the hit point. + float3 po = attr0.vertex * alpha + attr1.vertex * beta + attr2.vertex * gamma; + + // Transform attributes into internal space == world space. + po = transformPoint(light.matrix, po); + + // Calculate the outgoing direction from light sample position to surface point. + lightSample.direction = po - prd->pos; // Sample direction from surface point to light sample position. + lightSample.distance = length(lightSample.direction); + + if (lightSample.distance < DENOMINATOR_EPSILON) + { + return lightSample; + } + + lightSample.direction *= 1.0f / lightSample.distance; // Normalized vector from light sample position to surface point. + + float3 ns = attr0.normal * alpha + attr1.normal * beta + attr2.normal * gamma; + float3 ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + float3 tg = attr0.tangent * alpha + attr1.tangent * beta + attr2.tangent * gamma; + + // Transform attributes into internal space == world space. + ns = normalize(transformNormal(light.matrixInv, ns)); + ng = normalize(transformNormal(light.matrixInv, ng)); + tg = normalize(transformVector(light.matrix, tg)); + + float3 bt = normalize(cross(ns, tg)); + tg = cross(bt, ns); // Now the tangent is orthogonal to the shading normal. + + // The Mdl_state holds the texture attributes per texture space in separate arrays. + float3 texture_coordinates[NUM_TEXTURE_SPACES]; + float3 texture_tangents[NUM_TEXTURE_SPACES]; + float3 texture_bitangents[NUM_TEXTURE_SPACES]; + + // NUM_TEXTURE_SPACES is always at least 1. + texture_coordinates[0] = attr0.texcoord * alpha + attr1.texcoord * beta + attr2.texcoord * gamma; + texture_bitangents[0] = bt; + texture_tangents[0] = tg; + +#if NUM_TEXTURE_SPACES == 2 + // HACK Copy the vertex attributes of texture space 0, simply because there is no second texcoord inside TriangleAttributes. + texture_coordinates[1] = texture_coordinates[0]; + texture_bitangents[1] = bt; + texture_tangents[1] = tg; +#endif + + // Setup the Mdl_state. + Mdl_state state; + + float4 texture_results[NUM_TEXTURE_RESULTS]; + + state.normal = ns; + state.geom_normal = ng; + state.position = po; + state.animation_time = 0.0f; + state.text_coords = texture_coordinates; + state.tangent_u = texture_tangents; + state.tangent_v = texture_bitangents; + state.text_results = texture_results; + state.ro_data_segment = nullptr; + state.world_to_object = light.matrixInv; + state.object_to_world = light.matrix; + state.object_id = light.idObject; + state.meters_per_scene_unit = 1.0f; + + const MaterialDefinitionMDL& material = sysData.materialDefinitionsMDL[light.idMaterial]; + + mi::neuraylib::Resource_data res_data = { nullptr, material.texture_handler }; + + const DeviceShaderConfiguration& shaderConfiguration = sysData.shaderConfigurations[material.indexShader]; + + // This is always present, even if it just returns. + optixDirectCall(shaderConfiguration.idxCallInit, &state, &res_data, material.arg_block); + + // Arbitrary mesh lights can have cutout opacity! + float opacity = shaderConfiguration.cutout_opacity; + + if (0 <= shaderConfiguration.idxCallGeometryCutoutOpacity) + { + opacity = optixDirectCall(shaderConfiguration.idxCallGeometryCutoutOpacity, &state, &res_data, material.arg_block); + } + + // If the current light sample is inside a fully cutout region, reject that sample. + if (opacity <= 0.0f) + { + return lightSample; + } + + // Note that lightSample.direction is from surface point to light sample position. + const bool isFrontFace = (dot(lightSample.direction, state.geom_normal) < 0.0f); + + // thin_walled value in case the expression was a constant (idxCallThinWalled < 0). + bool thin_walled = ((shaderConfiguration.flags & IS_THIN_WALLED) != 0); + + if (0 <= shaderConfiguration.idxCallThinWalled) + { + thin_walled = optixDirectCall(shaderConfiguration.idxCallThinWalled, &state, &res_data, material.arg_block); + } + + // Default to no EDF. + int idxCallEmissionEval = -1; + int idxCallEmissionIntensity = -1; + int idxCallEmissionIntensityMode = -1; + // These are not used when there is no emission, no need to initialize. + float3 emission_intensity; + int emission_intensity_mode; + + // MDL Specs: There is no emission on the back-side unless an EDF is specified with the backface field and thin_walled is set to true. + if (isFrontFace) + { + idxCallEmissionEval = shaderConfiguration.idxCallSurfaceEmissionEval; + idxCallEmissionIntensity = shaderConfiguration.idxCallSurfaceEmissionIntensity; + idxCallEmissionIntensityMode = shaderConfiguration.idxCallSurfaceEmissionIntensityMode; + + emission_intensity = shaderConfiguration.surface_intensity; + emission_intensity_mode = shaderConfiguration.surface_intensity_mode; + } + else if (thin_walled) // && !isFrontFace + { + // These can be the same callable indices if the expressions from surface and backface were identical. + idxCallEmissionEval = shaderConfiguration.idxCallBackfaceEmissionEval; + idxCallEmissionIntensity = shaderConfiguration.idxCallBackfaceEmissionIntensity; + idxCallEmissionIntensityMode = shaderConfiguration.idxCallBackfaceEmissionIntensityMode; + + emission_intensity = shaderConfiguration.backface_intensity; + emission_intensity_mode = shaderConfiguration.backface_intensity_mode; + } + + // Check if the hit geometry contains any emission. + if (0 <= idxCallEmissionEval) + { + if (0 <= idxCallEmissionIntensity) // Emission intensity is not a constant. + { + emission_intensity = optixDirectCall(idxCallEmissionIntensity, &state, &res_data, material.arg_block); + } + if (0 <= idxCallEmissionIntensityMode) // Emission intensity mode is not a constant. + { + emission_intensity_mode = optixDirectCall(idxCallEmissionIntensityMode, &state, &res_data, material.arg_block); + } + + if (isNotNull(emission_intensity)) + { + mi::neuraylib::Edf_evaluate_data eval_data; + + eval_data.k1 = -lightSample.direction; // input: outgoing direction (from light sample position to surface point). + //eval_data.cos : output: dot(normal, k1) + //eval_data.edf : output: edf + //eval_data.pdf : output: pdf (non-projected hemisphere) + + optixDirectCall(idxCallEmissionEval, &eval_data, &state, &res_data, material.arg_block); + + // Modulate the emission with the cutout opacity value to get the correct value. + // The opacity value must not be greater than one here, which could happen for HDR textures. + opacity = min(opacity, 1.0f); + + // Power (flux) [W] divided by light area gives radiant exitance [W/m^2]. + const float factor = (emission_intensity_mode == 0) ? opacity : opacity / light.area; + + lightSample.pdf = lightSample.distance * lightSample.distance / (light.area * eval_data.cos); // Solid angle measure. + + lightSample.radiance_over_pdf = emission_intensity * eval_data.edf * (factor / lightSample.pdf); + } + } + + return lightSample; +} + + +extern "C" __global__ void __closesthit__curves() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->flags |= FLAG_HIT; // Required to distinguish surface hits from random walk miss. + + thePrd->distance = optixGetRayTmax(); // Return the current path segment distance, needed for absorption calculations in the integrator. + + // Note that no adjustment to the hit position is done here! + // The OptiX curve primitives are not intersecting with backfaces, which means the sceneEpsilon + // offset on the continuation ray t_min is enough to prevent self-intersections independently + // of the continuation ray direction being a reflection or transmisssion. + //thePrd->pos = optixGetWorldRayOrigin() + optixGetWorldRayDirection() * optixGetRayTmax(); + thePrd->pos += thePrd->wi * thePrd->distance; + + // If we're inside a volume and hit something, the path throughput needs to be modulated + // with the transmittance along this segment before adding surface or light radiance! + if (0 < thePrd->idxStack) // This assumes the first stack entry is vaccuum. + { + thePrd->throughput *= expf(thePrd->sigma_t * -thePrd->distance); + + // Increment the volume scattering random walk counter. + // Unused when FLAG_VOLUME_SCATTERING is not set. + ++thePrd->walk; + } + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(optixGetTransformListHandle(0), objectToWorld, worldToObject); // Single instance level transformation list only. + + const GeometryInstanceData theData = sysData.geometryInstanceData[optixGetInstanceId()]; + + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const unsigned int* indices = reinterpret_cast(theData.indices); + const unsigned int index = indices[thePrimitiveIndex]; + + const CurveAttributes* attributes = reinterpret_cast(theData.attributes); + + float4 spline[4]; + + spline[0] = attributes[index ].vertex; + spline[1] = attributes[index + 1].vertex; + spline[2] = attributes[index + 2].vertex; + spline[3] = attributes[index + 3].vertex; + + // Fixed bitangent-like reference vector for the vFiber calculation. + float3 rf = make_float3(attributes[index].reference); + + const float4 t = make_float4(attributes[index ].texcoord.w, + attributes[index + 1].texcoord.w, + attributes[index + 2].texcoord.w, + attributes[index + 3].texcoord.w); + + CubicInterpolator interpolator; + + interpolator.initializeFromBSpline(spline); + + // Convenience function for __uint_as_float( optixGetAttribute_0() ); + const float u = optixGetCurveParameter(); + + // Optimized (see curve.h): + const float o_s = 1.0f / 6.0f; + const float ts0 = (t.w - t.x) * o_s + (t.y - t.z) * 0.5f; + const float ts1 = (t.x + t.z) * 0.5f - t.y; + const float ts2 = (t.z - t.x) * 0.5f; + const float ts3 = (t.x + t.y * 4.0f + t.z) * o_s; + + // Coordinate in range [0.0f, 1.0f] from root to tip along the whole fiber. + const float uFiber = ( ( ( ts0 * u ) + ts1 ) * u + ts2 ) * u + ts3; + + const float4 fiberPosition = interpolator.position4(u); // .xyz = object space position, .w = radius of the curve's center line at the interpolant u. + + float3 tg = interpolator.velocity3(u); // Unnormalized object space tangent along the fiber from root to tip at the interpolant u. + + float3 position = transformPoint(worldToObject, thePrd->pos); // Transform to object space. + float3 ns = surfaceNormal(interpolator, u, position); // This moves position onto the surface of the curve in object space. + + // Transform into renderer internal space == world space. + rf = normalize(transformVector(objectToWorld, rf)); + tg = normalize(transformVector(objectToWorld, tg)); + ns = normalize(transformNormal(worldToObject, ns)); + + // Calculate an ortho-normal system for the fiber surface hit point. The shading normal is the fixed vector here! + // Expanding the TBN tbn(tg, ns) constructor because TBN members can't be used as pointers for the Mdl_state with NUM_TEXTURE_SPACES > 1. + float3 bt = normalize(cross(ns, tg)); + tg = cross(bt, ns); // Now the tangent is orthogonal to the shading normal. + + // Transform the constant reference fiber bitangent vector into the local fiber coordinate system. + // This makes the coordinate [0.0f, 1.0f] around the cross section of the hair relative to the hit point. + // Expanded proj = fbn.transformToLocal(rf); + // Only need the projected y- and z-components.(This works without normalization. + const float2 proj = make_float2(dot(rf, bt), dot(rf, ns)); + + // The (bitangent, normal) plane contains the cross section of the intersection. + // I want vFiber go from 0.0f to 1.0f counter-clockwise around the fiber when looking from the fiber root along the fiber. + // As texture coordinate this means lower-left origin texture coordinates like in OpenGL. + const float vFiber = (atan2f(-proj.x, proj.y) + M_PIf) * 0.5f * M_1_PIf; + + // The Mdl_state holds the texture attributes per texture space in separate arrays. + // NUM_TEXTURE_SPACES is always at least 1. + float3 texture_coordinates[NUM_TEXTURE_SPACES]; + float3 texture_tangents[NUM_TEXTURE_SPACES]; + float3 texture_bitangents[NUM_TEXTURE_SPACES]; + + // In hair shading, texture spaces contain the following values: + // texture_coordinate(0).x: The normalized position of the intersection point along the hair fiber in the range from zero for the root of the fiber to one for the tip of the fiber. + // texture_coordinate(0).y: The normalized position of the intersection point around the hair fiber in the range from zero to one. + // texture_coordinate(0).z: The thickness of the hair fiber at the intersection point in internal space. + texture_coordinates[0] = make_float3(uFiber, vFiber, fiberPosition.w * 2.0f); // .z = thickness = radius * 2.0f + texture_bitangents[0] = bt; + texture_tangents[0] = tg; + +#if NUM_TEXTURE_SPACES == 2 + // PERF Only ever set NUM_TEXTURE_SPACES to 2 if you need the hair BSDF to fully work, + // texture_coordinate(1): A position of the root of the hair fiber, for example, from a texture space of a surface supporting the hair fibers. This position is constant for a fiber. + // Fixed texture coordinate for the fiber. Only loaded when NUM_TEXTURE_SPACES == 2. + texture_coordinates[1] = make_float3(attributes[index].texcoord); + // HACK Copy the vertex attributes of texture space 0. + texture_bitangents[1] = bt; + texture_tangents[1] = tg; +#endif + + Mdl_state state; + + float4 texture_results[NUM_TEXTURE_RESULTS]; + + state.normal = ns; + state.geom_normal = ns; + state.position = thePrd->pos; + state.animation_time = 0.0f; + state.text_coords = texture_coordinates; + state.tangent_u = texture_tangents; + state.tangent_v = texture_bitangents; + state.text_results = texture_results; + state.ro_data_segment = nullptr; + state.world_to_object = worldToObject; + state.object_to_world = objectToWorld; + state.object_id = theData.ids.z; + state.meters_per_scene_unit = 1.0f; + + const MaterialDefinitionMDL& material = sysData.materialDefinitionsMDL[theData.ids.x]; + + mi::neuraylib::Resource_data res_data = { nullptr, material.texture_handler }; + + const DeviceShaderConfiguration& shaderConfiguration = sysData.shaderConfigurations[material.indexShader]; + + // This is always present, even if it just returns. + optixDirectCall(shaderConfiguration.idxCallInit, &state, &res_data, material.arg_block); + + // Start fresh with the next BSDF sample. + // Save the current path throughput for the direct lighting contribution. + // The path throughput will be modulated with the BSDF sampling results before that. + const float3 throughput = thePrd->throughput; + // The pdf of the previous event was needed for the emission calculation above. + thePrd->pdf = 0.0f; + + // Importance sample the hair BSDF. + if (0 <= shaderConfiguration.idxCallHairSample) + { + mi::neuraylib::Bsdf_sample_data sample_data; + + int idx = thePrd->idxStack; + + // FIXME The MDL-SDK libbsdf_hair.h ignores these ior values and only uses the ior value from the chiang_hair_bsdf structure! + sample_data.ior1 = thePrd->stack[idx].ior; // From surrounding medium ior + sample_data.ior2.x = MI_NEURAYLIB_BSDF_USE_MATERIAL_IOR; // to material ior. + sample_data.k1 = thePrd->wo; // == -optixGetWorldRayDirection() + sample_data.xi = rng4(thePrd->seed); + + optixDirectCall(shaderConfiguration.idxCallHairSample, &sample_data, &state, &res_data, material.arg_block); + + thePrd->wi = sample_data.k2; // Continuation direction. + thePrd->throughput *= sample_data.bsdf_over_pdf; // Adjust the path throughput for all following incident lighting. + thePrd->pdf = sample_data.pdf; // Specular events return pdf == 0.0f! + thePrd->eventType = sample_data.event_type; // This replaces the previous PRD flags. + } + else + { + // If there is no valid scattering BSDF, it's the black bsdf() which ends the path. + // This is usually happening with arbitrary mesh lights which only specify emission. + thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; + // None of the following code will have any effect in that case. + return; + } + + // Direct lighting if the sampled BSDF was diffuse and any light is in the scene. + const int numLights = sysData.numLights; + + if (sysData.directLighting && 0 < numLights && (thePrd->eventType & (mi::neuraylib::BSDF_EVENT_DIFFUSE | mi::neuraylib::BSDF_EVENT_GLOSSY))) + { + // Sample one of many lights. + // The caller picks the light to sample. Make sure the index stays in the bounds of the sysData.lightDefinitions array. + const int indexLight = (1 < numLights) ? clamp(static_cast(floorf(rng(thePrd->seed) * numLights)), 0, numLights - 1) : 0; + + const LightDefinition& light = sysData.lightDefinitions[indexLight]; + + LightSample lightSample = optixDirectCall(NUM_LENS_TYPES + light.typeLight, light, thePrd); + + if (0.0f < lightSample.pdf && 0 <= shaderConfiguration.idxCallHairEval) // Useful light sample and valid shader? + { + mi::neuraylib::Bsdf_evaluate_data eval_data; + + int idx = thePrd->idxStack; + + // DAR FIXME The MDL-SDK libbsdf_hair.h ignores these values and only uses the ior value from the chiang_hair-bsdf structure! + eval_data.ior1 = thePrd->stack[idx].ior; // From surrounding medium ior + eval_data.ior2.x = MI_NEURAYLIB_BSDF_USE_MATERIAL_IOR; // to material ior. + eval_data.k1 = thePrd->wo; + eval_data.k2 = lightSample.direction; + + optixDirectCall(shaderConfiguration.idxCallHairEval, &eval_data, &state, &res_data, material.arg_block); + + // DAR DEBUG This already contains the fabsf(dot(lightSample.direction, state.normal)) factor! + // For a white Lambert material, the bxdf components match the eval_data.pdf + const float3 bxdf = eval_data.bsdf_diffuse + eval_data.bsdf_glossy; + + if (0.0f < eval_data.pdf && isNotNull(bxdf)) + { + // Pass the current payload registers through to the shadow ray. + unsigned int p0 = optixGetPayload_0(); + unsigned int p1 = optixGetPayload_1(); + + thePrd->flags &= ~FLAG_SHADOW; // Clear the shadow flag. + + // Note that the sysData.sceneEpsilon is applied on both sides of the shadow ray [t_min, t_max] interval + // to prevent self-intersections with the actual light geometry in the scene. + optixTrace(sysData.topObject, + thePrd->pos, lightSample.direction, // origin, direction + sysData.sceneEpsilon, lightSample.distance - sysData.sceneEpsilon, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT, // The shadow ray type only uses anyhit programs. + TYPE_RAY_SHADOW, NUM_RAY_TYPES, TYPE_RAY_SHADOW, + p0, p1); // Pass through thePrd to the shadow ray. + + if ((thePrd->flags & FLAG_SHADOW) == 0) // Shadow flag not set? + { + const float weightMIS = (TYPE_LIGHT_POINT <= light.typeLight) ? 1.0f : balanceHeuristic(lightSample.pdf, eval_data.pdf); + + // The sampled emission needs to be scaled by the inverse probability to have selected this light, + // Selecting one of many lights means the inverse of 1.0f / numLights. + // This is using the path throughput before the sampling modulated it above. + thePrd->radiance += throughput * bxdf * lightSample.radiance_over_pdf * (float(numLights) * weightMIS); + } + } + } + } +} diff --git a/apps/MDL_sdf/shaders/intersection.cu b/apps/MDL_sdf/shaders/intersection.cu new file mode 100644 index 00000000..7a1a427a --- /dev/null +++ b/apps/MDL_sdf/shaders/intersection.cu @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "sdf_attributes.h" +#include "vector_math.h" + +extern "C" __constant__ SystemData sysData; + +// Compute the near and far intersections of the cube (stored in the x and y components) using the slab method. +// No intersection when tNear > tFar. +__forceinline__ __device__ float2 intersectAABB(float3 origin, float3 direction, float3 minAabb, float3 maxAabb) +{ + const float3 tMin = (minAabb - origin) / direction; + const float3 tMax = (maxAabb - origin) / direction; + + const float3 t0 = fminf(tMin, tMax); + const float3 t1 = fmaxf(tMin, tMax); + + const float tNear = fmaxf(t0); + const float tFar = fminf(t1); + + return make_float2(tNear, tFar); +} + + +// The SDF intersection function for single-component 3D texture data. +extern "C" __global__ void __intersection__sdf() +{ + const GeometryInstanceData theData = sysData.geometryInstanceData[optixGetInstanceId()]; + + // Cast the CUdeviceptr to the actual format for SignedDistanceField geometry data. + const SignedDistanceFieldAttributes* attributes = reinterpret_cast(theData.attributes); + + // There is only one primitive index inside the SDF attribute! + // PERF Explicitly do not load the uint3 sdfTextureSize which is unused here. + const float3 minimum = attributes->minimum; + const float3 maximum = attributes->maximum; + cudaTextureObject_t texture = attributes->sdfTexture; + const float lipschitz = attributes->sdfLipschitz; + + const float3 theRayOrigin = optixGetObjectRayOrigin(); + + // DANGER: The object space ray direction is not normalized if there is a scaling transform over the geometry! + // Both the ray origin and ray direction are transformed into object space. The ray tmin and tmax values are not touched. + // When the ray.direction is not a unit vector, the sphere tracing is not stepping with the expected SDF distances. + // If the AABB was scaled bigger, it would need more steps because the object space direction is shorter. + // And vice versa, which is even worse, when the AABB was scaled smaller, the ray direction vector is longer than 1.0 + // and the sphere tracing will actually overshoot and miss the surface. + // The invLength factor will correct all that! + const float3 theRayDirection = optixGetObjectRayDirection(); + + // At this point calculate start and end values from the intersections of the ray with the primitive's AABB. + float2 t = intersectAABB(theRayOrigin, theRayDirection, minimum, maximum); + + const float theRayTmin = optixGetRayTmin(); + const float theRayTmax = optixGetRayTmax(); + + // Factor scaling the SDF distance into object space. + // Merge the Lipschitz distance scaling factor into that. + const float invLength = 1.0f / (length(theRayDirection) * lipschitz); + + // Factor used during projection of the position into 3D texture space. + const float3 invExtents = 1.0f / (maximum - minimum); + + // PERF Testing the intersection with the SDF only inside the enclosing AABB will make + // shadow rays to environment lights way faster because they do no iterate the full count + // or until distance is infinity. + + // If tNear on the AABB is behind the ray.tmin, use the ray.tmin value as start value for the sphere tracing. + t.x = (theRayTmin < t.x) ? t.x : theRayTmin; + // If the tFar value on the AABB is farther than the ray.tmax, limit the sphere tracing end condition to the ray.tmax value. + t.y = (t.y < theRayTmax) ? t.y : theRayTmax; + + const int count = sysData.sdfIterations; // Maximum number of sphere tracing iterations. + const float sdfEpsilon = sysData.sdfEpsilon; // Minimum distance to SDF iso-surface at 0.0f which counts as hit. + + // The four reported intersection attributes. Unused when there is no intersection. + float sign; + float3 uvw; + + int iteration = 0; + + for (; iteration < count && t.x < t.y; ++iteration) + { + float3 position = theRayOrigin + theRayDirection * t.x; + + uvw = (position - minimum) * invExtents; // Position transformed into 3D texture coordinate space in range [0.0f, 1.0f]. + + float distance = tex3D(texture, uvw.x, uvw.y, uvw.z) * invLength; + + if (iteration == 0) + { + // Positive started ouside the SDF, negative started inside. + sign = copysignf(1.0f, distance); + } + else if (((__float_as_uint(sign) ^ __float_as_uint(distance)) & 0x80000000) != 0) // Detect overshoot (different sign bits). + { + // Adjust the intersection to lie exactly on the iso-surface. + // (sign * distance) is always negative here. + t.x += sign * distance; + + // Recalculate the uvw coordinate on the surface returned as intersection attibute. + position = theRayOrigin + theRayDirection * t.x; + uvw = (position - minimum) * invExtents; + break; + } + + // Here (sign * distance) must be positive. + // Saves an fabsf() in the next condition and is needed for the next sphere tracing step anyway. + distance *= sign; + + // Near enough to the SDF iso-surface? + // This must be on the same side as the start point because the distance's sign hasn't changed. + if (distance < sdfEpsilon) + { + break; + } + + // Next sphere tracing step in positive ray direction, irrespective of the interval's start position due to the (sign * distance) above. + t.x += distance; + } + + if (iteration < count && t.x < t.y) + { + optixReportIntersection(t.x, 1, + __float_as_uint(sign), // The sign attribute is the indicator for isFrontFace of an SDF iso-surface. + __float_as_uint(uvw.x), // Return the texture coordinate at which the iso-surface was found + __float_as_uint(uvw.y), // to have the perfect value for the normal attribute calculation. + __float_as_uint(uvw.z)); + } +} diff --git a/apps/MDL_sdf/shaders/lens_shader.cu b/apps/MDL_sdf/shaders/lens_shader.cu new file mode 100644 index 00000000..e89143a8 --- /dev/null +++ b/apps/MDL_sdf/shaders/lens_shader.cu @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "shader_common.h" + +extern "C" __constant__ SystemData sysData; + +// Note that all these lens shaders return the primary ray origin and direction in world space! + +extern "C" __device__ LensRay __direct_callable__pinhole(const float2 screen, const float2 pixel, const float2 sample) +{ + const float2 fragment = pixel + sample; // Jitter the sub-pixel location + const float2 ndc = (fragment / screen) * 2.0f - 1.0f; // Normalized device coordinates in range [-1, 1]. + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + LensRay ray; + + ray.org = camera.P; + ray.dir = normalize(camera.U * ndc.x + + camera.V * ndc.y + + camera.W); + return ray; +} + + +extern "C" __device__ LensRay __direct_callable__fisheye(const float2 screen, const float2 pixel, const float2 sample) +{ + const float2 fragment = pixel + sample; // x, y + + // Implement a fisheye projection with 180 degrees angle across the image diagonal (=> all pixels rendered, not a circular fisheye). + const float2 center = screen * 0.5f; + const float2 uv = (fragment - center) / length(center); // uv components are in the range [0, 1]. Both 1 in the corners of the image! + const float z = cosf(length(uv) * 0.7071067812f * 0.5f * M_PIf); // Scale by 1.0f / sqrtf(2.0f) to get length into the range [0, 1] + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + const float3 U = normalize(camera.U); + const float3 V = normalize(camera.V); + const float3 W = normalize(camera.W); + + LensRay ray; + + ray.org = camera.P; + ray.dir = normalize(uv.x * U + uv.y * V + z * W); + + return ray; +} + + +extern "C" __device__ LensRay __direct_callable__sphere(const float2 screen, const float2 pixel, const float2 sample) +{ + const float2 uv = (pixel + sample) / screen; // "texture coordinates" + + // Convert the 2D index into a direction. + const float phi = uv.x * 2.0f * M_PIf; + const float theta = uv.y * M_PIf; + + const float sinTheta = sinf(theta); + + const float3 v = make_float3(-sinf(phi) * sinTheta, + -cosf(theta), + -cosf(phi) * sinTheta); + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + const float3 U = normalize(camera.U); + const float3 V = normalize(camera.V); + const float3 W = normalize(camera.W); + + LensRay ray; + + ray.org = camera.P; + ray.dir = normalize(v.x * U + v.y * V + v.z * W); + + return ray; +} diff --git a/apps/MDL_sdf/shaders/light_definition.h b/apps/MDL_sdf/shaders/light_definition.h new file mode 100644 index 00000000..fc6356a3 --- /dev/null +++ b/apps/MDL_sdf/shaders/light_definition.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef LIGHT_DEFINITION_H +#define LIGHT_DEFINITION_H + +#include "function_indices.h" + +struct LightDefinition +{ + // 16 byte alignment + // These are used for arbitrary mesh lights. + float4 matrix[3]; // Object to world coordinates. + float4 matrixInv[3]; // World to object coordinates. + + // 8 byte alignment + CUdeviceptr attributes; // VertexAttributes of triangles for mesh lights. + CUdeviceptr indices; // unsigned int triangle indices for mesh lights. + + // Importance sampling information for the emission texture. + // Used by environment lights when textured. + CUdeviceptr cdfU; // float 2D, (width + 1) * height elements. + CUdeviceptr cdfV; // float 1D, (height + 1) elements. + + cudaTextureObject_t textureEmission; // Emisson texture. If not zero, scales emission. + cudaTextureObject_t textureProfile; // The IES light profile as luminance float texture. + + // 4 byte alignment + // These are only used in the spherical texture environment light and singular point, spot, and IES lights. + // They are not used in arbitrary mesh lights. + // Note that these will contain the identity matrix for mesh lights! + float3 ori[3]; // Object to world orientation (rotational part of matrix). + float3 oriInv[3]; // World to object orientation (rotational part of matrixInv). + + float3 emission; // Emission of the light. (The multiplier is applied on the host.) + + TypeLight typeLight; + + int idMaterial; // The arbitrary mesh lights with MDL materials require a material index + // to be able to determine the shader configuration which defines the emission sample and evaluate functions. + + int idObject; // Object ID for the state.object_id field. Comes from the sg::Instance node ID. Only used for mesh lights. + + float area; // The world space area of arbitrary mesh lights. Unused for other lights. + float invIntegral; // The spherical environment map light texture map integral over the whole texture, inversed to save a division. + + // Emission texture width and height. Used to index the CDFs, see above. + // For mesh lights the width matches the number of triangles and the cdfU is over the triangle areas. + unsigned int width; + unsigned int height; + + float spotAngleHalf; // Spot light cone half angle in radians, max. PI/2 (MaterialGUI has full angle in degrees.) + float spotExponent; // Affects the falloff from cone center to cone edge of the spot light. + + // Structure size padding to multiple of 16 bytes. + int pad0; + int pad1; + //int pad2; + //int pad3; +}; + + +struct LightSample // In world space coordinates. +{ + float3 direction; // Direction from surface to light sampling position. + float distance; // Distance between surface and light sample positon, RT_DEFAULT_MAX for environment light. + float3 radiance_over_pdf; // Radiance of this light sample divided by the pdf. + float pdf; // Probability density for this light sample projected to solid angle. 1.0 when singular light. +}; + +#endif // LIGHT_DEFINITION_H diff --git a/apps/MDL_sdf/shaders/light_sample.cu b/apps/MDL_sdf/shaders/light_sample.cu new file mode 100644 index 00000000..a8374f9c --- /dev/null +++ b/apps/MDL_sdf/shaders/light_sample.cu @@ -0,0 +1,290 @@ + /* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" + +#include "per_ray_data.h" +#include "random_number_generators.h" +#include "shader_common.h" +#include "transform.h" + +extern "C" __constant__ SystemData sysData; + + +// Note that all light sampling routines return lightSample.direction and lightSample.distance in world space! + +extern "C" __device__ LightSample __direct_callable__light_env_constant(const LightDefinition& light, PerRayData* prd) +{ + LightSample lightSample; + + const float2 sample = rng2(prd->seed); + + unitSquareToSphere(sample.x, sample.y, lightSample.direction, lightSample.pdf); + + // The emission is constant in all directions. + // There is no transformation of the object space direction into world space necessary. + + lightSample.distance = RT_DEFAULT_MAX; // Environment light. + + lightSample.radiance_over_pdf = light.emission / lightSample.pdf; + + return lightSample; +} + + +extern "C" __device__ LightSample __direct_callable__light_env_sphere(const LightDefinition& light, PerRayData* prd) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; + + // Importance-sample the spherical environment light direction in object space. + // FIXME The binary searches are generating a lot of memory traffic. Replace this with an alias-map lookup. + const float2 sample = rng2(prd->seed); + + // Note that the marginal CDF is one bigger than the texture height. As index this is the 1.0f at the end of the CDF. + const float* cdfV = reinterpret_cast(light.cdfV); + const unsigned int idxV = binarySearchCDF(cdfV, light.height, sample.y); + + const float* cdfU = reinterpret_cast(light.cdfU); + cdfU += (light.width + 1) * idxV; // Horizontal CDF is one bigger than the texture width! + const unsigned int idxU = binarySearchCDF(cdfU, light.width, sample.x); + + // Continuous sampling of the CDF. + const float cdfLowerU = cdfU[idxU]; + const float cdfUpperU = cdfU[idxU + 1]; + const float du = (sample.x - cdfLowerU) / (cdfUpperU - cdfLowerU); + const float u = (float(idxU) + du) / float(light.width); + + const float cdfLowerV = cdfV[idxV]; + const float cdfUpperV = cdfV[idxV + 1]; + const float dv = (sample.y - cdfLowerV) / (cdfUpperV - cdfLowerV); + const float v = (float(idxV) + dv) / float(light.height); + + // Light sample direction vector in object space polar coordinates. + const float phi = u * M_PIf * 2.0f; + const float theta = v * M_PIf; // theta == 0.0f is south pole, theta == M_PIf is north pole. + + const float sinTheta = sinf(theta); + + // All lights shine down the positive z-axis in this renderer. + // Orient the 2D texture map so that the center (u, v) = (0.5, 0.5) lies exactly on the positive z-axis. + // Means the seam from u == 1.0 -> 0.0 lies on the negative z-axis and the u range [0.0, 1.0] + // goes clockwise on the xz-plane when looking from the positive y-axis. + const float3 dir = make_float3( sinf(phi) * sinTheta, // Starting on negative z-axis going around clockwise (to positive x-axis). + -cosf(theta), // From south pole to north pole. + -cosf(phi) * sinTheta); // Starting on negative z-axis. + + // Now rotate that normalized object space direction into world space. + lightSample.direction = transformVector(light.ori, dir); + + lightSample.distance = RT_DEFAULT_MAX; // Environment light. + + // Get the emission from the spherical environment texture. + const float3 emission = make_float3(tex2D(light.textureEmission, u, v)); + + // For simplicity we pretend that we perfectly importance-sampled the actual texture-filtered environment map + // and not the Gaussian-smoothed one used to actually generate the CDFs and uniform sampling in the texel. + // (Note that this does not contain the light.emission which just modulates the texture.) + lightSample.pdf = intensity(emission) * light.invIntegral; + + if (DENOMINATOR_EPSILON < lightSample.pdf) + { + lightSample.radiance_over_pdf = light.emission * emission / lightSample.pdf; + } + + return lightSample; +} + + +extern "C" __device__ LightSample __direct_callable__light_point(const LightDefinition& light, PerRayData* prd) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; // Default return, invalid light sample (backface, edge on, or too near to the surface) + + // Get the world space position from the object to world matrix translation. + const float3 position = make_float3(light.matrix[0].w, light.matrix[1].w, light.matrix[2].w); + + lightSample.direction = position - prd->pos; // Sample direction from surface point to light sample position. + + const float distanceSquared = dot(lightSample.direction, lightSample.direction); + + if (DENOMINATOR_EPSILON < distanceSquared) + { + lightSample.distance = sqrtf(distanceSquared); + lightSample.direction *= 1.0f / lightSample.distance; // Normalized direction to light. + + // Hardcoded singular lights are defined in visible radiance directly, don't normalize by 0.25f * M_1_PIf. + float3 emission = light.emission * (1.0f / distanceSquared); // Quadratic attenuation. + + // The emission texture is used as spherical projection around the point light similar to spherical environment lights. + // By design all lights in this renderer shine down the light's local positive z-Axis, which is the "normal" direction for rect and mesh lights. + if (light.textureEmission) + { + // Transform the direction from light to surface from world space into light object space. + const float3 R = transformVector(light.oriInv, -lightSample.direction); + + // All lights shine down the positive z-axis in this renderer. + // Means the spherical texture coordinate seam u == 0.0 == 1.0 is on the negative z-axis direction now. + const float u = (atan2f(-R.x, R.z) + M_PIf) * 0.5f * M_1_PIf; + const float v = acosf(-R.y) * M_1_PIf; // Texture is origin at lower left, v == 0.0f is south pole. + + // Modulate the base emission with the emission texture. + emission *= make_float3(tex2D(light.textureEmission, u, v)); + } + + lightSample.radiance_over_pdf = emission; // pdf == 1.0f for singular light. + + // Indicate valid light sample (pdf != 0.0f). + // This value is otherwise unused in a singular light. + // The PDF is a Dirac with infinite value for this case. + lightSample.pdf = 1.0f; + } + + return lightSample; +} + + +extern "C" __device__ LightSample __direct_callable__light_spot(const LightDefinition& light, PerRayData* prd) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; + + // Get the world space position from the world to object matrix translation. + const float3 position = make_float3(light.matrix[0].w, light.matrix[1].w, light.matrix[2].w); + lightSample.direction = position - prd->pos; // Sample direction from surface point to light sample position. + + const float distanceSquared = dot(lightSample.direction, lightSample.direction); + + if (DENOMINATOR_EPSILON < distanceSquared) + { + lightSample.distance = sqrtf(distanceSquared); + lightSample.direction *= 1.0f / lightSample.distance; // Normalized direction to light. + + //const float3 normal = normalize(transformNormal(light.matrixInv, make_float3(0.0f, 0.0f, 1.0f))); + const float3 normal = normalize(make_float3(light.matrixInv[2])); + + // Spot light is aligned to the local z-axis (the normal). + const float cosTheta = -dot(lightSample.direction, normal); // Negative because lightSample.direction is from surface to light. + const float cosSpread = cosf(light.spotAngleHalf); // Note that the spot light only supports hemispherical distributions. + + if (cosSpread <= cosTheta) // Is the lightSample.direction inside the spot light cone? + { + // Normalize the hemispherical distribution (half-angle M_PI_2f) to the cone angle (scale by factor: angleHalf / light.spotAngleHalf, range [0.0f, 1.0f]). + const float cosHemi = cosf(M_PI_2f * acosf(cosTheta) / light.spotAngleHalf); + + // Hardcoded singular lights are defined in visible radiance directly, don't normalize. + float3 emission = light.emission * (powf(cosHemi, light.spotExponent) / distanceSquared); // Quadratic attenuation. + + // The emission texture is used as projection scaled to the spherical cap inside the spot light cone. + // By design all lights in this renderer shine down the light's local positive z-Axis, which is the "normal" direction for rect and mesh lights. + if (light.textureEmission) + { + // Transform the direction from light to surface from world space into light object space. + const float3 R = transformVector(light.oriInv, -lightSample.direction); + + const float u = (acosf(R.x) - M_PI_2f) / light.spotAngleHalf * 0.5f + 0.5f; + const float v = 0.5f - ((acosf(R.y) - M_PI_2f) / light.spotAngleHalf * 0.5f); + + // Modulate the base emission with the emission texture. + emission *= make_float3(tex2D(light.textureEmission, u, v)); + } + + lightSample.radiance_over_pdf = emission; // pdf == 1.0f for singular light. + + // Indicate valid light sample (pdf != 0.0f). + // This value is otherwise unused in a singular light. + // The PDF is a Dirac with infinite value for this case. + lightSample.pdf = 1.0f; + } + } + + return lightSample; +} + + +extern "C" __device__ LightSample __direct_callable__light_ies(const LightDefinition& light, PerRayData* prd) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; // Default return, invalid light sample (backface, edge on, or too near to the surface) + + // Get the worls space position from the world to object matrix translation. + const float3 position = make_float3(light.matrix[0].w, light.matrix[1].w, light.matrix[2].w); + lightSample.direction = position - prd->pos; // Sample direction from surface point to light sample position. + + const float distanceSquared = dot(lightSample.direction, lightSample.direction); + + if (DENOMINATOR_EPSILON < distanceSquared) + { + lightSample.distance = sqrtf(distanceSquared); + lightSample.direction *= 1.0f / lightSample.distance; // Normalized direction to light. + + // Hardcoded singular lights are defined in visible radiance directly, do not normalize. + // This just returns the candela values (luminous power per solid angle). + float3 emission = light.emission * (1.0f / distanceSquared); + + // The emission texture is used as spherical projection around the point light similar to spherical environment lights. + // By design all lights in this renderer shine down the light's local positive z-Axis, which is the "normal" direction for rect and mesh lights. + + // Transform the direction from light to surface from world into light object space. + const float3 R = transformVector(light.oriInv, -lightSample.direction); + + // All lights shine down the positive z-axis in this renderer. + // Means the spherical texture coordinate seam u == 0.0 == 1.0 is on the negative z-axis direction now. + const float u = (atan2f(-R.x, R.z) + M_PIf) * 0.5f * M_1_PIf; + const float v = acosf(-R.y) * M_1_PIf; // Texture is origin at lower left, v == 0.0f is south pole. + + if (light.textureProfile) + { + // Modulate the base emission with the profile texture (single component float texture, candela) + emission *= tex2D(light.textureProfile, u, v); + } + + if (light.textureEmission) // IES light profile can be modulated by emission color texture. + { + // Modulate the base emission with the emission texture. + emission *= make_float3(tex2D(light.textureEmission, u, v)); + } + + lightSample.radiance_over_pdf = emission; // pdf == 1.0f for singular light. + + // Indicate valid light sample (pdf != 0.0f). + // This value is otherwise unused in a singular light. + // The PDF is a Dirac with infinite value for this case. + lightSample.pdf = 1.0f; + } + + return lightSample; +} diff --git a/apps/MDL_sdf/shaders/material_definition_mdl.h b/apps/MDL_sdf/shaders/material_definition_mdl.h new file mode 100644 index 00000000..bf37b28d --- /dev/null +++ b/apps/MDL_sdf/shaders/material_definition_mdl.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MATERIAL_DEFINITION_MDL_H +#define MATERIAL_DEFINITION_MDL_H + +#include "function_indices.h" + +#include "texture_handler.h" + +struct MaterialDefinitionMDL +{ + // 8 byte alignment. + CUdeviceptr arg_block; // The MDL argument block pointer for this material reference. + Texture_handler* texture_handler; // The pointer to the texture handler data. + int indexShader; // Index into the MDL shader configuration vector. + // Pad to 8 byte alignment. + int pad0; +}; + +#endif // MATERIAL_DEFINITION_MDL_H diff --git a/apps/MDL_sdf/shaders/miss.cu b/apps/MDL_sdf/shaders/miss.cu new file mode 100644 index 00000000..d2faa340 --- /dev/null +++ b/apps/MDL_sdf/shaders/miss.cu @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "light_definition.h" +#include "shader_common.h" +#include "system_data.h" +#include "transform.h" + +extern "C" __constant__ SystemData sysData; + + +// Take the step along the volume scattering random walk. +__forceinline__ __device__ void stepVolume(PerRayData* thePrd) +{ + // Calculate the new position at the end of the random walk ray segment. + thePrd->pos += thePrd->wi * thePrd->distance; + + // Change the throughput along the random walk according to the current extinction and the sampled density. + const float3 transmittance = expf(thePrd->sigma_t * -thePrd->distance); + const float pdf = dot(thePrd->pdfVolume, thePrd->sigma_t * transmittance); + + thePrd->throughput *= thePrd->stack[thePrd->idxStack].sigma_s * transmittance / pdf; + + // Indicate that the random walk missed. + thePrd->flags |= FLAG_VOLUME_SCATTERING_MISS; + + // Increment the number of steps done for the random walk + ++thePrd->walk; +} + + +// Not actually a light. Never appears inside the sysLightDefinitions. +extern "C" __global__ void __miss__env_null() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + if (thePrd->flags & FLAG_VOLUME_SCATTERING) + { + stepVolume(thePrd); + + return; // Continue the random walk. + } + + // The null environment adds nothing to the radiance. + thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; +} + + +extern "C" __global__ void __miss__env_constant() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + if (thePrd->flags & FLAG_VOLUME_SCATTERING) + { + stepVolume(thePrd); + + return; // Continue the random walk. + } + + // The environment light is always in the first element. + float3 emission = sysData.lightDefinitions[0].emission; // Constant emission. + + if (sysData.directLighting) + { + // If the last surface intersection was diffuse or glossy which was directly lit with multiple importance sampling, + // then calculate implicit light emission with multiple importance sampling as well. + const float weightMIS = (thePrd->eventType & (mi::neuraylib::BSDF_EVENT_DIFFUSE | mi::neuraylib::BSDF_EVENT_GLOSSY)) + ? balanceHeuristic(thePrd->pdf, 0.25f * M_1_PIf) + : 1.0f; + + emission *= weightMIS; + } + + thePrd->radiance += thePrd->throughput * emission; + thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; +} + + +extern "C" __global__ void __miss__env_sphere() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + if (thePrd->flags & FLAG_VOLUME_SCATTERING) + { + stepVolume(thePrd); + + return; // Continue the random walk. + } + + // The environment light is always in the first element. + const LightDefinition& light = sysData.lightDefinitions[0]; + + // Transform the ray.direction from world space to light object space. + const float3 R = transformVector(light.oriInv, thePrd->wi); + + // All lights shine down the positive z-axis in this renderer. + const float u = (atan2f(-R.x, R.z) + M_PIf) * 0.5f * M_1_PIf; + // Texture is with origin at lower left, v == 0.0f is south pole. + const float v = acosf(-R.y) * M_1_PIf; + + float3 emission = make_float3(tex2D(light.textureEmission, u, v)); + + if (sysData.directLighting) + { + // If the last surface intersection was a diffuse event which was directly lit with multiple importance sampling, + // then calculate implicit light emission with multiple importance sampling as well. + if (thePrd->eventType & (mi::neuraylib::BSDF_EVENT_DIFFUSE | mi::neuraylib::BSDF_EVENT_GLOSSY)) + { + // For simplicity we pretend that we perfectly importance-sampled the actual texture-filtered environment map + // and not the Gaussian smoothed one used to actually generate the CDFs. + const float pdfLight = intensity(emission) * light.invIntegral; + + emission *= balanceHeuristic(thePrd->pdf, pdfLight); + } + } + + thePrd->radiance += thePrd->throughput * emission * light.emission; + thePrd->eventType = mi::neuraylib::BSDF_EVENT_ABSORB; +} diff --git a/apps/MDL_sdf/shaders/per_ray_data.h b/apps/MDL_sdf/shaders/per_ray_data.h new file mode 100644 index 00000000..444d46df --- /dev/null +++ b/apps/MDL_sdf/shaders/per_ray_data.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef PER_RAY_DATA_H +#define PER_RAY_DATA_H + +#include "config.h" + +#include + +// For the Bsdf_event_type. +#include + +// Set when reaching a closesthit program. +#define FLAG_HIT 0x00000001 +// Set when reaching the __anyhit__shadow program. Indicates visibility test failed. +#define FLAG_SHADOW 0x00000002 +// Set if the material stack is not empty. +// This is implicit with the idxStack value. +//#define FLAG_VOLUME 0x00000010 +#define FLAG_VOLUME_SCATTERING 0x00000020 +#define FLAG_VOLUME_SCATTERING_MISS 0x00000040 + +// Small 4 entries deep material stack. Entry 0 is vacuum. +#define MATERIAL_STACK_LAST 3 +#define MATERIAL_STACK_SIZE 4 + +// This is the minimal size of the struct. float4 for vectorized access was slower due to more registers used. +struct MaterialStack +{ + float3 ior; // index of refraction + float3 sigma_a; // absorption coefficient + float3 sigma_s; // scattering coefficient + float bias; // directional bias +}; + + +// Note that the fields are ordered by CUDA alignment restrictions. +struct PerRayData +{ + // 16-byte alignment + + // 8-byte alignment + + // 4-byte alignment + float3 pos; // Current surface hit point or volume sample point, in world space + float distance; // Distance from the ray origin to the current position, in world space. Needed for absorption of nested materials. + + float3 wo; // Outgoing direction, to observer, in world space. + float3 wi; // Incoming direction, to light, in world space. + + float3 radiance; // Radiance along the current path segment. + float pdf; // The last BSDF sample's pdf, tracked for multiple importance sampling. + + float3 throughput; // The current path troughput. Starts white and gets modulated with bsdf_over_pdf with each sample. + + unsigned int flags; // Bitfield with flags. See FLAG_* defines above for its contents. + + float3 sigma_t; // Extinction coefficient in a homogeneous medium. + int walk; // Number of random walk steps done through scattering volume. + float3 pdfVolume; // Volume extinction sample pdf. Used to adjust the throughput along the random walk. + + mi::neuraylib::Bsdf_event_type eventType; // The type of events created by BSDF importance sampling. + + unsigned int seed; // Random number generator input. + + // Small material stack tracking IOR, absorption ansd scattering coefficients of the entered materials. Entry 0 is vacuum. + int idxStack; + MaterialStack stack[MATERIAL_STACK_SIZE]; +}; + + +// Alias the PerRayData pointer and an uint2 for the payload split and merge operations. This generates only move instructions. +typedef union +{ + PerRayData* ptr; + uint2 dat; +} Payload; + +__forceinline__ __device__ uint2 splitPointer(PerRayData* ptr) +{ + Payload payload; + + payload.ptr = ptr; + + return payload.dat; +} + +__forceinline__ __device__ PerRayData* mergePointer(unsigned int p0, unsigned int p1) +{ + Payload payload; + + payload.dat.x = p0; + payload.dat.y = p1; + + return payload.ptr; +} + +#endif // PER_RAY_DATA_H diff --git a/apps/MDL_sdf/shaders/random_number_generators.h b/apps/MDL_sdf/shaders/random_number_generators.h new file mode 100644 index 00000000..9d26d72a --- /dev/null +++ b/apps/MDL_sdf/shaders/random_number_generators.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RANDOM_NUMBER_GENERATORS_H +#define RANDOM_NUMBER_GENERATORS_H + +#include "config.h" + + +// Tiny Encryption Algorithm (TEA) to calculate a the seed per launch index and iteration. +// This results in a ton of integer instructions! Use the smallest N necessary. +template +__forceinline__ __device__ unsigned int tea(const unsigned int val0, const unsigned int val1) +{ + unsigned int v0 = val0; + unsigned int v1 = val1; + unsigned int s0 = 0; + + for (unsigned int n = 0; n < N; ++n) + { + s0 += 0x9e3779b9; + v0 += ((v1 << 4) + 0xA341316C) ^ (v1 + s0) ^ ((v1 >> 5) + 0xC8013EA4); + v1 += ((v0 << 4) + 0xAD90777D) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7E95761E); + } + return v0; +} + +// Just do one LCG step. The new random unsigned int number is in the referenced argument. +__forceinline__ __device__ void lcg(unsigned int& previous) +{ + previous = previous * 1664525u + 1013904223u; +} + + +// Return a random sample in the range [0, 1) with a simple Linear Congruential Generator. +__forceinline__ __device__ float rng(unsigned int& previous) +{ + previous = previous * 1664525u + 1013904223u; + + //return float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + return float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits +} + +// Convenience function +__forceinline__ __device__ float2 rng2(unsigned int& previous) +{ + float2 s; + + previous = previous * 1664525u + 1013904223u; + //s.x = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.x = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + //s.y = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.y = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + return s; +} + +// Convenience function +__forceinline__ __device__ float3 rng3(unsigned int& previous) +{ + float3 s; + + previous = previous * 1664525u + 1013904223u; + //s.x = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.x = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + //s.y = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.y = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + //s.z = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.z = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + return s; +} + + +// Convenience function +__forceinline__ __device__ float4 rng4(unsigned int& previous) +{ + float4 s; + + previous = previous * 1664525u + 1013904223u; + //s.x = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.x = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + //s.y = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.y = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + //s.z = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.z = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + //s.w = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + s.w = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + return s; +} + + + +#endif // RANDOM_NUMBER_GENERATORS_H diff --git a/apps/MDL_sdf/shaders/raygeneration.cu b/apps/MDL_sdf/shaders/raygeneration.cu new file mode 100644 index 00000000..eeb89ebd --- /dev/null +++ b/apps/MDL_sdf/shaders/raygeneration.cu @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +#include "shader_common.h" +#include "half_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +__forceinline__ __device__ float3 safe_div(const float3& a, const float3& b) +{ + const float x = (b.x != 0.0f) ? a.x / b.x : 0.0f; + const float y = (b.y != 0.0f) ? a.y / b.y : 0.0f; + const float z = (b.z != 0.0f) ? a.z / b.z : 0.0f; + + return make_float3(x, y, z); +} + +__forceinline__ __device__ float sampleDensity(const float3& albedo, + const float3& throughput, + const float3& sigma_t, + const float u, + float3& pdf) +{ + const float3 weights = throughput * albedo; + + const float sum = weights.x + weights.y + weights.z; + + pdf = (0.0f < sum) ? weights / sum : make_float3(1.0f / 3.0f); + + if (u < pdf.x) + { + return sigma_t.x; + } + if (u < pdf.x + pdf.y) + { + return sigma_t.y; + } + return sigma_t.z; +} + +// Determine Henyey-Greenstein phase function cos(theta) of scattering direction +__forceinline__ __device__ float sampleHenyeyGreensteinCos(const float xi, const float g) +{ + // PBRT v3: Chapter 15.2.3 + if (fabsf(g) < 1e-3f) // Isotropic. + { + return 1.0f - 2.0f * xi; + } + + const float s = (1.0f - g * g) / (1.0f - g + 2.0f * g * xi); + return (1.0f + g * g - s * s) / (2.0f * g); +} + +// Determine scatter reflection direction with Henyey-Greenstein phase function. +__forceinline__ __device__ void sampleVolumeScattering(const float2 xi, const float g, float3& dir) +{ + const float cost = sampleHenyeyGreensteinCos(xi.x, g); + + float sint = 1.0f - cost * cost; + sint = (0.0f < sint) ? sqrtf(sint) : 0.0f; + + const float phi = 2.0f * M_PIf * xi.y; + + // This vector is oriented in its own local coordinate system: + const float3 d = make_float3(cosf(phi) * sint, sinf(phi) * sint, cost); + + // Align the vector with the incoming direction. + const TBN tbn(dir); // Just some ortho-normal basis along dir as z-axis. + + dir = tbn.transformToWorld(d); +} + + +__forceinline__ __device__ float3 integrator(PerRayData& prd) +{ + // The integrator starts with black radiance and full path throughput. + prd.radiance = make_float3(0.0f); + prd.pdf = 0.0f; + prd.throughput = make_float3(1.0f); + prd.flags = 0; + prd.sigma_t = make_float3(0.0f); // Extinction coefficient: sigma_a + sigma_s. + prd.walk = 0; // Number of random walk steps taken through volume scattering. + prd.eventType = mi::neuraylib::BSDF_EVENT_ABSORB; // Initialize for exit. (Otherwise miss programs do not work.) + // Nested material handling. + prd.idxStack = 0; + // Small stack of four entries of which the first is vacuum. + prd.stack[0].ior = make_float3(1.0f); // No effective IOR. + prd.stack[0].sigma_a = make_float3(0.0f); // No volume absorption. + prd.stack[0].sigma_s = make_float3(0.0f); // No volume scattering. + prd.stack[0].bias = 0.0f; // Isotropic volume scattering. + + // Put payload pointer into two unsigned integers. Actually const, but that's not what optixTrace() expects. + uint2 payload = splitPointer(&prd); + + // Russian Roulette path termination after a specified number of bounces needs the current depth. + int depth = 0; // Path segment index. Primary ray is depth == 0. + + while (depth < sysData.pathLengths.y) + { + // Self-intersection avoidance: + // Offset the ray t_min value by sysData.sceneEpsilon when a geometric primitive was hit by the previous ray. + // Primary rays and volume scattering miss events will not offset the ray t_min. + const float epsilon = (prd.flags & FLAG_HIT) ? sysData.sceneEpsilon : 0.0f; + + prd.wo = -prd.wi; // Direction to observer. + prd.distance = RT_DEFAULT_MAX; // Shoot the next ray with maximum length. + prd.flags = 0; + + // Special cases for volume scattering! + if (0 < prd.idxStack) // Inside a volume? + { + // Note that this only supports homogeneous volumes so far! + // No change in sigma_s along the random walk here. + const float3 sigma_s = prd.stack[prd.idxStack].sigma_s; + + if (isNotNull(sigma_s)) // We're inside a volume and it has volume scattering? + { + // Indicate that we're inside a random walk. This changes the behavior of the miss programs. + prd.flags |= FLAG_VOLUME_SCATTERING; + + // Random walk through scattering volume, sampling the distance. + // Note that the entry and exit of the volume is done according to the BSDF sampling. + // Means glass with volume scattering will still do the proper refractions. + // When the number of random walk steps has been exceeded, the next ray is shot with distance RT_DEFAULT_MAX + // to hit something. If that results in a transmission the scattering volume is left. + // If not, this continues until the maximum path length has been exceeded. + if (prd.walk < sysData.walkLength) + { + const float3 albedo = safe_div(sigma_s, prd.sigma_t); + const float2 xi = rng2(prd.seed); + + const float s = sampleDensity(albedo, prd.throughput, prd.sigma_t, xi.x, prd.pdfVolume); + + // Prevent logf(0.0f) by sampling the inverse range (0.0f, 1.0f]. + prd.distance = -logf(1.0f - xi.y) / s; + } + } + } + +#if (USE_SHADER_EXECUTION_REORDERING == 0 || OPTIX_VERSION < 80000) + optixTrace(sysData.topObject, + prd.pos, prd.wi, // origin, direction + epsilon, prd.distance, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_NONE, + TYPE_RAY_RADIANCE, NUM_RAY_TYPES, TYPE_RAY_RADIANCE, + payload.x, payload.y); + +#else + // OptiX Shader Execution Reordering (SER) implementation. + // Use this for additional performance on Ada generation GPUs when the scene is using multiple different material shader configurations. + optixTraverse(sysData.topObject, + prd.pos, prd.wi, // origin, direction + epsilon, prd.distance, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_NONE, + TYPE_RAY_RADIANCE, NUM_RAY_TYPES, TYPE_RAY_RADIANCE, + payload.x, payload.y); + + unsigned int hint = 0; // miss uses some default value. The record type itself will distinguish this case. + if (optixHitObjectIsHit()) + { + const int idMaterial = sysData.geometryInstanceData[optixHitObjectGetInstanceId()].ids.x; + hint = sysData.materialDefinitionsMDL[idMaterial].indexShader; // Shader configuration only. + } + optixReorder(hint, sysData.numBitsShaders); + + optixInvoke(payload.x, payload.y); +#endif + + // Path termination by miss shader or sample() routines. + if ((prd.eventType == mi::neuraylib::BSDF_EVENT_ABSORB) || isNull(prd.throughput)) + { + break; + } + + // Unbiased Russian Roulette path termination. + if (sysData.pathLengths.x <= depth) // Start termination after a minimum number of bounces. + { + const float probability = fmaxf(prd.throughput); + + if (probability < rng(prd.seed)) // Paths with lower probability to continue are terminated earlier. + { + break; + } + + prd.throughput /= probability; // Path isn't terminated. Adjust the throughput so that the average is right again. + } + + // We're inside a volume and the scatter ray missed. + if (prd.flags & FLAG_VOLUME_SCATTERING_MISS) // This implies FLAG_VOLUME_SCATTERING. + { + // Random walk through scattering volume, sampling the direction according to the phase function. + sampleVolumeScattering(rng2(prd.seed), prd.stack[prd.idxStack].bias, prd.wi); + } + + ++depth; // Next path segment. + } + + return prd.radiance; +} + + +__forceinline__ __device__ unsigned int distribute(const uint2 launchIndex) +{ + // First calculate block coordinates of this launch index. + // That is the launch index divided by the tile dimensions. (No operator>>() on vectors?) + const unsigned int xBlock = launchIndex.x >> sysData.tileShift.x; + const unsigned int yBlock = launchIndex.y >> sysData.tileShift.y; + + // Each device needs to start at a different column and each row should start with a different device. + const unsigned int xTile = xBlock * sysData.deviceCount + ((sysData.deviceIndex + yBlock) % sysData.deviceCount); + + // The horizontal pixel coordinate is: tile coordinate * tile width + launch index % tile width. + return xTile * sysData.tileSize.x + (launchIndex.x & (sysData.tileSize.x - 1)); // tileSize needs to be power-of-two for this modulo operation. +} + + +extern "C" __global__ void __raygen__path_tracer_local_copy() +{ +#if USE_TIME_VIEW + clock_t clockBegin = clock(); +#endif + + const uint2 theLaunchIndex = make_uint2(optixGetLaunchIndex()); + + unsigned int launchColumn = theLaunchIndex.x; + + if (1 < sysData.deviceCount) // Multi-GPU distribution required? + { + launchColumn = distribute(theLaunchIndex); // Calculate mapping from launch index to pixel index. + if (sysData.resolution.x <= launchColumn) // Check if the launchColumn is outside the resolution. + { + return; + } + } + + PerRayData prd; + + const uint2 theLaunchDim = make_uint2(optixGetLaunchDimensions()); // For multi-GPU tiling this is (resolution + deviceCount - 1) / deviceCount. + + // Initialize the random number generator seed from the linear pixel index and the iteration index. + prd.seed = tea<4>(theLaunchIndex.y * theLaunchDim.x + launchColumn, sysData.iterationIndex); // PERF This template really generates a lot of instructions. + + // Decoupling the pixel coordinates from the screen size will allow for partial rendering algorithms. + // Resolution is the actual full rendering resolution and for the single GPU strategy, theLaunchDim == resolution. + const float2 screen = make_float2(sysData.resolution); // == theLaunchDim for rendering strategy RS_SINGLE_GPU. + const float2 pixel = make_float2(launchColumn, theLaunchIndex.y); + const float2 sample = rng2(prd.seed); + + // Lens shaders + const LensRay ray = optixDirectCall(sysData.typeLens, screen, pixel, sample); + + prd.pos = ray.org; + prd.wi = ray.dir; + + float3 radiance = integrator(prd); + +#if USE_DEBUG_EXCEPTIONS + // DEBUG Highlight numerical errors. + if (isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z)) + { + radiance = make_float3(1000000.0f, 0.0f, 0.0f); // super red + } + else if (isinf(radiance.x) || isinf(radiance.y) || isinf(radiance.z)) + { + radiance = make_float3(0.0f, 1000000.0f, 0.0f); // super green + } + else if (radiance.x < 0.0f || radiance.y < 0.0f || radiance.z < 0.0f) + { + radiance = make_float3(0.0f, 0.0f, 1000000.0f); // super blue + } +#else + // NaN values will never go away. Filter them out before they can arrive in the output buffer. + // This only has an effect if the debug coloring above is off! + if (!(isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z))) +#endif + { + // This renderer write the results into individual launch sized local buffers and composites them in a separate native CUDA kernel. + const unsigned int index = theLaunchIndex.y * theLaunchDim.x + theLaunchIndex.x; + +#if USE_FP32_OUTPUT + // The texelBuffer is a CUdeviceptr to allow different formats. + // This is a per device launch sized buffer in this renderer strategy. + float4* buffer = reinterpret_cast(sysData.texelBuffer); + +#if USE_TIME_VIEW + clock_t clockEnd = clock(); + const float alpha = (clockEnd - clockBegin) * sysData.clockScale; + + float4 result = make_float4(radiance, alpha); + + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + result = lerp(dst, result, 1.0f / float(sysData.iterationIndex + 1)); // Accumulate the alpha as well. + } + buffer[index] = result; +#else + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + radiance = lerp(make_float3(dst), radiance, 1.0f / float(sysData.iterationIndex + 1)); // Only accumulate the radiance, alpha stays 1.0f. + } + buffer[index] = make_float4(radiance, 1.0f); +#endif + +#else // if !USE_FP32_OUTPUT + + Half4* buffer = reinterpret_cast(sysData.texelBuffer); + +#if USE_TIME_VIEW + clock_t clockEnd = clock(); + const float alpha = (clockEnd - clockBegin) * sysData.clockScale; + + if (0 < sysData.iterationIndex) + { + const float t = 1.0f / float(sysData.iterationIndex + 1); + + const Half4 dst = buffer[index]; // RGBA16F + + radiance.x = lerp(__half2float(dst.x), radiance.x, t); + radiance.y = lerp(__half2float(dst.y), radiance.y, t); + radiance.z = lerp(__half2float(dst.z), radiance.z, t); + alpha = lerp(__half2float(dst.z), alpha, t); + } + buffer[index] = make_Half4(radiance, alpha); +#else + if (0 < sysData.iterationIndex) + { + const float t = 1.0f / float(sysData.iterationIndex + 1); + + const Half4 dst = buffer[index]; // RGBA16F + + radiance.x = lerp(__half2float(dst.x), radiance.x, t); + radiance.y = lerp(__half2float(dst.y), radiance.y, t); + radiance.z = lerp(__half2float(dst.z), radiance.z, t); + } + buffer[index] = make_Half4(radiance, 1.0f); +#endif + +#endif // USE_FP32_OUTPUT + } +} + +extern "C" __global__ void __raygen__path_tracer() +{ +#if USE_TIME_VIEW + clock_t clockBegin = clock(); +#endif + + const uint2 theLaunchDim = make_uint2(optixGetLaunchDimensions()); // For multi-GPU tiling this is (resolution + deviceCount - 1) / deviceCount. + const uint2 theLaunchIndex = make_uint2(optixGetLaunchIndex()); + + PerRayData prd; + + // Initialize the random number generator seed from the linear pixel index and the iteration index. + prd.seed = tea<4>( theLaunchDim.x * theLaunchIndex.y + theLaunchIndex.x, sysData.iterationIndex); // PERF This template really generates a lot of instructions. + + // Decoupling the pixel coordinates from the screen size will allow for partial rendering algorithms. + // Resolution is the actual full rendering resolution and for the single GPU strategy, theLaunchDim == resolution. + const float2 screen = make_float2(sysData.resolution); // == theLaunchDim for rendering strategy RS_SINGLE_GPU. + const float2 pixel = make_float2(theLaunchIndex); + const float2 sample = rng2(prd.seed); + + // Lens shaders + const LensRay ray = optixDirectCall(sysData.typeLens, screen, pixel, sample); + + prd.pos = ray.org; + prd.wi = ray.dir; + + float3 radiance = integrator(prd); + +#if USE_DEBUG_EXCEPTIONS + // DEBUG Highlight numerical errors. + if (isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z)) + { + radiance = make_float3(1000000.0f, 0.0f, 0.0f); // super red + } + else if (isinf(radiance.x) || isinf(radiance.y) || isinf(radiance.z)) + { + radiance = make_float3(0.0f, 1000000.0f, 0.0f); // super green + } + else if (radiance.x < 0.0f || radiance.y < 0.0f || radiance.z < 0.0f) + { + radiance = make_float3(0.0f, 0.0f, 1000000.0f); // super blue + } +#else + // NaN values will never go away. Filter them out before they can arrive in the output buffer. + // This only has an effect if the debug coloring above is off! + if (!(isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z))) +#endif + { + const unsigned int index = theLaunchDim.x * theLaunchIndex.y + theLaunchIndex.x; + +#if USE_FP32_OUTPUT + + float4* buffer = reinterpret_cast(sysData.outputBuffer); + +#if USE_TIME_VIEW + clock_t clockEnd = clock(); + const float alpha = (clockEnd - clockBegin) * sysData.clockScale; + + float4 result = make_float4(radiance, alpha); + + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + + result = lerp(dst, result, 1.0f / float(sysData.iterationIndex + 1)); // Accumulate the alpha as well. + } + buffer[index] = result; +#else // if !USE_TIME_VIEW + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + + radiance = lerp(make_float3(dst), radiance, 1.0f / float(sysData.iterationIndex + 1)); // Only accumulate the radiance, alpha stays 1.0f. + } + buffer[index] = make_float4(radiance, 1.0f); +#endif // USE_TIME_VIEW + +#else // if !USE_FP32_OUPUT + + Half4* buffer = reinterpret_cast(sysData.outputBuffer); + +#if USE_TIME_VIEW + clock_t clockEnd = clock(); + float alpha = (clockEnd - clockBegin) * sysData.clockScale; + + if (0 < sysData.iterationIndex) + { + const float t = 1.0f / float(sysData.iterationIndex + 1); + + const Half4 dst = buffer[index]; // RGBA16F + + radiance.x = lerp(__half2float(dst.x), radiance.x, t); + radiance.y = lerp(__half2float(dst.y), radiance.y, t); + radiance.z = lerp(__half2float(dst.z), radiance.z, t); + alpha = lerp(__half2float(dst.z), alpha, t); + } + buffer[index] = make_Half4(radiance, alpha); +#else // if !USE_TIME_VIEW + if (0 < sysData.iterationIndex) + { + const float t = 1.0f / float(sysData.iterationIndex + 1); + + const Half4 dst = buffer[index]; // RGBA16F + + radiance.x = lerp(__half2float(dst.x), radiance.x, t); + radiance.y = lerp(__half2float(dst.y), radiance.y, t); + radiance.z = lerp(__half2float(dst.z), radiance.z, t); + } + buffer[index] = make_Half4(radiance, 1.0f); +#endif // USE_TIME_VIEW + +#endif // USE_FP32_OUTPUT + } +} + diff --git a/apps/MDL_sdf/shaders/sdf_attributes.h b/apps/MDL_sdf/shaders/sdf_attributes.h new file mode 100644 index 00000000..e6009ab7 --- /dev/null +++ b/apps/MDL_sdf/shaders/sdf_attributes.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SDF_ATTRIBUTES_H +#define SDF_ATTRIBUTES_H + +#include + +struct SignedDistanceFieldAttributes +{ + // This must match OptixAabb and placed first! + float3 minimum; + float3 maximum; + // 8 byte aligned. + cudaTextureObject_t sdfTexture; // The 3D texture object. + // 4 byte aligned. + float sdfLipschitz; // Inverse factor on the SDF distances inside the texture. + uint3 sdfTextureSize; // The 3D texture size. +}; + +#endif // SDF_ATTRIBUTES_H diff --git a/apps/MDL_sdf/shaders/shader_common.h b/apps/MDL_sdf/shaders/shader_common.h new file mode 100644 index 00000000..66c047b9 --- /dev/null +++ b/apps/MDL_sdf/shaders/shader_common.h @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SHADER_COMMON_H +#define SHADER_COMMON_H + +#include "config.h" + +#include "vector_math.h" + + +/** +* Calculates refraction direction +* r : refraction vector +* i : incident vector +* n : surface normal +* ior : index of refraction ( n2 / n1 ) +* returns false in case of total internal reflection, in that case r is initialized to (0,0,0). +*/ +__forceinline__ __host__ __device__ bool refract(float3& r, const float3& i, const float3& n, const float ior) +{ + float3 nn = n; + float negNdotV = dot(i, nn); + float eta; + + if (0.0f < negNdotV) + { + eta = ior; + nn = -n; + negNdotV = -negNdotV; + } + else + { + eta = 1.0f / ior; + } + + const float k = 1.0f - eta * eta * (1.0f - negNdotV * negNdotV); + + if (k < 0.0f) + { + // Initialize this value, so that r always leaves this function initialized. + r = make_float3(0.0f); + return false; + } + else + { + r = normalize(eta * i - (eta * negNdotV + sqrtf(k)) * nn); + return true; + } +} + + + +// Tangent-Bitangent-Normal orthonormal space. +struct TBN +{ + // Default constructor to be able to include it into other structures when needed. + __forceinline__ __host__ __device__ TBN() + { + } + + __forceinline__ __host__ __device__ TBN(const float3& n) + : normal(n) + { + if (fabsf(normal.z) < fabsf(normal.x)) + { + tangent.x = normal.z; + tangent.y = 0.0f; + tangent.z = -normal.x; + } + else + { + tangent.x = 0.0f; + tangent.y = normal.z; + tangent.z = -normal.y; + } + tangent = normalize(tangent); + bitangent = cross(normal, tangent); + } + + // Constructor for cases where tangent, bitangent, and normal are given as ortho-normal basis. + __forceinline__ __host__ __device__ TBN(const float3& t, const float3& b, const float3& n) + : tangent(t) + , bitangent(b) + , normal(n) + { + } + + // Normal is kept, tangent and bitangent are calculated. + // Normal must be normalized. + // Must not be used with degenerated vectors! + __forceinline__ __host__ __device__ TBN(const float3& tangent_reference, const float3& n) + : normal(n) + { + bitangent = normalize(cross(normal, tangent_reference)); + tangent = cross(bitangent, normal); + } + + __forceinline__ __host__ __device__ void negate() + { + tangent = -tangent; + bitangent = -bitangent; + normal = -normal; + } + + __forceinline__ __host__ __device__ float3 transformToLocal(const float3& p) const + { + return make_float3(dot(p, tangent), + dot(p, bitangent), + dot(p, normal)); + } + + __forceinline__ __host__ __device__ float3 transformToWorld(const float3& p) const + { + return p.x * tangent + p.y * bitangent + p.z * normal; + } + + float3 tangent; + float3 bitangent; + float3 normal; +}; + +// FBN (Fiber, Bitangent, Normal) +// Special version of TBN (Tangent, Bitangent, Normal) ortho-normal basis generation for fiber geometry. +// The difference is that the TBN keeps the normal intact where the FBN keeps the tangent intact, which is the fiber orientation! +struct FBN +{ + // Default constructor to be able to include it into State. + __forceinline__ __host__ __device__ FBN() + { + } + + // Do not use these single argument constructors for anisotropic materials! + // There will be discontinuities on round objects when the FBN orientation flips. + // Tangent is kept, bitangent and normal are calculated. + // Tangent t must be normalized. + __forceinline__ __host__ __device__ FBN(const float3& t) // t == fiber orientation + : tangent(t) + { + if (fabsf(tangent.z) < fabsf(tangent.x)) + { + bitangent.x = -tangent.y; + bitangent.y = tangent.x; + bitangent.z = 0.0f; + } + else + { + bitangent.x = 0.0f; + bitangent.y = -tangent.z; + bitangent.z = tangent.y; + } + + bitangent = normalize(bitangent); + normal = cross(tangent, bitangent); + } + + // Constructor for cases where tangent, bitangent, and normal are given as ortho-normal basis. + __forceinline__ __host__ __device__ FBN(const float3& t, const float3& b, const float3& n) // t == fiber orientation + : tangent(t) + , bitangent(b) + , normal(n) + { + } + + // Tangent is kept, bitangent and normal are calculated. + // Tangent t must be normalized. + // Must not be used with degenerated vectors! + __forceinline__ __host__ __device__ FBN(const float3& t, const float3& n) + : tangent(t) + { + bitangent = normalize(cross(n, tangent)); + normal = cross(tangent, bitangent); + } + + __forceinline__ __host__ __device__ void negate() + { + tangent = -tangent; + bitangent = -bitangent; + normal = -normal; + } + + __forceinline__ __host__ __device__ float3 transformToLocal(const float3& p) const + { + return make_float3(dot(p, tangent), + dot(p, bitangent), + dot(p, normal)); + } + + __forceinline__ __host__ __device__ float3 transformToWorld(const float3& p) const + { + return p.x * tangent + p.y * bitangent + p.z * normal; + } + + float3 tangent; + float3 bitangent; + float3 normal; +}; + + + +__forceinline__ __host__ __device__ float luminance(const float3& rgb) +{ + const float3 ntsc_luminance = { 0.30f, 0.59f, 0.11f }; + return dot(rgb, ntsc_luminance); +} + +__forceinline__ __host__ __device__ float intensity(const float3& rgb) +{ + return (rgb.x + rgb.y + rgb.z) * 0.3333333333f; +} + +__forceinline__ __host__ __device__ float cube(const float x) +{ + return x * x * x; +} + +__forceinline__ __host__ __device__ bool isNull(const float3& v) +{ + return (v.x == 0.0f && v.y == 0.0f && v.z == 0.0f); +} + +__forceinline__ __host__ __device__ bool isNotNull(const float3& v) +{ + return (v.x != 0.0f || v.y != 0.0f || v.z != 0.0f); +} + +// Used for Multiple Importance Sampling. +__forceinline__ __host__ __device__ float powerHeuristic(const float a, const float b) +{ + const float t = a * a; + return t / (t + b * b); +} + +__forceinline__ __host__ __device__ float balanceHeuristic(const float a, const float b) +{ + return a / (a + b); +} + +__forceinline__ __device__ void alignVector(const float3& axis, float3& w) +{ + // Align w with axis. + const float s = copysignf(1.0f, axis.z); + w.z *= s; + const float3 h = make_float3(axis.x, axis.y, axis.z + s); + const float k = dot(w, h) / (1.0f + fabsf(axis.z)); + w = k * h - w; +} + +__forceinline__ __device__ void unitSquareToCosineHemisphere(const float2 sample, const float3& axis, float3& w, float& pdf) +{ + // Choose a point on the local hemisphere coordinates about +z. + const float theta = 2.0f * M_PIf * sample.x; + const float r = sqrtf(sample.y); + w.x = r * cosf(theta); + w.y = r * sinf(theta); + w.z = 1.0f - w.x * w.x - w.y * w.y; + w.z = (0.0f < w.z) ? sqrtf(w.z) : 0.0f; + + pdf = w.z * M_1_PIf; + + // Align with axis. + alignVector(axis, w); +} + +__forceinline__ __device__ void unitSquareToSphere(const float u, const float v, float3& p, float& pdf) +{ + p.z = 1.0f - 2.0f * u; + float r = 1.0f - p.z * p.z; + r = (0.0f < r) ? sqrtf(r) : 0.0f; + + const float phi = v * 2.0f * M_PIf; + p.x = r * cosf(phi); + p.y = r * sinf(phi); + + pdf = 0.25f * M_1_PIf; // == 1.0f / (4.0f * M_PIf) +} + +// Binary-search and return the highest cell index with CDF value <= sample. +// Arguments are the CDF values array pointer, the index of the last element and the random sample in the range [0.0f, 1.0f). +__forceinline__ __device__ unsigned int binarySearchCDF(const float* cdf, const unsigned int last, const float sample) +{ + unsigned int ilo = 0; + unsigned int ihi = last; // Index on the last entry containing 1.0f. Can never be reached with the sample in the range [0.0f, 1.0f). + + while (ilo + 1 != ihi) // When a pair of limits have been found, the lower index indicates the cell to use. + { + const unsigned int i = (ilo + ihi) >> 1; + + if (sample < cdf[i]) // If the CDF value is greater than the sample, use that as new higher limit. + { + ihi = i; + } + else // If the sample is greater than or equal to the CDF value, use that as new lower limit. + { + ilo = i; + } + } + + return ilo; +} + + + +#endif // SHADER_COMMON_H diff --git a/apps/MDL_sdf/shaders/shader_configuration.h b/apps/MDL_sdf/shaders/shader_configuration.h new file mode 100644 index 00000000..61bfb02f --- /dev/null +++ b/apps/MDL_sdf/shaders/shader_configuration.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef DEVICE_SHADER_CONFIGURATION_H +#define DEVICE_SHADER_CONFIGURATION_H + +#include "config.h" + +#include + +// Defines for the DeviceShaderConfiguration::flags +#define IS_THIN_WALLED (1u << 0) +// These flags are used to control which specific hit record is used. +#define USE_EMISSION (1u << 1) +#define USE_CUTOUT_OPACITY (1u << 2) + +struct DeviceShaderConfiguration +{ + unsigned int flags; // See defines above. + + int idxCallInit; // The material global init function. + + int idxCallThinWalled; + + int idxCallSurfaceScatteringSample; + int idxCallSurfaceScatteringEval; + + int idxCallBackfaceScatteringSample; + int idxCallBackfaceScatteringEval; + + int idxCallSurfaceEmissionEval; + int idxCallSurfaceEmissionIntensity; + int idxCallSurfaceEmissionIntensityMode; + + int idxCallBackfaceEmissionEval; + int idxCallBackfaceEmissionIntensity; + int idxCallBackfaceEmissionIntensityMode; + + int idxCallIor; + + int idxCallVolumeAbsorptionCoefficient; + int idxCallVolumeScatteringCoefficient; + int idxCallVolumeDirectionalBias; + + int idxCallGeometryCutoutOpacity; + + int idxCallHairSample; + int idxCallHairEval; + + // The constant expression values: + //bool thin_walled; // Stored inside flags. + float3 surface_intensity; + int surface_intensity_mode; + float3 backface_intensity; + int backface_intensity_mode; + float3 ior; + float3 absorption_coefficient; + float3 scattering_coefficient; + float directional_bias; + float cutout_opacity; +}; + +#endif // DEVICE_SHADER_CONFIGURATION_H diff --git a/apps/MDL_sdf/shaders/system_data.h b/apps/MDL_sdf/shaders/system_data.h new file mode 100644 index 00000000..438b0e63 --- /dev/null +++ b/apps/MDL_sdf/shaders/system_data.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SYSTEM_DATA_H +#define SYSTEM_DATA_H + +#include "config.h" + +#include "camera_definition.h" +#include "light_definition.h" +#include "material_definition_mdl.h" +#include "shader_configuration.h" +#include "vertex_attributes.h" + + +// Structure storing the per instance data for all instances inside the geometryInstanceData buffer below. Indexed via optixGetInstanceId(). +struct GeometryInstanceData +{ + // 16 byte alignment + // Pack the different IDs into a single int4 to load them vectorized. + int4 ids; // .x = idMaterial, .y = idLight, .z = idObject, .w = pad + // 8 byte alignment + // Using CUdeviceptr here to be able to handle different attribute and index formats. + CUdeviceptr attributes; + CUdeviceptr indices; +}; + +struct SystemData +{ + // 16 byte alignment + //int4 rect; // Unused, not implementing a tile renderer. + + // 8 byte alignment + OptixTraversableHandle topObject; + + // The accumulated linear color space output buffer. + // This is always sized to the resolution, not always matching the launch dimension. + // Using a CUdeviceptr here to allow for different buffer formats without too many casts. + CUdeviceptr outputBuffer; + // These buffers are used differently among the rendering strategies. + CUdeviceptr tileBuffer; + CUdeviceptr texelBuffer; + + GeometryInstanceData* geometryInstanceData; // Attributes, indices, idMaterial, idLight, idObject per instance. + + CameraDefinition* cameraDefinitions; // Currently only one camera in the array. (Allows camera motion blur in the future.) + LightDefinition* lightDefinitions; + + MaterialDefinitionMDL* materialDefinitionsMDL; // The MDL material parameter argument block, texture handler and index into the shader. + DeviceShaderConfiguration* shaderConfigurations; // Indexed by MaterialDefinitionMDL::indexShader. + + int2 resolution; // The actual rendering resolution. Independent from the launch dimensions for some rendering strategies. + int2 tileSize; // Example: make_int2(8, 4) for 8x4 tiles. Must be a power of two to make the division a right-shift. + int2 tileShift; // Example: make_int2(3, 2) for the integer division by tile size. That actually makes the tileSize redundant. + int2 pathLengths; // .x = min path length before Russian Roulette kicks in, .y = maximum path length + + // 4 byte alignment + int deviceCount; // Number of devices doing the rendering. + int deviceIndex; // Device index to be able to distinguish the individual devices in a multi-GPU environment. + int iterationIndex; + int samplesSqrt; + int walkLength; // Volume scattering random walk steps until the maximum distance is used to potentially exit the volume (could be TIR). + + float sceneEpsilon; + float clockScale; // Only used with USE_TIME_VIEW. + + int typeLens; // Camera type. + + int numCameras; // Number of elements in cameraDefinitions. + int numLights; // Number of elements in lightDefinitions. + int numMaterials; // Number of elements in materialDefinitionsMDL. (Actually not used in device code.) + int numBitsShaders; // The number of bits needed to represent the number of elements in shaderConfigurations. Used as coherence hint in SER. + + int directLighting; + + int sdfIterations; // Maximum number of iteratation for the SDF sphere tracing intersection program. + float sdfEpsilon; // SDF minimum distance epsilon value which stops the sphere tracing. + float sdfOffset; // Scale factor for self-intersection avoidance of continuation and shadow rays on SDF surfaces. +}; + + +// Helper structure to optimize the lens shader direct callable arguments. +// Return this primary ray structure instead of using references to local memory. +struct LensRay +{ + float3 org; + float3 dir; +}; +#endif // SYSTEM_DATA_H diff --git a/apps/MDL_sdf/shaders/texture_handler.h b/apps/MDL_sdf/shaders/texture_handler.h new file mode 100644 index 00000000..07dd0054 --- /dev/null +++ b/apps/MDL_sdf/shaders/texture_handler.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TEXTURE_HANDLER_H +#define TEXTURE_HANDLER_H + +#include "config.h" + +#include +#include + +#include + +typedef mi::neuraylib::Texture_handler_base Texture_handler_base; + +// Custom structure representing an MDL texture. +// Containing filtered and unfiltered CUDA texture objects and the size of the texture. +struct TextureMDL +{ + explicit TextureMDL() + : filtered_object(0) + , unfiltered_object(0) + , size(make_uint3(0, 0, 0)) + , inv_size(make_float3(0.0f, 0.0f, 0.0f)) + { + } + + explicit TextureMDL(cudaTextureObject_t filtered_object, + cudaTextureObject_t unfiltered_object, + uint3 size) + : filtered_object(filtered_object) + , unfiltered_object(unfiltered_object) + , size(size) + , inv_size(make_float3(1.0f / size.x, 1.0f / size.y, 1.0f / size.z)) + { + } + + cudaTextureObject_t filtered_object; // Uses filter mode cudaFilterModeLinear. + cudaTextureObject_t unfiltered_object; // Uses filter mode cudaFilterModePoint. + uint3 size; // Size of the texture, needed for texel access. + float3 inv_size; // The inverse values of the size of the texture. +}; + +// Structure representing an MDL bsdf measurement. +struct Mbsdf +{ + explicit Mbsdf() + { + for (int i = 0; i < 2; ++i) + { + eval_data[i] = 0; + sample_data[i] = 0; + albedo_data[i] = 0; + angular_resolution[i] = make_uint2(0u, 0u); + inv_angular_resolution[i] = make_float2(0.0f, 0.0f); + has_data[i] = 0u; + this->max_albedo[i] = 0.0f; + num_channels[i] = 0; + } + } + + void Add(mi::neuraylib::Mbsdf_part part, + const uint2& angular_resolution, + unsigned int num_channels) + { + unsigned int part_idx = static_cast(part); + + this->has_data[part_idx] = 1u; + this->angular_resolution[part_idx] = angular_resolution; + this->inv_angular_resolution[part_idx] = make_float2(1.0f / float(angular_resolution.x), + 1.0f / float(angular_resolution.y)); + this->num_channels[part_idx] = num_channels; + } + + // 8 byte aligned + cudaTextureObject_t eval_data[2]; // uses filter mode cudaFilterModeLinear + float* sample_data[2]; // CDFs for sampling a BSDF measurement + float* albedo_data[2]; // max albedo for each theta (isotropic) + uint2 angular_resolution[2]; // size of the dataset, needed for texel access + float2 inv_angular_resolution[2]; // the inverse values of the size of the dataset + // 4 byte alignment. + unsigned int has_data[2]; // true if there is a measurement for this part + float max_albedo[2]; // max albedo used to limit the multiplier + unsigned int num_channels[2]; // number of color channels (1 or 3) +}; + +// Structure representing a Light Profile +struct Lightprofile +{ + explicit Lightprofile(cudaTextureObject_t eval_data = 0, + float* cdf_data = nullptr, + uint2 angular_resolution = make_uint2(0, 0), + float2 theta_phi_start = make_float2(0.0f, 0.0f), + float2 theta_phi_delta = make_float2(0.0f, 0.0f), + float candela_multiplier = 0.0f, + float total_power = 0.0f) + : eval_data(eval_data) + , cdf_data(cdf_data) + , angular_resolution(angular_resolution) + , inv_angular_resolution(make_float2(1.0f / float(angular_resolution.x), + 1.0f / float(angular_resolution.y))) + , theta_phi_start(theta_phi_start) + , theta_phi_delta(theta_phi_delta) + , theta_phi_inv_delta(make_float2(0.0f, 0.0f)) + , candela_multiplier(candela_multiplier) + , total_power(total_power) + { + theta_phi_inv_delta.x = (theta_phi_delta.x) ? (1.0f / theta_phi_delta.x) : 0.0f; + theta_phi_inv_delta.y = (theta_phi_delta.y) ? (1.0f / theta_phi_delta.y) : 0.0f; + } + + // 8 byte aligned + cudaTextureObject_t eval_data; // normalized data sampled on grid + float* cdf_data; // CDFs for sampling a light profile + uint2 angular_resolution; // angular resolution of the grid + float2 inv_angular_resolution; // inverse angular resolution of the grid + float2 theta_phi_start; // start of the grid + float2 theta_phi_delta; // angular step size + float2 theta_phi_inv_delta; // inverse step size + // 4 byte aligned + float candela_multiplier; // factor to rescale the normalized data + float total_power; +}; + + +// The texture handler structure required by the MDL SDK with custom additional fields. +struct Texture_handler: Texture_handler_base +{ + // Additional data for the texture access functions can be provided here. + // These fields are used inside the MDL runtime functions implemented inside texture_lookup.h. + // The base class is containing a pointer to a virtual table which is not used in this renderer configuration. + + // 8 byte aligned. + const TextureMDL* textures; // The textures used by the material (without the invalid texture). + const Mbsdf* mbsdfs; // The measured BSDFs used by the material (without the invalid mbsdf). + const Lightprofile* lightprofiles; // The light_profile objects used by the material (without the invalid light profile). + + // 4 byte aligned. + unsigned int num_textures; // The number of textures used by the material (without the invalid texture). + unsigned int num_mbsdfs; // The number of mbsdfs used by the material (without the invalid mbsdf). + unsigned int num_lightprofiles; // number of elements in the lightprofiles field (without the invalid light profile). + + unsigned int pad0; // Make sure the structure is a multiple of 8 bytes in size. +}; + +#endif // TEXTURE_HANDLER_H diff --git a/apps/MDL_sdf/shaders/texture_lookup.h b/apps/MDL_sdf/shaders/texture_lookup.h new file mode 100644 index 00000000..d0ec5a8d --- /dev/null +++ b/apps/MDL_sdf/shaders/texture_lookup.h @@ -0,0 +1,1571 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TEXTURE_LOOKUP_H +#define TEXTURE_LOOKUP_H + +#include "config.h" + +#include +#include + +#include "vector_math.h" +#include "texture_handler.h" + +#include + +// PERF Disabled to not slow down the texure lookup functions. +//#define USE_SMOOTHERSTEP_FILTER + + +typedef mi::neuraylib::tct_deriv_float tct_deriv_float; +typedef mi::neuraylib::tct_deriv_float2 tct_deriv_float2; +typedef mi::neuraylib::tct_deriv_arr_float_2 tct_deriv_arr_float_2; +typedef mi::neuraylib::tct_deriv_arr_float_3 tct_deriv_arr_float_3; +typedef mi::neuraylib::tct_deriv_arr_float_4 tct_deriv_arr_float_4; +typedef mi::neuraylib::Shading_state_material_with_derivs Shading_state_material_with_derivs; +typedef mi::neuraylib::Shading_state_material Shading_state_material; +typedef mi::neuraylib::Texture_handler_base Texture_handler_base; +typedef mi::neuraylib::Tex_wrap_mode Tex_wrap_mode; +typedef mi::neuraylib::Mbsdf_part Mbsdf_part; + +#ifdef __CUDACC__ + + // Stores a float4 in a float[4] array. +__forceinline__ __device__ void store_result4(float res[4], const float4& v) +{ + res[0] = v.x; + res[1] = v.y; + res[2] = v.z; + res[3] = v.w; +} + +// Stores a float in all elements of a float[4] array. +__forceinline__ __device__ void store_result4(float res[4], float s) +{ + res[0] = res[1] = res[2] = res[3] = s; +} + +// Stores the given float values in a float[4] array. +__forceinline__ __device__ void store_result4(float res[4], float v0, float v1, float v2, float v3) +{ + res[0] = v0; + res[1] = v1; + res[2] = v2; + res[3] = v3; +} + +// Stores a float3 in a float[3] array. +__forceinline__ __device__ void store_result3(float res[3], const float3& v) +{ + res[0] = v.x; + res[1] = v.y; + res[2] = v.z; +} + +// Stores a float4 in a float[3] array, ignoring v.w. +__forceinline__ __device__ void store_result3(float res[3], const float4& v) +{ + res[0] = v.x; + res[1] = v.y; + res[2] = v.z; +} + +// Stores a float in all elements of a float[3] array. +__forceinline__ __device__ void store_result3(float res[3], float s) +{ + res[0] = res[1] = res[2] = s; +} + +// Stores the given float values in a float[3] array. +__forceinline__ __device__ void store_result3(float res[3], float v0, float v1, float v2) +{ + res[0] = v0; + res[1] = v1; + res[2] = v2; +} + +// Stores the luminance of a given float3 in a float. +__forceinline__ __device__ void store_result1(float* res, const float3& v) +{ + // store luminance + *res = 0.212671f * v.x + 0.71516f * v.y + 0.072169f * v.z; +} + +// Stores the luminance of 3 float scalars in a float. +__forceinline__ __device__ void store_result1(float* res, float v0, float v1, float v2) +{ + // store luminance + *res = 0.212671f * v0 + 0.715160f * v1 + 0.072169f * v2; +} + +// Stores a given float in a float +__forceinline__ __device__ void store_result1(float* res, float s) +{ + *res = s; +} + + +// ------------------------------------------------------------------------------------------------ +// Textures +// ------------------------------------------------------------------------------------------------ + +// Applies wrapping and cropping to the given coordinate. +// Note: This macro returns if wrap mode is clip and the coordinate is out of range. +#define WRAP_AND_CROP_OR_RETURN_BLACK(val, inv_dim, wrap_mode, crop_vals, store_res_func) \ + do \ + { \ + if ((wrap_mode) == mi::neuraylib::TEX_WRAP_REPEAT && \ + (crop_vals)[0] == 0.0f && (crop_vals)[1] == 1.0f) \ + { \ + /* Do nothing, use texture sampler default behavior */ \ + } \ + else \ + { \ + if ((wrap_mode) == mi::neuraylib::TEX_WRAP_REPEAT) \ + val = val - floorf(val); \ + else \ + { \ + if ((wrap_mode) == mi::neuraylib::TEX_WRAP_CLIP && (val < 0.0f || 1.0f <= val)) \ + { \ + store_res_func(result, 0.0f); \ + return; \ + } \ + else if ((wrap_mode) == mi::neuraylib::TEX_WRAP_MIRRORED_REPEAT) \ + { \ + float floored_val = floorf(val); \ + if ((int(floored_val) & 1) != 0) \ + val = 1.0f - (val - floored_val); \ + else \ + val = val - floored_val; \ + } \ + float inv_hdim = 0.5f * (inv_dim); \ + val = fminf(fmaxf(val, inv_hdim), 1.f - inv_hdim); \ + } \ + val = val * ((crop_vals)[1] - (crop_vals)[0]) + (crop_vals)[0]; \ + } \ + } while (0) + + +#ifdef USE_SMOOTHERSTEP_FILTER +// Modify texture coordinates to get better texture filtering, +// see http://www.iquilezles.org/www/articles/texture/texture.htm +#define APPLY_SMOOTHERSTEP_FILTER() \ + do \ + { \ + u = u * tex.size.x + 0.5f; \ + v = v * tex.size.y + 0.5f; \ + float u_i = floorf(u), v_i = floorf(v); \ + float u_f = u - u_i; \ + float v_f = v - v_i; \ + u_f = u_f * u_f * u_f * (u_f * (u_f * 6.f - 15.f) + 10.f); \ + v_f = v_f * v_f * v_f * (v_f * (v_f * 6.f - 15.f) + 10.f); \ + u = u_i + u_f; \ + v = v_i + v_f; \ + u = (u - 0.5f) * tex.inv_size.x; \ + v = (v - 0.5f) * tex.inv_size.y; \ + } while (0) +#else +#define APPLY_SMOOTHERSTEP_FILTER() +#endif + + +// Implementation of tex::lookup_float4() for a texture_2d texture. +extern "C" __device__ void tex_lookup_float4_2d( + float result[4], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float const coord[2], + Tex_wrap_mode const wrap_u, + Tex_wrap_mode const wrap_v, + float const crop_u[2], + float const crop_v[2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + // Note that self->num_textures == texture_idx is a valid case because 1 (the invalid index 0) is subtracted to get the final zerop based index. + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result4(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + float u = coord[0]; + float v = coord[1]; + + WRAP_AND_CROP_OR_RETURN_BLACK(u, tex.inv_size.x, wrap_u, crop_u, store_result4); + WRAP_AND_CROP_OR_RETURN_BLACK(v, tex.inv_size.y, wrap_v, crop_v, store_result4); + + APPLY_SMOOTHERSTEP_FILTER(); + + store_result4(result, tex2D(tex.filtered_object, u, v)); +} + +// Implementation of tex::lookup_float4() for a texture_2d texture. +extern "C" __device__ void tex_lookup_deriv_float4_2d( + float result[4], + Texture_handler_base const* self_base, + unsigned int texture_idx, + tct_deriv_float2 const* coord, + Tex_wrap_mode const wrap_u, + Tex_wrap_mode const wrap_v, + float const crop_u[2], + float const crop_v[2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result4(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + float u = coord->val.x; + float v = coord->val.y; + + WRAP_AND_CROP_OR_RETURN_BLACK(u, tex.inv_size.x, wrap_u, crop_u, store_result4); + WRAP_AND_CROP_OR_RETURN_BLACK(v, tex.inv_size.y, wrap_v, crop_v, store_result4); + + APPLY_SMOOTHERSTEP_FILTER(); + + store_result4(result, tex2DGrad(tex.filtered_object, u, v, coord->dx, coord->dy)); +} + +// Implementation of tex::lookup_float3() for a texture_2d texture. +extern "C" __device__ void tex_lookup_float3_2d( + float result[3], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float const coord[2], + Tex_wrap_mode const wrap_u, + Tex_wrap_mode const wrap_v, + float const crop_u[2], + float const crop_v[2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result3(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + float u = coord[0]; + float v = coord[1]; + + WRAP_AND_CROP_OR_RETURN_BLACK(u, tex.inv_size.x, wrap_u, crop_u, store_result3); + WRAP_AND_CROP_OR_RETURN_BLACK(v, tex.inv_size.y, wrap_v, crop_v, store_result3); + + APPLY_SMOOTHERSTEP_FILTER(); + + store_result3(result, tex2D(tex.filtered_object, u, v)); +} + +// Implementation of tex::lookup_float3() for a texture_2d texture. +extern "C" __device__ void tex_lookup_deriv_float3_2d( + float result[3], + Texture_handler_base const* self_base, + unsigned int texture_idx, + tct_deriv_float2 const* coord, + Tex_wrap_mode const wrap_u, + Tex_wrap_mode const wrap_v, + float const crop_u[2], + float const crop_v[2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result3(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + float u = coord->val.x; + float v = coord->val.y; + + WRAP_AND_CROP_OR_RETURN_BLACK(u, tex.inv_size.x, wrap_u, crop_u, store_result3); + WRAP_AND_CROP_OR_RETURN_BLACK(v, tex.inv_size.y, wrap_v, crop_v, store_result3); + + APPLY_SMOOTHERSTEP_FILTER(); + + store_result3(result, tex2DGrad(tex.filtered_object, u, v, coord->dx, coord->dy)); +} + +// Implementation of tex::texel_float4() for a texture_2d texture. +// Note: uvtile and/or animated textures are not supported +extern "C" __device__ void tex_texel_float4_2d( + float result[4], + Texture_handler_base const* self_base, + unsigned int texture_idx, + int const coord[2], + int const /*uv_tile*/ [2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result4(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + store_result4(result, tex2D(tex.unfiltered_object, + float(coord[0]) * tex.inv_size.x, + float(coord[1]) * tex.inv_size.y)); +} + +// Implementation of tex::lookup_float4() for a texture_3d texture. +extern "C" __device__ void tex_lookup_float4_3d( + float result[4], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float const coord[3], + Tex_wrap_mode wrap_u, + Tex_wrap_mode wrap_v, + Tex_wrap_mode wrap_w, + float const crop_u[2], + float const crop_v[2], + float const crop_w[2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result4(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + float u = coord[0]; + float v = coord[1]; + float w = coord[2]; + + WRAP_AND_CROP_OR_RETURN_BLACK(u, tex.inv_size.x, wrap_u, crop_u, store_result4); + WRAP_AND_CROP_OR_RETURN_BLACK(v, tex.inv_size.y, wrap_v, crop_v, store_result4); + WRAP_AND_CROP_OR_RETURN_BLACK(w, tex.inv_size.z, wrap_w, crop_w, store_result4); + + store_result4(result, tex3D(tex.filtered_object, u, v, w)); +} + +// Implementation of tex::lookup_float3() for a texture_3d texture. +extern "C" __device__ void tex_lookup_float3_3d( + float result[3], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float const coord[3], + Tex_wrap_mode wrap_u, + Tex_wrap_mode wrap_v, + Tex_wrap_mode wrap_w, + float const crop_u[2], + float const crop_v[2], + float const crop_w[2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result3(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + float u = coord[0]; + float v = coord[1]; + float w = coord[2]; + + WRAP_AND_CROP_OR_RETURN_BLACK(u, tex.inv_size.x, wrap_u, crop_u, store_result3); + WRAP_AND_CROP_OR_RETURN_BLACK(v, tex.inv_size.y, wrap_v, crop_v, store_result3); + WRAP_AND_CROP_OR_RETURN_BLACK(w, tex.inv_size.z, wrap_w, crop_w, store_result3); + + store_result3(result, tex3D(tex.filtered_object, u, v, w)); +} + +// Implementation of tex::texel_float4() for a texture_3d texture. +extern "C" __device__ void tex_texel_float4_3d( + float result[4], + Texture_handler_base const* self_base, + unsigned int texture_idx, + const int coord[3], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result4(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + store_result4(result, tex3D(tex.unfiltered_object, + float(coord[0]) * tex.inv_size.x, + float(coord[1]) * tex.inv_size.y, + float(coord[2]) * tex.inv_size.z)); +} + +// Implementation of tex::lookup_float4() for a texture_cube texture. +extern "C" __device__ void tex_lookup_float4_cube( + float result[4], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float const coord[3]) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result4(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + store_result4(result, texCubemap(tex.filtered_object, coord[0], coord[1], coord[2])); +} + +// Implementation of tex::lookup_float3() for a texture_cube texture. +extern "C" __device__ void tex_lookup_float3_cube( + float result[3], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float const coord[3]) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + store_result3(result, 0.0f); + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + store_result3(result, texCubemap(tex.filtered_object, coord[0], coord[1], coord[2])); +} + +// Implementation of resolution_2d function needed by generated code. +// Note: uvtile and/or animated textures are not supported +extern "C" __device__ void tex_resolution_2d( + int result[2], + Texture_handler_base const* self_base, + unsigned int texture_idx, + int const /*uv_tile*/ [2], + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + result[0] = 0; + result[1] = 0; + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + result[0] = tex.size.x; + result[1] = tex.size.y; +} + +// Implementation of resolution_3d function needed by generated code. +extern "C" __device__ void tex_resolution_3d( + int result[3], + Texture_handler_base const* self_base, + unsigned int texture_idx, + float /*frame*/) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + result[0] = 0; + result[1] = 0; + result[2] = 0; + return; + } + + TextureMDL const& tex = self->textures[texture_idx - 1]; + + result[0] = tex.size.x; + result[1] = tex.size.y; + result[2] = tex.size.z; +} + +// Implementation of texture_isvalid(). +extern "C" __device__ bool tex_texture_isvalid( + Texture_handler_base const* self_base, + unsigned int texture_idx) +{ + Texture_handler const* self = static_cast(self_base); + + return (texture_idx != 0 && texture_idx <= self->num_textures); +} + +// Implementation of frame function needed by generated code. +extern "C" __device__ void tex_frame( + int result[2], + Texture_handler_base const* self_base, + unsigned int texture_idx) +{ + Texture_handler const* self = static_cast(self_base); + + if (texture_idx == 0 || self->num_textures < texture_idx) + { + // invalid texture returns zero + result[0] = 0; + result[1] = 0; + return; + } + + // TextureMDL const& tex = self->textures[texture_idx - 1]; + result[0] = 0; + result[1] = 0; +} + + +// ------------------------------------------------------------------------------------------------ +// Light Profiles +// ------------------------------------------------------------------------------------------------ + + +// Implementation of light_profile_power() for a light profile. +extern "C" __device__ float df_light_profile_power( + Texture_handler_base const* self_base, + unsigned int light_profile_idx) +{ + Texture_handler const* self = static_cast(self_base); + + if (light_profile_idx == 0 || self->num_lightprofiles < light_profile_idx) + { + return 0.0f; // invalid light profile returns zero + } + + const Lightprofile& lp = self->lightprofiles[light_profile_idx - 1]; + + return lp.total_power; +} + +// Implementation of light_profile_maximum() for a light profile. +extern "C" __device__ float df_light_profile_maximum( + Texture_handler_base const* self_base, + unsigned int light_profile_idx) +{ + Texture_handler const* self = static_cast(self_base); + + if (light_profile_idx == 0 || self->num_lightprofiles < light_profile_idx) + { + return 0.0f; // invalid light profile returns zero + } + + const Lightprofile& lp = self->lightprofiles[light_profile_idx - 1]; + + return lp.candela_multiplier; +} + +// Implementation of light_profile_isvalid() for a light profile. +extern "C" __device__ bool df_light_profile_isvalid( + Texture_handler_base const* self_base, + unsigned int light_profile_idx) +{ + Texture_handler const* self = static_cast(self_base); + + return (light_profile_idx != 0 && light_profile_idx <= self->num_lightprofiles); +} + +// binary search through CDF +__forceinline__ __device__ unsigned int sample_cdf( + const float* cdf, + unsigned int cdf_size, + float xi) +{ + unsigned int li = 0; + unsigned int ri = cdf_size - 1; // This fails for cdf_size == 0. + unsigned int m = (li + ri) / 2; + + while (ri > li) + { + if (xi < cdf[m]) + { + ri = m; + } + else + { + li = m + 1; + } + + m = (li + ri) / 2; + } + + return m; +} + + +// Implementation of df::light_profile_evaluate() for a light profile. +extern "C" __device__ float df_light_profile_evaluate( + Texture_handler_base const* self_base, + unsigned int light_profile_idx, + float const theta_phi[2]) +{ + Texture_handler const* self = static_cast(self_base); + + if (light_profile_idx == 0 || self->num_lightprofiles < light_profile_idx) + { + return 0.0f; // invalid light profile returns zero + } + + const Lightprofile& lp = self->lightprofiles[light_profile_idx - 1]; + + // map theta to 0..1 range + float u = (theta_phi[0] - lp.theta_phi_start.x) * lp.theta_phi_inv_delta.x * lp.inv_angular_resolution.x; + + // converting input phi from -pi..pi to 0..2pi + float phi = (theta_phi[1] > 0.0f) ? theta_phi[1] : 2.0f * M_PIf + theta_phi[1]; + + // floorf wraps phi range into 0..2pi + phi = phi - lp.theta_phi_start.y - floorf((phi - lp.theta_phi_start.y) * 0.5f * M_1_PIf) * (2.0f * M_PIf); + + // (phi < 0.0f) is no problem, this is handle by the (black) border + // since it implies lp.theta_phi_start.y > 0 (and we really have "no data" below that) + float v = phi * lp.theta_phi_inv_delta.y * lp.inv_angular_resolution.y; + + // half pixel offset + // see https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#linear-filtering + u += 0.5f * lp.inv_angular_resolution.x; + v += 0.5f * lp.inv_angular_resolution.y; + + // wrap_mode: border black would be an alternative (but it produces artifacts at low res) + if (u < 0.0f || 1.0f < u || v < 0.0f || 1.0f < v) + { + return 0.0f; + } + + return tex2D(lp.eval_data, u, v) * lp.candela_multiplier; +} + +// Implementation of df::light_profile_sample() for a light profile. +extern "C" __device__ void df_light_profile_sample( + float result[3], // output: theta, phi, pdf + Texture_handler_base const* self_base, + unsigned int light_profile_idx, + float const xi[3]) // uniform random values +{ + result[0] = -1.0f; // negative theta means no emission + result[1] = -1.0f; + result[2] = 0.0f; + + Texture_handler const* self = static_cast(self_base); + + if (light_profile_idx == 0 || self->num_lightprofiles < light_profile_idx) + { + return; // invalid light profile returns zero + } + + const Lightprofile& lp = self->lightprofiles[light_profile_idx - 1]; + + uint2 res = lp.angular_resolution; + + // sample theta_out + //------------------------------------------- + float xi0 = xi[0]; + const float* cdf_data_theta = lp.cdf_data; // CDF theta + unsigned int idx_theta = sample_cdf(cdf_data_theta, res.x - 1, xi0); // binary search + + float prob_theta = cdf_data_theta[idx_theta]; + if (idx_theta > 0) + { + const float tmp = cdf_data_theta[idx_theta - 1]; + prob_theta -= tmp; + xi0 -= tmp; + } + + xi0 /= prob_theta; // rescale for re-usage + + // sample phi_out + //------------------------------------------- + float xi1 = xi[1]; + const float* cdf_data_phi = cdf_data_theta + + (res.x - 1) // CDF theta block + + (idx_theta * (res.y - 1)); // selected CDF for phi + + const unsigned int idx_phi = sample_cdf(cdf_data_phi, res.y - 1, xi1); // binary search + + float prob_phi = cdf_data_phi[idx_phi]; + if (idx_phi > 0) + { + const float tmp = cdf_data_phi[idx_phi - 1]; + + prob_phi -= tmp; + xi1 -= tmp; + } + + xi1 /= prob_phi; // rescale for re-usage + + // compute theta and phi + //------------------------------------------- + // sample uniformly within the patch (grid cell) + const float2 start = lp.theta_phi_start; + const float2 delta = lp.theta_phi_delta; + + const float cos_theta_0 = cosf(start.x + float(idx_theta) * delta.x); + const float cos_theta_1 = cosf(start.x + float(idx_theta + 1u) * delta.x); + + // n = \int_{\theta_0}^{\theta_1} \sin{\theta} \delta \theta + // = 1 / (\cos{\theta_0} - \cos{\theta_1}) + // + // \xi = n * \int_{\theta_0}^{\theta_1} \sin{\theta} \delta \theta + // => \cos{\theta} = (1 - \xi) \cos{\theta_0} + \xi \cos{\theta_1} + + const float cos_theta = (1.0f - xi1) * cos_theta_0 + xi1 * cos_theta_1; + + result[0] = acosf(cos_theta); + result[1] = start.y + (float(idx_phi) + xi0) * delta.y; + + // align phi + if (result[1] > 2.0f * M_PIf) + { + result[1] -= 2.0f * M_PIf; // wrap + } + if (result[1] > M_PIf) + { + result[1] = -2.0f * M_PIf + result[1]; // to [-pi, pi] + } + + // compute pdf + //------------------------------------------- + result[2] = prob_theta * prob_phi / (delta.y * (cos_theta_0 - cos_theta_1)); +} + + +// Implementation of df::light_profile_pdf() for a light profile. +extern "C" __device__ float df_light_profile_pdf( + Texture_handler_base const* self_base, + unsigned int light_profile_idx, + float const theta_phi[2]) +{ + Texture_handler const* self = static_cast(self_base); + + if (light_profile_idx == 0 || self->num_lightprofiles < light_profile_idx) + { + return 0.0f; // invalid light profile returns zero + } + + const Lightprofile& lp = self->lightprofiles[light_profile_idx - 1]; + + // CDF data + const uint2 res = lp.angular_resolution; + const float* cdf_data_theta = lp.cdf_data; + + // map theta to 0..1 range + const float theta = theta_phi[0] - lp.theta_phi_start.x; + const int idx_theta = int(theta * lp.theta_phi_inv_delta.x); + + // converting input phi from -pi..pi to 0..2pi + float phi = (theta_phi[1] > 0.0f) ? theta_phi[1] : (2.0f * M_PIf + theta_phi[1]); + + // floorf wraps phi range into 0..2pi + phi = phi - lp.theta_phi_start.y - floorf((phi - lp.theta_phi_start.y) * (0.5f * M_1_PIf)) * (2.0f * M_PIf); + + // (phi < 0.0f) is no problem, this is handle by the (black) border + // since it implies lp.theta_phi_start.y > 0 (and we really have "no data" below that) + const int idx_phi = int(phi * lp.theta_phi_inv_delta.y); + + // wrap_mode: border black would be an alternative (but it produces artifacts at low res) + if (idx_theta < 0 || (res.x - 2) < idx_theta || idx_phi < 0 || (res.y - 2) < idx_phi) // DAR BUG Was: (res.x - 2) < idx_phi + { + return 0.0f; + } + + // get probability for theta + //------------------------------------------- + + float prob_theta = cdf_data_theta[idx_theta]; + if (idx_theta > 0) + { + const float tmp = cdf_data_theta[idx_theta - 1]; + prob_theta -= tmp; + } + + // get probability for phi + //------------------------------------------- + const float* cdf_data_phi = cdf_data_theta + + (res.x - 1) // CDF theta block + + (idx_theta * (res.y - 1)); // selected CDF for phi + + + float prob_phi = cdf_data_phi[idx_phi]; + if (idx_phi > 0) + { + const float tmp = cdf_data_phi[idx_phi - 1]; + prob_phi -= tmp; + } + + // compute probability to select a position in the sphere patch + const float2 start = lp.theta_phi_start; + const float2 delta = lp.theta_phi_delta; + + const float cos_theta_0 = cos(start.x + float(idx_theta) * delta.x); + const float cos_theta_1 = cos(start.x + float(idx_theta + 1u) * delta.x); + + return prob_theta * prob_phi / (delta.y * (cos_theta_0 - cos_theta_1)); +} + + +// ------------------------------------------------------------------------------------------------ +// BSDF Measurements +// ------------------------------------------------------------------------------------------------ + +// Implementation of bsdf_measurement_isvalid() for an MBSDF. +extern "C" __device__ bool df_bsdf_measurement_isvalid( + Texture_handler_base const* self_base, + unsigned int bsdf_measurement_index) +{ + Texture_handler const* self = static_cast(self_base); + + return bsdf_measurement_index != 0 && bsdf_measurement_index <= self->num_mbsdfs; +} + +// Implementation of df::bsdf_measurement_resolution() function needed by generated code, +// which retrieves the angular and chromatic resolution of the given MBSDF. +// The returned triple consists of: number of equi-spaced steps of theta_i and theta_o, +// number of equi-spaced steps of phi, and number of color channels (1 or 3). +extern "C" __device__ void df_bsdf_measurement_resolution( + unsigned int result[3], + Texture_handler_base const* self_base, + unsigned int bsdf_measurement_index, + mi::neuraylib::Mbsdf_part part) +{ + Texture_handler const* self = static_cast(self_base); + + if (bsdf_measurement_index == 0 || self->num_mbsdfs < bsdf_measurement_index) + { + // invalid MBSDF returns zero + result[0] = 0; + result[1] = 0; + result[2] = 0; + return; + } + + Mbsdf const& bm = self->mbsdfs[bsdf_measurement_index - 1]; + + const unsigned int part_index = static_cast(part); + + // check for the part + if (bm.has_data[part_index] == 0) + { + result[0] = 0; + result[1] = 0; + result[2] = 0; + return; + } + + // pass out the information + result[0] = bm.angular_resolution[part_index].x; + result[1] = bm.angular_resolution[part_index].y; + result[2] = bm.num_channels[part_index]; +} + +__forceinline__ __device__ float3 bsdf_compute_uvw(const float theta_phi_in[2], + const float theta_phi_out[2]) +{ + // assuming each phi is between -pi and pi + float u = theta_phi_out[1] - theta_phi_in[1]; + if (u < 0.0) + { + u += 2.0f * M_PIf; + } + if (u > M_PIf) + { + u = 2.0f * M_PIf - u; + } + u *= M_1_PIf; + + const float v = theta_phi_out[0] * M_2_PIf; + const float w = theta_phi_in[0] * M_2_PIf; + + return make_float3(u, v, w); +} + +template +__forceinline__ __device__ T bsdf_measurement_lookup( + const cudaTextureObject_t& eval_volume, + const float theta_phi_in[2], + const float theta_phi_out[2]) +{ + // 3D volume on the GPU (phi_delta x theta_out x theta_in) + const float3 uvw = bsdf_compute_uvw(theta_phi_in, theta_phi_out); + + return tex3D(eval_volume, uvw.x, uvw.y, uvw.z); +} + +// Implementation of df::bsdf_measurement_evaluate() for an MBSDF. +extern "C" __device__ void df_bsdf_measurement_evaluate( + float result[3], + Texture_handler_base const* self_base, + unsigned int bsdf_measurement_index, + float const theta_phi_in[2], + float const theta_phi_out[2], + Mbsdf_part part) +{ + Texture_handler const* self = static_cast(self_base); + + if (bsdf_measurement_index == 0 || self->num_mbsdfs < bsdf_measurement_index) + { + // invalid MBSDF returns zero + store_result3(result, 0.0f); + return; + } + + const Mbsdf& bm = self->mbsdfs[bsdf_measurement_index - 1]; + + const unsigned int part_index = static_cast(part); + + // check for the parta + if (bm.has_data[part_index] == 0) + { + store_result3(result, 0.0f); + return; + } + + // handle channels + if (bm.num_channels[part_index] == 3) + { + const float4 sample = bsdf_measurement_lookup(bm.eval_data[part_index], theta_phi_in, theta_phi_out); + store_result3(result, sample.x, sample.y, sample.z); + } + else + { + const float sample = bsdf_measurement_lookup(bm.eval_data[part_index], theta_phi_in, theta_phi_out); + store_result3(result, sample); + } +} + +// Implementation of df::bsdf_measurement_sample() for an MBSDF. +extern "C" __device__ void df_bsdf_measurement_sample( + float result[3], // output: theta, phi, pdf + Texture_handler_base const* self_base, + unsigned int bsdf_measurement_index, + float const theta_phi_out[2], + float const xi[3], // uniform random values + Mbsdf_part part) +{ + result[0] = -1.0f; // negative theta means absorption + result[1] = -1.0f; + result[2] = 0.0f; + + Texture_handler const* self = static_cast(self_base); + + if (bsdf_measurement_index == 0 || self->num_mbsdfs < bsdf_measurement_index) + { + return; // invalid MBSDFs returns zero + } + + const Mbsdf& bm = self->mbsdfs[bsdf_measurement_index - 1]; + + unsigned int part_index = static_cast(part); + + if (bm.has_data[part_index] == 0) + { + return; // check for the part + } + + // CDF data + uint2 res = bm.angular_resolution[part_index]; + const float* sample_data = bm.sample_data[part_index]; + + // compute the theta_in index (flipping input and output, BSDFs are symmetric) + unsigned int idx_theta_in = (unsigned int)(theta_phi_out[0] * M_2_PIf * float(res.x)); + idx_theta_in = min(idx_theta_in, res.x - 1); + + // sample theta_out + //------------------------------------------- + float xi0 = xi[0]; + const float* cdf_theta = sample_data + idx_theta_in * res.x; + unsigned int idx_theta_out = sample_cdf(cdf_theta, res.x, xi0); // binary search + + float prob_theta = cdf_theta[idx_theta_out]; + if (idx_theta_out > 0) + { + const float tmp = cdf_theta[idx_theta_out - 1]; + prob_theta -= tmp; + xi0 -= tmp; + } + xi0 /= prob_theta; // rescale for re-usage + + // sample phi_out + //------------------------------------------- + float xi1 = xi[1]; + const float* cdf_phi = sample_data + + (res.x * res.x) + // CDF theta block + (idx_theta_in * res.x + idx_theta_out) * res.y; // selected CDF phi + + // select which half-circle to choose with probability 0.5 + const bool flip = (xi1 > 0.5f); + if (flip) + { + xi1 = 1.0f - xi1; + } + xi1 *= 2.0f; + + unsigned int idx_phi_out = sample_cdf(cdf_phi, res.y, xi1); // binary search + float prob_phi = cdf_phi[idx_phi_out]; + if (idx_phi_out > 0) + { + const float tmp = cdf_phi[idx_phi_out - 1]; + prob_phi -= tmp; + xi1 -= tmp; + } + xi1 /= prob_phi; // rescale for re-usage + + // compute theta and phi out + //------------------------------------------- + const float2 inv_res = bm.inv_angular_resolution[part_index]; + + const float s_theta = M_PI_2f * inv_res.x; + const float s_phi = M_PIf * inv_res.y; + + const float cos_theta_0 = cosf(float(idx_theta_out) * s_theta); + const float cos_theta_1 = cosf(float(idx_theta_out + 1u) * s_theta); + + const float cos_theta = cos_theta_0 * (1.0f - xi1) + cos_theta_1 * xi1; + result[0] = acosf(cos_theta); + result[1] = (float(idx_phi_out) + xi0) * s_phi; + + if (flip) + { + result[1] = 2.0f * M_PIf - result[1]; // phi \in [0, 2pi] + } + + // align phi + result[1] += (theta_phi_out[1] > 0) ? theta_phi_out[1] : (2.0f * M_PIf + theta_phi_out[1]); + if (result[1] > 2.0f * M_PIf) + { + result[1] -= 2.0f * M_PIf; + } + if (result[1] > M_PIf) + { + result[1] = -2.0f * M_PIf + result[1]; // to [-pi, pi] + } + + // compute pdf + //------------------------------------------- + result[2] = prob_theta * prob_phi * 0.5f / (s_phi * (cos_theta_0 - cos_theta_1)); +} + +// Implementation of df::bsdf_measurement_pdf() for an MBSDF. +extern "C" __device__ float df_bsdf_measurement_pdf( + Texture_handler_base const* self_base, + unsigned int bsdf_measurement_index, + float const theta_phi_in[2], + float const theta_phi_out[2], + Mbsdf_part part) +{ + Texture_handler const* self = static_cast(self_base); + + if (bsdf_measurement_index == 0 || self->num_mbsdfs < bsdf_measurement_index) + return 0.0f; // invalid MBSDF returns zero + + const Mbsdf& bm = self->mbsdfs[bsdf_measurement_index - 1]; + unsigned int part_index = static_cast(part); + + // check for the part + if (bm.has_data[part_index] == 0) + { + return 0.0f; + } + + // CDF data and resolution + const float* sample_data = bm.sample_data[part_index]; + uint2 res = bm.angular_resolution[part_index]; + + // compute indices in the CDF data + float3 uvw = bsdf_compute_uvw(theta_phi_in, theta_phi_out); // phi_delta, theta_out, theta_in + unsigned int idx_theta_in = (unsigned int)(theta_phi_in[0] * M_2_PIf * float(res.x)); + unsigned int idx_theta_out = (unsigned int)(theta_phi_out[0] * M_2_PIf * float(res.x)); + unsigned int idx_phi_out = (unsigned int)(uvw.x * float(res.y)); + + idx_theta_in = min(idx_theta_in, res.x - 1); + idx_theta_out = min(idx_theta_out, res.x - 1); + idx_phi_out = min(idx_phi_out, res.y - 1); + + // get probability to select theta_out + const float* cdf_theta = sample_data + idx_theta_in * res.x; + float prob_theta = cdf_theta[idx_theta_out]; + if (idx_theta_out > 0) + { + const float tmp = cdf_theta[idx_theta_out - 1]; + prob_theta -= tmp; + } + + // get probability to select phi_out + const float* cdf_phi = sample_data + + (res.x * res.x) + // CDF theta block + (idx_theta_in * res.x + idx_theta_out) * res.y; // selected CDF phi + + float prob_phi = cdf_phi[idx_phi_out]; + if (idx_phi_out > 0) + { + const float tmp = cdf_phi[idx_phi_out - 1]; + prob_phi -= tmp; + } + + // compute probability to select a position in the sphere patch + float2 inv_res = bm.inv_angular_resolution[part_index]; + + const float s_theta = M_PI_2f * inv_res.x; + const float s_phi = M_PIf * inv_res.y; + + const float cos_theta_0 = cosf(float(idx_theta_out) * s_theta); + const float cos_theta_1 = cosf(float(idx_theta_out + 1u) * s_theta); + + return prob_theta * prob_phi * 0.5f / (s_phi * (cos_theta_0 - cos_theta_1)); +} + + +__forceinline__ __device__ void df_bsdf_measurement_albedo( + float result[2], // output: max (in case of color) albedo for the selected direction ([0]) and global ([1]) + Texture_handler const* self, + unsigned int bsdf_measurement_index, + float const theta_phi[2], + Mbsdf_part part) +{ + const Mbsdf& bm = self->mbsdfs[bsdf_measurement_index - 1]; + const unsigned int part_index = static_cast(part); + + // check for the part + if (bm.has_data[part_index] == 0) + { + return; + } + + const uint2 res = bm.angular_resolution[part_index]; + unsigned int idx_theta = (unsigned int)(theta_phi[0] * M_2_PIf * float(res.x)); + + idx_theta = min(idx_theta, res.x - 1u); + result[0] = bm.albedo_data[part_index][idx_theta]; + result[1] = bm.max_albedo[part_index]; +} + +// Implementation of df::bsdf_measurement_albedos() for an MBSDF. +extern "C" __device__ void df_bsdf_measurement_albedos( + float result[4], // output: [0] albedo refl. for theta_phi + // [1] max albedo refl. global + // [2] albedo trans. for theta_phi + // [3] max albedo trans. global + Texture_handler_base const* self_base, + unsigned int bsdf_measurement_index, + float const theta_phi[2]) +{ + result[0] = 0.0f; + result[1] = 0.0f; + result[2] = 0.0f; + result[3] = 0.0f; + + Texture_handler const* self = static_cast(self_base); + + if (bsdf_measurement_index == 0 || self->num_mbsdfs < bsdf_measurement_index) + { + return; // invalid MBSDF returns zero + } + + df_bsdf_measurement_albedo(&result[0], + self, + bsdf_measurement_index, + theta_phi, + mi::neuraylib::MBSDF_DATA_REFLECTION); + + df_bsdf_measurement_albedo(&result[2], + self, + bsdf_measurement_index, + theta_phi, + mi::neuraylib::MBSDF_DATA_TRANSMISSION); +} + + +// ------------------------------------------------------------------------------------------------ +// Normal adaption (dummy functions) +// +// Can be enabled via backend option "use_renderer_adapt_normal". +// ------------------------------------------------------------------------------------------------ + +#ifndef TEX_SUPPORT_NO_DUMMY_ADAPTNORMAL + +// Implementation of adapt_normal(). +extern "C" __device__ void adapt_normal( + float result[3], + Texture_handler_base const* self_base, + Shading_state_material* state, + float const normal[3]) +{ + // just return original normal + result[0] = normal[0]; + result[1] = normal[1]; + result[2] = normal[2]; +} + +#endif // TEX_SUPPORT_NO_DUMMY_ADAPTNORMAL + + +// ------------------------------------------------------------------------------------------------ +// Scene data (dummy functions) +// ------------------------------------------------------------------------------------------------ + +#ifndef TEX_SUPPORT_NO_DUMMY_SCENEDATA + +// Implementation of scene_data_isvalid(). +extern "C" __device__ bool scene_data_isvalid( + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id) +{ + return false; +} + +// Implementation of scene_data_lookup_float4(). +extern "C" __device__ void scene_data_lookup_float4( + float result[4], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + float const default_value[4], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; + result[2] = default_value[2]; + result[3] = default_value[3]; +} + +// Implementation of scene_data_lookup_float3(). +extern "C" __device__ void scene_data_lookup_float3( + float result[3], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + float const default_value[3], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; + result[2] = default_value[2]; +} + +// Implementation of scene_data_lookup_color(). +extern "C" __device__ void scene_data_lookup_color( + float result[3], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + float const default_value[3], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; + result[2] = default_value[2]; +} + +// Implementation of scene_data_lookup_float2(). +extern "C" __device__ void scene_data_lookup_float2( + float result[2], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + float const default_value[2], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; +} + +// Implementation of scene_data_lookup_float(). +extern "C" __device__ float scene_data_lookup_float( + Texture_handler_base const* self_base, + Shading_state_material * state, + unsigned int scene_data_id, + float const default_value, + bool uniform_lookup) +{ + // just return default value + return default_value; +} + +// Implementation of scene_data_lookup_int4(). +extern "C" __device__ void scene_data_lookup_int4( + int result[4], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + int const default_value[4], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; + result[2] = default_value[2]; + result[3] = default_value[3]; +} + +// Implementation of scene_data_lookup_int3(). +extern "C" __device__ void scene_data_lookup_int3( + int result[3], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + int const default_value[3], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; + result[2] = default_value[2]; +} + +// Implementation of scene_data_lookup_int2(). +extern "C" __device__ void scene_data_lookup_int2( + int result[2], + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + int const default_value[2], + bool uniform_lookup) +{ + // just return default value + result[0] = default_value[0]; + result[1] = default_value[1]; +} + +// Implementation of scene_data_lookup_int(). +extern "C" __device__ int scene_data_lookup_int( + Texture_handler_base const* self_base, + Shading_state_material* state, + unsigned int scene_data_id, + int default_value, + bool uniform_lookup) +{ + // just return default value + return default_value; +} + +// Implementation of scene_data_lookup_float4() with derivatives. +extern "C" __device__ void scene_data_lookup_deriv_float4( + tct_deriv_arr_float_4* result, + Texture_handler_base const* self_base, + Shading_state_material_with_derivs* state, + unsigned int scene_data_id, + tct_deriv_arr_float_4 const* default_value, + bool uniform_lookup) +{ + // just return default value + *result = *default_value; +} + +// Implementation of scene_data_lookup_float3() with derivatives. +extern "C" __device__ void scene_data_lookup_deriv_float3( + tct_deriv_arr_float_3* result, + Texture_handler_base const* self_base, + Shading_state_material_with_derivs* state, + unsigned int scene_data_id, + tct_deriv_arr_float_3 const* default_value, + bool uniform_lookup) +{ + // just return default value + *result = *default_value; +} + +// Implementation of scene_data_lookup_color() with derivatives. +extern "C" __device__ void scene_data_lookup_deriv_color( + tct_deriv_arr_float_3* result, + Texture_handler_base const* self_base, + Shading_state_material_with_derivs * state, + unsigned int scene_data_id, + tct_deriv_arr_float_3 const* default_value, + bool uniform_lookup) +{ + // just return default value + *result = *default_value; +} + +// Implementation of scene_data_lookup_float2() with derivatives. +extern "C" __device__ void scene_data_lookup_deriv_float2( + tct_deriv_arr_float_2* result, + Texture_handler_base const* self_base, + Shading_state_material_with_derivs* state, + unsigned int scene_data_id, + tct_deriv_arr_float_2 const* default_value, + bool uniform_lookup) +{ + // just return default value + *result = *default_value; +} + +// Implementation of scene_data_lookup_float() with derivatives. +extern "C" __device__ void scene_data_lookup_deriv_float( + tct_deriv_float* result, + Texture_handler_base const* self_base, + Shading_state_material_with_derivs* state, + unsigned int scene_data_id, + tct_deriv_float const* default_value, + bool uniform_lookup) +{ + // just return default value + *result = *default_value; +} + +#endif // TEX_SUPPORT_NO_DUMMY_SCENEDATA + + +// ------------------------------------------------------------------------------------------------ +// Vtables +// ------------------------------------------------------------------------------------------------ + +#ifndef TEX_SUPPORT_NO_VTABLES +// The vtable containing all texture access handlers required by the generated code +// in "vtable" mode. +__device__ mi::neuraylib::Texture_handler_vtable tex_vtable = { + tex_lookup_float4_2d, + tex_lookup_float3_2d, + tex_texel_float4_2d, + tex_lookup_float4_3d, + tex_lookup_float3_3d, + tex_texel_float4_3d, + tex_lookup_float4_cube, + tex_lookup_float3_cube, + tex_resolution_2d, + tex_resolution_3d, + tex_texture_isvalid, + tex_frame, + df_light_profile_power, + df_light_profile_maximum, + df_light_profile_isvalid, + df_light_profile_evaluate, + df_light_profile_sample, + df_light_profile_pdf, + df_bsdf_measurement_isvalid, + df_bsdf_measurement_resolution, + df_bsdf_measurement_evaluate, + df_bsdf_measurement_sample, + df_bsdf_measurement_pdf, + df_bsdf_measurement_albedos, + adapt_normal, + scene_data_isvalid, + scene_data_lookup_float, + scene_data_lookup_float2, + scene_data_lookup_float3, + scene_data_lookup_float4, + scene_data_lookup_int, + scene_data_lookup_int2, + scene_data_lookup_int3, + scene_data_lookup_int4, + scene_data_lookup_color, +}; + +// The vtable containing all texture access handlers required by the generated code +// in "vtable" mode with derivatives. +__device__ mi::neuraylib::Texture_handler_deriv_vtable tex_deriv_vtable = { + tex_lookup_deriv_float4_2d, + tex_lookup_deriv_float3_2d, + tex_texel_float4_2d, + tex_lookup_float4_3d, + tex_lookup_float3_3d, + tex_texel_float4_3d, + tex_lookup_float4_cube, + tex_lookup_float3_cube, + tex_resolution_2d, + tex_resolution_3d, + tex_texture_isvalid, + tex_frame, + df_light_profile_power, + df_light_profile_maximum, + df_light_profile_isvalid, + df_light_profile_evaluate, + df_light_profile_sample, + df_light_profile_pdf, + df_bsdf_measurement_isvalid, + df_bsdf_measurement_resolution, + df_bsdf_measurement_evaluate, + df_bsdf_measurement_sample, + df_bsdf_measurement_pdf, + df_bsdf_measurement_albedos, + adapt_normal, + scene_data_isvalid, + scene_data_lookup_float, + scene_data_lookup_float2, + scene_data_lookup_float3, + scene_data_lookup_float4, + scene_data_lookup_int, + scene_data_lookup_int2, + scene_data_lookup_int3, + scene_data_lookup_int4, + scene_data_lookup_color, + scene_data_lookup_deriv_float, + scene_data_lookup_deriv_float2, + scene_data_lookup_deriv_float3, + scene_data_lookup_deriv_float4, + scene_data_lookup_deriv_color, +}; +#endif // TEX_SUPPORT_NO_VTABLES + +#endif // __CUDACC__ + +#endif // TEXTURE_LOOKUP_H diff --git a/apps/MDL_sdf/shaders/transform.h b/apps/MDL_sdf/shaders/transform.h new file mode 100644 index 00000000..49a63944 --- /dev/null +++ b/apps/MDL_sdf/shaders/transform.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TRANSFORM_H +#define TRANSFORM_H + +#include "config.h" + +#include + +#include "vector_math.h" + +// Get the 3x4 object to world transform and its inverse. +__forceinline__ __device__ void getTransforms(const OptixTraversableHandle handle, float4* mW, float4* mO) +{ + const float4* tW = optixGetInstanceTransformFromHandle(handle); + const float4* tO = optixGetInstanceInverseTransformFromHandle(handle); + + mW[0] = tW[0]; + mW[1] = tW[1]; + mW[2] = tW[2]; + + mO[0] = tO[0]; + mO[1] = tO[1]; + mO[2] = tO[2]; +} + +// Functions to get the individual transforms in case only one of them is needed. +__forceinline__ __device__ void getTransformObjectToWorld(const OptixTraversableHandle handle, float4* mW) +{ + const float4* tW = optixGetInstanceTransformFromHandle(handle); + + mW[0] = tW[0]; + mW[1] = tW[1]; + mW[2] = tW[2]; +} + +__forceinline__ __device__ void getTransformWorldToObject(const OptixTraversableHandle handle, float4* mO) +{ + const float4* tO = optixGetInstanceInverseTransformFromHandle(handle); + + mO[0] = tO[0]; + mO[1] = tO[1]; + mO[2] = tO[2]; +} + + +// Matrix3x4 * point. v.w == 1.0f +__forceinline__ __device__ float3 transformPoint(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z + m[0].w; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z + m[1].w; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z + m[2].w; + + return r; +} + +// Matrix3x4 * vector. v.w == 0.0f +__forceinline__ __device__ float3 transformVector(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z; + + return r; +} + +// (Matrix3x4^-1)^T * normal. v.w == 0.0f +// Takes the inverse matrix as input and applies it transposed. +__forceinline__ __device__ float3 transformNormal(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + r.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + r.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + + return r; +} + +// Matrix3x3 * vector. +// Used with light orientation matrices. +__forceinline__ __device__ float3 transformVector(const float3* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z; + + return r; +} + +#endif // TRANSFORM_H diff --git a/apps/MDL_sdf/shaders/vector_math.h b/apps/MDL_sdf/shaders/vector_math.h new file mode 100644 index 00000000..583d8cf0 --- /dev/null +++ b/apps/MDL_sdf/shaders/vector_math.h @@ -0,0 +1,2985 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef VECTOR_MATH_H +#define VECTOR_MATH_H + +#include "config.h" + +#include +#include + + +#if defined(__CUDACC__) || defined(__CUDABE__) +#define VECTOR_MATH_API __forceinline__ __host__ __device__ +#else +#include +#define VECTOR_MATH_API inline +#endif + + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288419716939937510 +#endif +#ifndef M_Ef +#define M_Ef 2.71828182845904523536f +#endif +#ifndef M_LOG2Ef +#define M_LOG2Ef 1.44269504088896340736f +#endif +#ifndef M_LOG10Ef +#define M_LOG10Ef 0.434294481903251827651f +#endif +#ifndef M_LN2f +#define M_LN2f 0.693147180559945309417f +#endif +#ifndef M_LN10f +#define M_LN10f 2.30258509299404568402f +#endif +#ifndef M_PIf +#define M_PIf 3.14159265358979323846f +#endif +#ifndef M_PI_2f +#define M_PI_2f 1.57079632679489661923f +#endif +#ifndef M_PI_4f +#define M_PI_4f 0.785398163397448309616f +#endif +#ifndef M_1_PIf +#define M_1_PIf 0.318309886183790671538f +#endif +#ifndef M_2_PIf +#define M_2_PIf 0.636619772367581343076f +#endif +#ifndef M_2_SQRTPIf +#define M_2_SQRTPIf 1.12837916709551257390f +#endif +#ifndef M_SQRT2f +#define M_SQRT2f 1.41421356237309504880f +#endif +#ifndef M_SQRT1_2f +#define M_SQRT1_2f 0.707106781186547524401f +#endif + +#if !defined(__CUDACC__) + +VECTOR_MATH_API int max(int a, int b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API int min(int a, int b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API long long max(long long a, long long b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API long long min(long long a, long long b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API unsigned int max(unsigned int a, unsigned int b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API unsigned int min(unsigned int a, unsigned int b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API unsigned long long max(unsigned long long a, unsigned long long b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API unsigned long long min(unsigned long long a, unsigned long long b) +{ + return (a < b) ? a : b; +} + +#endif + +/** lerp */ +VECTOR_MATH_API float lerp(const float a, const float b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float bilerp(const float x00, const float x10, const float x01, const float x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp */ +VECTOR_MATH_API float clamp(const float f, const float a, const float b) +{ + return fmaxf(a, fminf(f, b)); +} + + +/* float2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float2 make_float2(const float s) +{ + return make_float2(s, s); +} +VECTOR_MATH_API float2 make_float2(const int2& a) +{ + return make_float2(float(a.x), float(a.y)); +} +VECTOR_MATH_API float2 make_float2(const uint2& a) +{ + return make_float2(float(a.x), float(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float2 operator-(const float2& a) +{ + return make_float2(-a.x, -a.y); +} + +/** fabsf +* @{ +*/ +VECTOR_MATH_API float2 fabsf(const float2& a) +{ + return make_float2(fabsf(a.x), fabsf(a.y)); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float2 fminf(const float2& a, const float2& b) +{ + return make_float2(fminf(a.x, b.x), fminf(a.y, b.y)); +} +VECTOR_MATH_API float fminf(const float2& a) +{ + return fminf(a.x, a.y); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float2 fmaxf(const float2& a, const float2& b) +{ + return make_float2(fmaxf(a.x, b.x), fmaxf(a.y, b.y)); +} +VECTOR_MATH_API float fmaxf(const float2& a) +{ + return fmaxf(a.x, a.y); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float2 operator+(const float2& a, const float2& b) +{ + return make_float2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API float2 operator+(const float2& a, const float b) +{ + return make_float2(a.x + b, a.y + b); +} +VECTOR_MATH_API float2 operator+(const float a, const float2& b) +{ + return make_float2(a + b.x, a + b.y); +} +VECTOR_MATH_API void operator+=(float2& a, const float2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float2 operator-(const float2& a, const float2& b) +{ + return make_float2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API float2 operator-(const float2& a, const float b) +{ + return make_float2(a.x - b, a.y - b); +} +VECTOR_MATH_API float2 operator-(const float a, const float2& b) +{ + return make_float2(a - b.x, a - b.y); +} +VECTOR_MATH_API void operator-=(float2& a, const float2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float2 operator*(const float2& a, const float2& b) +{ + return make_float2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API float2 operator*(const float2& a, const float s) +{ + return make_float2(a.x * s, a.y * s); +} +VECTOR_MATH_API float2 operator*(const float s, const float2& a) +{ + return make_float2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(float2& a, const float2& s) +{ + a.x *= s.x; + a.y *= s.y; +} +VECTOR_MATH_API void operator*=(float2& a, const float s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float2 operator/(const float2& a, const float2& b) +{ + return make_float2(a.x / b.x, a.y / b.y); +} +VECTOR_MATH_API float2 operator/(const float2& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float2 operator/(const float s, const float2& a) +{ + return make_float2(s / a.x, s / a.y); +} +VECTOR_MATH_API void operator/=(float2& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float2 lerp(const float2& a, const float2& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float2 bilerp(const float2& x00, const float2& x10, const float2& x01, const float2& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float2 clamp(const float2& v, const float a, const float b) +{ + return make_float2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API float2 clamp(const float2& v, const float2& a, const float2& b) +{ + return make_float2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float2& a, const float2& b) +{ + return a.x * b.x + a.y * b.y; +} + +/** length */ +VECTOR_MATH_API float length(const float2& v) +{ + return sqrtf(dot(v, v)); +} + +/** normalize */ +VECTOR_MATH_API float2 normalize(const float2& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float2 floor(const float2& v) +{ + return make_float2(::floorf(v.x), ::floorf(v.y)); +} + +/** reflect */ +VECTOR_MATH_API float2 reflect(const float2& i, const float2& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL */ +VECTOR_MATH_API float2 faceforward(const float2& n, const float2& i, const float2& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float2 expf(const float2& v) +{ + return make_float2(::expf(v.x), ::expf(v.y)); +} + +/** pow **/ +VECTOR_MATH_API float2 powf(const float2& v, const float e) +{ + return make_float2(::powf(v.x, e), ::powf(v.y, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float2 sqrtf(const float2& v) +{ + return make_float2(::sqrtf(v.x), ::sqrtf(v.y)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float2& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float2& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + + +/* float3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float3 make_float3(const float s) +{ + return make_float3(s, s, s); +} +VECTOR_MATH_API float3 make_float3(const float2& a) +{ + return make_float3(a.x, a.y, 0.0f); +} +VECTOR_MATH_API float3 make_float3(const int3& a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +VECTOR_MATH_API float3 make_float3(const uint3& a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float3 operator-(const float3& a) +{ + return make_float3(-a.x, -a.y, -a.z); +} + +/** fabsf +* @{ +*/ +VECTOR_MATH_API float3 fabsf(const float3& a) +{ + return make_float3(fabsf(a.x), fabsf(a.y), fabsf(a.z)); +} + +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API float3 fminf(const float3& a, const float3& b) +{ + return make_float3(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z)); +} +VECTOR_MATH_API float fminf(const float3& a) +{ + return fminf(fminf(a.x, a.y), a.z); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float3 fmaxf(const float3& a, const float3& b) +{ + return make_float3(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z)); +} +VECTOR_MATH_API float fmaxf(const float3& a) +{ + return fmaxf(fmaxf(a.x, a.y), a.z); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float3 operator+(const float3& a, const float3& b) +{ + return make_float3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API float3 operator+(const float3& a, const float b) +{ + return make_float3(a.x + b, a.y + b, a.z + b); +} +VECTOR_MATH_API float3 operator+(const float a, const float3& b) +{ + return make_float3(a + b.x, a + b.y, a + b.z); +} +VECTOR_MATH_API void operator+=(float3& a, const float3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float3 operator-(const float3& a, const float3& b) +{ + return make_float3(a.x - b.x, a.y - b.y, a.z - b.z); +} +VECTOR_MATH_API float3 operator-(const float3& a, const float b) +{ + return make_float3(a.x - b, a.y - b, a.z - b); +} +VECTOR_MATH_API float3 operator-(const float a, const float3& b) +{ + return make_float3(a - b.x, a - b.y, a - b.z); +} +VECTOR_MATH_API void operator-=(float3& a, const float3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float3 operator*(const float3& a, const float3& b) +{ + return make_float3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API float3 operator*(const float3& a, const float s) +{ + return make_float3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API float3 operator*(const float s, const float3& a) +{ + return make_float3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(float3& a, const float3& s) +{ + a.x *= s.x; + a.y *= s.y; + a.z *= s.z; +} +VECTOR_MATH_API void operator*=(float3& a, const float s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float3 operator/(const float3& a, const float3& b) +{ + return make_float3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API float3 operator/(const float3& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float3 operator/(const float s, const float3& a) +{ + return make_float3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(float3& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float3 lerp(const float3& a, const float3& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float3 bilerp(const float3& x00, const float3& x10, const float3& x01, const float3& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float3 clamp(const float3& v, const float a, const float b) +{ + return make_float3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API float3 clamp(const float3& v, const float3& a, const float3& b) +{ + return make_float3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float3& a, const float3& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +/** cross product */ +VECTOR_MATH_API float3 cross(const float3& a, const float3& b) +{ + return make_float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); +} + +/** length */ +VECTOR_MATH_API float length(const float3& v) +{ + return sqrtf(dot(v, v)); +} + +/** normalize */ +VECTOR_MATH_API float3 normalize(const float3& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float3 floor(const float3& v) +{ + return make_float3(::floorf(v.x), ::floorf(v.y), ::floorf(v.z)); +} + +/** reflect */ +VECTOR_MATH_API float3 reflect(const float3& i, const float3& n) +{ + return i - 2.0f * n * dot(i, n); +} + +/** Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL */ +VECTOR_MATH_API float3 faceforward(const float3& n, const float3& i, const float3& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float3 expf(const float3& v) +{ + return make_float3(::expf(v.x), ::expf(v.y), ::expf(v.z)); +} + +/** pow **/ +VECTOR_MATH_API float3 powf(const float3& v, const float e) +{ + return make_float3(::powf(v.x, e), ::powf(v.y, e), ::powf(v.z, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float3 sqrtf(const float3& v) +{ + return make_float3(::sqrtf(v.x), ::sqrtf(v.y), ::sqrtf(v.z)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float3& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float3& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + +/* float4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float4 make_float4(const float s) +{ + return make_float4(s, s, s, s); +} +VECTOR_MATH_API float4 make_float4(const float3& a) +{ + return make_float4(a.x, a.y, a.z, 0.0f); +} +VECTOR_MATH_API float4 make_float4(const int4& a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +VECTOR_MATH_API float4 make_float4(const uint4& a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float4 operator-(const float4& a) +{ + return make_float4(-a.x, -a.y, -a.z, -a.w); +} + +/** fabsf +* @{ +*/ +VECTOR_MATH_API float4 fabsf(const float4& a) +{ + return make_float4(fabsf(a.x), fabsf(a.y), fabsf(a.z), fabsf(a.w)); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float4 fminf(const float4& a, const float4& b) +{ + return make_float4(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z), fminf(a.w, b.w)); +} +VECTOR_MATH_API float fminf(const float4& a) +{ + return fminf(fminf(a.x, a.y), fminf(a.z, a.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float4 fmaxf(const float4& a, const float4& b) +{ + return make_float4(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z), fmaxf(a.w, b.w)); +} +VECTOR_MATH_API float fmaxf(const float4& a) +{ + return fmaxf(fmaxf(a.x, a.y), fmaxf(a.z, a.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float4 operator+(const float4& a, const float4& b) +{ + return make_float4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API float4 operator+(const float4& a, const float b) +{ + return make_float4(a.x + b, a.y + b, a.z + b, a.w + b); +} +VECTOR_MATH_API float4 operator+(const float a, const float4& b) +{ + return make_float4(a + b.x, a + b.y, a + b.z, a + b.w); +} +VECTOR_MATH_API void operator+=(float4& a, const float4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float4 operator-(const float4& a, const float4& b) +{ + return make_float4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} +VECTOR_MATH_API float4 operator-(const float4& a, const float b) +{ + return make_float4(a.x - b, a.y - b, a.z - b, a.w - b); +} +VECTOR_MATH_API float4 operator-(const float a, const float4& b) +{ + return make_float4(a - b.x, a - b.y, a - b.z, a - b.w); +} +VECTOR_MATH_API void operator-=(float4& a, const float4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float4 operator*(const float4& a, const float4& s) +{ + return make_float4(a.x * s.x, a.y * s.y, a.z * s.z, a.w * s.w); +} +VECTOR_MATH_API float4 operator*(const float4& a, const float s) +{ + return make_float4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API float4 operator*(const float s, const float4& a) +{ + return make_float4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(float4& a, const float4& s) +{ + a.x *= s.x; a.y *= s.y; a.z *= s.z; a.w *= s.w; +} +VECTOR_MATH_API void operator*=(float4& a, const float s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float4 operator/(const float4& a, const float4& b) +{ + return make_float4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API float4 operator/(const float4& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float4 operator/(const float s, const float4& a) +{ + return make_float4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(float4& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float4 lerp(const float4& a, const float4& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float4 bilerp(const float4& x00, const float4& x10, const float4& x01, const float4& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float4 clamp(const float4& v, const float a, const float b) +{ + return make_float4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API float4 clamp(const float4& v, const float4& a, const float4& b) +{ + return make_float4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float4& a, const float4& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +/** length */ +VECTOR_MATH_API float length(const float4& r) +{ + return sqrtf(dot(r, r)); +} + +/** normalize */ +VECTOR_MATH_API float4 normalize(const float4& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float4 floor(const float4& v) +{ + return make_float4(::floorf(v.x), ::floorf(v.y), ::floorf(v.z), ::floorf(v.w)); +} + +/** reflect */ +VECTOR_MATH_API float4 reflect(const float4& i, const float4& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** +* Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL +*/ +VECTOR_MATH_API float4 faceforward(const float4& n, const float4& i, const float4& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float4 expf(const float4& v) +{ + return make_float4(::expf(v.x), ::expf(v.y), ::expf(v.z), ::expf(v.w)); +} + +/** pow */ +VECTOR_MATH_API float4 powf(const float4& v, const float e) +{ + return make_float4(::powf(v.x, e), ::powf(v.y, e), ::powf(v.z, e), ::powf(v.w, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float4 sqrtf(const float4& v) +{ + return make_float4(::sqrtf(v.x), ::sqrtf(v.y), ::sqrtf(v.z), ::sqrtf(v.w)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float4& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float4& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + + +/* int functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API int clamp(const int f, const int a, const int b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int1& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int1& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int2 make_int2(const int s) +{ + return make_int2(s, s); +} +VECTOR_MATH_API int2 make_int2(const float2& a) +{ + return make_int2(int(a.x), int(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int2 operator-(const int2& a) +{ + return make_int2(-a.x, -a.y); +} + +/** min */ +VECTOR_MATH_API int2 min(const int2& a, const int2& b) +{ + return make_int2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API int2 max(const int2& a, const int2& b) +{ + return make_int2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int2 operator+(const int2& a, const int2& b) +{ + return make_int2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(int2& a, const int2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int2 operator-(const int2& a, const int2& b) +{ + return make_int2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API int2 operator-(const int2& a, const int b) +{ + return make_int2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(int2& a, const int2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int2 operator*(const int2& a, const int2& b) +{ + return make_int2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API int2 operator*(const int2& a, const int s) +{ + return make_int2(a.x * s, a.y * s); +} +VECTOR_MATH_API int2 operator*(const int s, const int2& a) +{ + return make_int2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(int2& a, const int s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int2 clamp(const int2& v, const int a, const int b) +{ + return make_int2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API int2 clamp(const int2& v, const int2& a, const int2& b) +{ + return make_int2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int2& a, const int2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const int2& a, const int2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int2& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int2& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int3 make_int3(const int s) +{ + return make_int3(s, s, s); +} +VECTOR_MATH_API int3 make_int3(const float3& a) +{ + return make_int3(int(a.x), int(a.y), int(a.z)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int3 operator-(const int3& a) +{ + return make_int3(-a.x, -a.y, -a.z); +} + +/** min */ +VECTOR_MATH_API int3 min(const int3& a, const int3& b) +{ + return make_int3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API int3 max(const int3& a, const int3& b) +{ + return make_int3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int3 operator+(const int3& a, const int3& b) +{ + return make_int3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(int3& a, const int3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int3 operator-(const int3& a, const int3& b) +{ + return make_int3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(int3& a, const int3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int3 operator*(const int3& a, const int3& b) +{ + return make_int3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API int3 operator*(const int3& a, const int s) +{ + return make_int3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API int3 operator*(const int s, const int3& a) +{ + return make_int3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(int3& a, const int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API int3 operator/(const int3& a, const int3& b) +{ + return make_int3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API int3 operator/(const int3& a, const int s) +{ + return make_int3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API int3 operator/(const int s, const int3& a) +{ + return make_int3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(int3& a, const int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int3 clamp(const int3& v, const int a, const int b) +{ + return make_int3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API int3 clamp(const int3& v, const int3& a, const int3& b) +{ + return make_int3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int3& a, const int3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const int3& a, const int3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int3& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int3& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int4 make_int4(const int s) +{ + return make_int4(s, s, s, s); +} +VECTOR_MATH_API int4 make_int4(const float4& a) +{ + return make_int4((int) a.x, (int) a.y, (int) a.z, (int) a.w); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int4 operator-(const int4& a) +{ + return make_int4(-a.x, -a.y, -a.z, -a.w); +} + +/** min */ +VECTOR_MATH_API int4 min(const int4& a, const int4& b) +{ + return make_int4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} + +/** max */ +VECTOR_MATH_API int4 max(const int4& a, const int4& b) +{ + return make_int4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int4 operator+(const int4& a, const int4& b) +{ + return make_int4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(int4& a, const int4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int4 operator-(const int4& a, const int4& b) +{ + return make_int4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(int4& a, const int4& b) +{ + a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int4 operator*(const int4& a, const int4& b) +{ + return make_int4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API int4 operator*(const int4& a, const int s) +{ + return make_int4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API int4 operator*(const int s, const int4& a) +{ + return make_int4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(int4& a, const int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API int4 operator/(const int4& a, const int4& b) +{ + return make_int4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API int4 operator/(const int4& a, const int s) +{ + return make_int4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API int4 operator/(const int s, const int4& a) +{ + return make_int4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(int4& a, const int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int4 clamp(const int4& v, const int a, const int b) +{ + return make_int4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API int4 clamp(const int4& v, const int4& a, const int4& b) +{ + return make_int4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int4& a, const int4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const int4& a, const int4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int4& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int4& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* uint functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API unsigned int clamp(const unsigned int f, const unsigned int a, const unsigned int b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API unsigned int getByIndex(const uint1& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(uint1& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint2 make_uint2(const unsigned int s) +{ + return make_uint2(s, s); +} +VECTOR_MATH_API uint2 make_uint2(const float2& a) +{ + return make_uint2((unsigned int) a.x, (unsigned int) a.y); +} +/** @} */ + +/** min */ +VECTOR_MATH_API uint2 min(const uint2& a, const uint2& b) +{ + return make_uint2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API uint2 max(const uint2& a, const uint2& b) +{ + return make_uint2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API uint2 operator+(const uint2& a, const uint2& b) +{ + return make_uint2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(uint2& a, const uint2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint2 operator-(const uint2& a, const uint2& b) +{ + return make_uint2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API uint2 operator-(const uint2& a, const unsigned int b) +{ + return make_uint2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(uint2& a, const uint2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint2 operator*(const uint2& a, const uint2& b) +{ + return make_uint2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API uint2 operator*(const uint2& a, const unsigned int s) +{ + return make_uint2(a.x * s, a.y * s); +} +VECTOR_MATH_API uint2 operator*(const unsigned int s, const uint2& a) +{ + return make_uint2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(uint2& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint2 clamp(const uint2& v, const unsigned int a, const unsigned int b) +{ + return make_uint2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API uint2 clamp(const uint2& v, const uint2& a, const uint2& b) +{ + return make_uint2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint2& a, const uint2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const uint2& a, const uint2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API unsigned int getByIndex(const uint2& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(uint2& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint3 make_uint3(const unsigned int s) +{ + return make_uint3(s, s, s); +} +VECTOR_MATH_API uint3 make_uint3(const float3& a) +{ + return make_uint3((unsigned int) a.x, (unsigned int) a.y, (unsigned int) a.z); +} +/** @} */ + +/** min */ +VECTOR_MATH_API uint3 min(const uint3& a, const uint3& b) +{ + return make_uint3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API uint3 max(const uint3& a, const uint3& b) +{ + return make_uint3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API uint3 operator+(const uint3& a, const uint3& b) +{ + return make_uint3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(uint3& a, const uint3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint3 operator-(const uint3& a, const uint3& b) +{ + return make_uint3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(uint3& a, const uint3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint3 operator*(const uint3& a, const uint3& b) +{ + return make_uint3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API uint3 operator*(const uint3& a, const unsigned int s) +{ + return make_uint3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API uint3 operator*(const unsigned int s, const uint3& a) +{ + return make_uint3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(uint3& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API uint3 operator/(const uint3& a, const uint3& b) +{ + return make_uint3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API uint3 operator/(const uint3& a, const unsigned int s) +{ + return make_uint3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API uint3 operator/(const unsigned int s, const uint3& a) +{ + return make_uint3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(uint3& a, const unsigned int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint3 clamp(const uint3& v, const unsigned int a, const unsigned int b) +{ + return make_uint3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API uint3 clamp(const uint3& v, const uint3& a, const uint3& b) +{ + return make_uint3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint3& a, const uint3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const uint3& a, const uint3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API unsigned int getByIndex(const uint3& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(uint3& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint4 make_uint4(const unsigned int s) +{ + return make_uint4(s, s, s, s); +} +VECTOR_MATH_API uint4 make_uint4(const float4& a) +{ + return make_uint4((unsigned int) a.x, (unsigned int) a.y, (unsigned int) a.z, (unsigned int) a.w); +} +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API uint4 min(const uint4& a, const uint4& b) +{ + return make_uint4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API uint4 max(const uint4& a, const uint4& b) +{ + return make_uint4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API uint4 operator+(const uint4& a, const uint4& b) +{ + return make_uint4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(uint4& a, const uint4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint4 operator-(const uint4& a, const uint4& b) +{ + return make_uint4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(uint4& a, const uint4& b) +{ + a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint4 operator*(const uint4& a, const uint4& b) +{ + return make_uint4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API uint4 operator*(const uint4& a, const unsigned int s) +{ + return make_uint4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API uint4 operator*(const unsigned int s, const uint4& a) +{ + return make_uint4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(uint4& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API uint4 operator/(const uint4& a, const uint4& b) +{ + return make_uint4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API uint4 operator/(const uint4& a, const unsigned int s) +{ + return make_uint4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API uint4 operator/(const unsigned int s, const uint4& a) +{ + return make_uint4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(uint4& a, const unsigned int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint4 clamp(const uint4& v, const unsigned int a, const unsigned int b) +{ + return make_uint4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API uint4 clamp(const uint4& v, const uint4& a, const uint4& b) +{ + return make_uint4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint4& a, const uint4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const uint4& a, const uint4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API unsigned int getByIndex(const uint4& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(uint4& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + +/* long long functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API long long clamp(const long long f, const long long a, const long long b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong1& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong1& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong2 make_longlong2(const long long s) +{ + return make_longlong2(s, s); +} +VECTOR_MATH_API longlong2 make_longlong2(const float2& a) +{ + return make_longlong2(int(a.x), int(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong2 operator-(const longlong2& a) +{ + return make_longlong2(-a.x, -a.y); +} + +/** min */ +VECTOR_MATH_API longlong2 min(const longlong2& a, const longlong2& b) +{ + return make_longlong2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API longlong2 max(const longlong2& a, const longlong2& b) +{ + return make_longlong2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong2 operator+(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(longlong2& a, const longlong2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong2 operator-(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API longlong2 operator-(const longlong2& a, const long long b) +{ + return make_longlong2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(longlong2& a, const longlong2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong2 operator*(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API longlong2 operator*(const longlong2& a, const long long s) +{ + return make_longlong2(a.x * s, a.y * s); +} +VECTOR_MATH_API longlong2 operator*(const long long s, const longlong2& a) +{ + return make_longlong2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(longlong2& a, const long long s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong2 clamp(const longlong2& v, const long long a, const long long b) +{ + return make_longlong2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API longlong2 clamp(const longlong2& v, const longlong2& a, const longlong2& b) +{ + return make_longlong2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong2& a, const longlong2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const longlong2& a, const longlong2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong2& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong2& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong3 make_longlong3(const long long s) +{ + return make_longlong3(s, s, s); +} +VECTOR_MATH_API longlong3 make_longlong3(const float3& a) +{ + return make_longlong3((long long) a.x, (long long) a.y, (long long) a.z); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong3 operator-(const longlong3& a) +{ + return make_longlong3(-a.x, -a.y, -a.z); +} + +/** min */ +VECTOR_MATH_API longlong3 min(const longlong3& a, const longlong3& b) +{ + return make_longlong3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API longlong3 max(const longlong3& a, const longlong3& b) +{ + return make_longlong3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong3 operator+(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(longlong3& a, const longlong3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong3 operator-(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(longlong3& a, const longlong3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong3 operator*(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API longlong3 operator*(const longlong3& a, const long long s) +{ + return make_longlong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API longlong3 operator*(const long long s, const longlong3& a) +{ + return make_longlong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(longlong3& a, const long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API longlong3 operator/(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API longlong3 operator/(const longlong3& a, const long long s) +{ + return make_longlong3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API longlong3 operator/(const long long s, const longlong3& a) +{ + return make_longlong3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(longlong3& a, const long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong3 clamp(const longlong3& v, const long long a, const long long b) +{ + return make_longlong3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API longlong3 clamp(const longlong3& v, const longlong3& a, const longlong3& b) +{ + return make_longlong3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong3& a, const longlong3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const longlong3& a, const longlong3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong3& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong3& v, int i, int x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong4 make_longlong4(const long long s) +{ + return make_longlong4(s, s, s, s); +} +VECTOR_MATH_API longlong4 make_longlong4(const float4& a) +{ + return make_longlong4((long long) a.x, (long long) a.y, (long long) a.z, (long long) a.w); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong4 operator-(const longlong4& a) +{ + return make_longlong4(-a.x, -a.y, -a.z, -a.w); +} + +/** min */ +VECTOR_MATH_API longlong4 min(const longlong4& a, const longlong4& b) +{ + return make_longlong4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} + +/** max */ +VECTOR_MATH_API longlong4 max(const longlong4& a, const longlong4& b) +{ + return make_longlong4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong4 operator+(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(longlong4& a, const longlong4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong4 operator-(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(longlong4& a, const longlong4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong4 operator*(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API longlong4 operator*(const longlong4& a, const long long s) +{ + return make_longlong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API longlong4 operator*(const long long s, const longlong4& a) +{ + return make_longlong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(longlong4& a, const long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API longlong4 operator/(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API longlong4 operator/(const longlong4& a, const long long s) +{ + return make_longlong4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API longlong4 operator/(const long long s, const longlong4& a) +{ + return make_longlong4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(longlong4& a, const long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong4 clamp(const longlong4& v, const long long a, const long long b) +{ + return make_longlong4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API longlong4 clamp(const longlong4& v, const longlong4& a, const longlong4& b) +{ + return make_longlong4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong4& a, const longlong4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const longlong4& a, const longlong4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong4& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong4& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + +/* ulonglong functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API unsigned long long clamp(const unsigned long long f, const unsigned long long a, const unsigned long long b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong1& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(ulonglong1& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong2 make_ulonglong2(const unsigned long long s) +{ + return make_ulonglong2(s, s); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const float2& a) +{ + return make_ulonglong2((unsigned long long)a.x, (unsigned long long)a.y); +} +/** @} */ + +/** min */ +VECTOR_MATH_API ulonglong2 min(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API ulonglong2 max(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator+(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(ulonglong2& a, const ulonglong2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator-(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API ulonglong2 operator-(const ulonglong2& a, const unsigned long long b) +{ + return make_ulonglong2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(ulonglong2& a, const ulonglong2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator*(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API ulonglong2 operator*(const ulonglong2& a, const unsigned long long s) +{ + return make_ulonglong2(a.x * s, a.y * s); +} +VECTOR_MATH_API ulonglong2 operator*(const unsigned long long s, const ulonglong2& a) +{ + return make_ulonglong2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(ulonglong2& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong2 clamp(const ulonglong2& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API ulonglong2 clamp(const ulonglong2& v, const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong2& a, const ulonglong2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const ulonglong2& a, const ulonglong2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong2& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(ulonglong2& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong3 make_ulonglong3(const unsigned long long s) +{ + return make_ulonglong3(s, s, s); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const float3& a) +{ + return make_ulonglong3((unsigned long long)a.x, (unsigned long long)a.y, (unsigned long long)a.z); +} +/** @} */ + +/** min */ +VECTOR_MATH_API ulonglong3 min(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API ulonglong3 max(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator+(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(ulonglong3& a, const ulonglong3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator-(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(ulonglong3& a, const ulonglong3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator*(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API ulonglong3 operator*(const ulonglong3& a, const unsigned long long s) +{ + return make_ulonglong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API ulonglong3 operator*(const unsigned long long s, const ulonglong3& a) +{ + return make_ulonglong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(ulonglong3& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator/(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API ulonglong3 operator/(const ulonglong3& a, const unsigned long long s) +{ + return make_ulonglong3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API ulonglong3 operator/(const unsigned long long s, const ulonglong3& a) +{ + return make_ulonglong3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(ulonglong3& a, const unsigned long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong3 clamp(const ulonglong3& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API ulonglong3 clamp(const ulonglong3& v, const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong3& a, const ulonglong3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const ulonglong3& a, const ulonglong3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong3& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(ulonglong3& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long s) +{ + return make_ulonglong4(s, s, s, s); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const float4& a) +{ + return make_ulonglong4((unsigned long long)a.x, (unsigned long long)a.y, (unsigned long long)a.z, (unsigned long long)a.w); +} +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API ulonglong4 min(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API ulonglong4 max(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator+(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(ulonglong4& a, const ulonglong4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator-(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(ulonglong4& a, const ulonglong4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator*(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API ulonglong4 operator*(const ulonglong4& a, const unsigned long long s) +{ + return make_ulonglong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API ulonglong4 operator*(const unsigned long long s, const ulonglong4& a) +{ + return make_ulonglong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(ulonglong4& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator/(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API ulonglong4 operator/(const ulonglong4& a, const unsigned long long s) +{ + return make_ulonglong4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API ulonglong4 operator/(const unsigned long long s, const ulonglong4& a) +{ + return make_ulonglong4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(ulonglong4& a, const unsigned long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong4 clamp(const ulonglong4& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API ulonglong4 clamp(const ulonglong4& v, const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong4& a, const ulonglong4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const ulonglong4& a, const ulonglong4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong4& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(ulonglong4& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/******************************************************************************/ + +/** Narrowing functions +* @{ +*/ +VECTOR_MATH_API int2 make_int2(const int3& v0) +{ + return make_int2(v0.x, v0.y); +} +VECTOR_MATH_API int2 make_int2(const int4& v0) +{ + return make_int2(v0.x, v0.y); +} +VECTOR_MATH_API int3 make_int3(const int4& v0) +{ + return make_int3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API uint2 make_uint2(const uint3& v0) +{ + return make_uint2(v0.x, v0.y); +} +VECTOR_MATH_API uint2 make_uint2(const uint4& v0) +{ + return make_uint2(v0.x, v0.y); +} +VECTOR_MATH_API uint3 make_uint3(const uint4& v0) +{ + return make_uint3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API longlong2 make_longlong2(const longlong3& v0) +{ + return make_longlong2(v0.x, v0.y); +} +VECTOR_MATH_API longlong2 make_longlong2(const longlong4& v0) +{ + return make_longlong2(v0.x, v0.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const longlong4& v0) +{ + return make_longlong3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const ulonglong3& v0) +{ + return make_ulonglong2(v0.x, v0.y); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const ulonglong4& v0) +{ + return make_ulonglong2(v0.x, v0.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const ulonglong4& v0) +{ + return make_ulonglong3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API float2 make_float2(const float3& v0) +{ + return make_float2(v0.x, v0.y); +} +VECTOR_MATH_API float2 make_float2(const float4& v0) +{ + return make_float2(v0.x, v0.y); +} +VECTOR_MATH_API float2 make_float2(const uint3& v0) +{ + return make_float2(float(v0.x), float(v0.y)); +} +VECTOR_MATH_API float3 make_float3(const float4& v0) +{ + return make_float3(v0.x, v0.y, v0.z); +} +/** @} */ + +/** Assemble functions from smaller vectors +* @{ +*/ +VECTOR_MATH_API int3 make_int3(const int v0, const int2& v1) +{ + return make_int3(v0, v1.x, v1.y); +} +VECTOR_MATH_API int3 make_int3(const int2& v0, const int v1) +{ + return make_int3(v0.x, v0.y, v1); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int v1, const int2& v2) +{ + return make_int4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int2& v1, const int v2) +{ + return make_int4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API int4 make_int4(const int2& v0, const int v1, const int v2) +{ + return make_int4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int3& v1) +{ + return make_int4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API int4 make_int4(const int3& v0, const int v1) +{ + return make_int4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API int4 make_int4(const int2& v0, const int2& v1) +{ + return make_int4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API uint3 make_uint3(const unsigned int v0, const uint2& v1) +{ + return make_uint3(v0, v1.x, v1.y); +} +VECTOR_MATH_API uint3 make_uint3(const uint2& v0, const unsigned int v1) +{ + return make_uint3(v0.x, v0.y, v1); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const unsigned int v1, const uint2& v2) +{ + return make_uint4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const uint2& v1, const unsigned int v2) +{ + return make_uint4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API uint4 make_uint4(const uint2& v0, const unsigned int v1, const unsigned int v2) +{ + return make_uint4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const uint3& v1) +{ + return make_uint4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API uint4 make_uint4(const uint3& v0, const unsigned int v1) +{ + return make_uint4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API uint4 make_uint4(const uint2& v0, const uint2& v1) +{ + return make_uint4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const long long v0, const longlong2& v1) +{ + return make_longlong3(v0, v1.x, v1.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const longlong2& v0, const long long v1) +{ + return make_longlong3(v0.x, v0.y, v1); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const long long v1, const longlong2& v2) +{ + return make_longlong4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const longlong2& v1, const long long v2) +{ + return make_longlong4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong2& v0, const long long v1, const long long v2) +{ + return make_longlong4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const longlong3& v1) +{ + return make_longlong4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong3& v0, const long long v1) +{ + return make_longlong4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong2& v0, const longlong2& v1) +{ + return make_longlong4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const unsigned long long v0, const ulonglong2& v1) +{ + return make_ulonglong3(v0, v1.x, v1.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const ulonglong2& v0, const unsigned long long v1) +{ + return make_ulonglong3(v0.x, v0.y, v1); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const unsigned long long v1, const ulonglong2& v2) +{ + return make_ulonglong4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const ulonglong2& v1, const unsigned long long v2) +{ + return make_ulonglong4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong2& v0, const unsigned long long v1, const unsigned long long v2) +{ + return make_ulonglong4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const ulonglong3& v1) +{ + return make_ulonglong4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong3& v0, const unsigned long long v1) +{ + return make_ulonglong4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong2& v0, const ulonglong2& v1) +{ + return make_ulonglong4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API float3 make_float3(const float2& v0, const float v1) +{ + return make_float3(v0.x, v0.y, v1); +} +VECTOR_MATH_API float3 make_float3(const float v0, const float2& v1) +{ + return make_float3(v0, v1.x, v1.y); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float v1, const float2& v2) +{ + return make_float4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float2& v1, const float v2) +{ + return make_float4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API float4 make_float4(const float2& v0, const float v1, const float v2) +{ + return make_float4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float3& v1) +{ + return make_float4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API float4 make_float4(const float3& v0, const float v1) +{ + return make_float4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API float4 make_float4(const float2& v0, const float2& v1) +{ + return make_float4(v0.x, v0.y, v1.x, v1.y); +} +/** @} */ + +#endif // VECTOR_MATH_H diff --git a/apps/MDL_sdf/shaders/vertex_attributes.h b/apps/MDL_sdf/shaders/vertex_attributes.h new file mode 100644 index 00000000..c87fdb7e --- /dev/null +++ b/apps/MDL_sdf/shaders/vertex_attributes.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef VERTEX_ATTRIBUTES_H +#define VERTEX_ATTRIBUTES_H + +struct TriangleAttributes +{ + float3 vertex; + float3 tangent; + float3 normal; + float3 texcoord; +}; + +#endif // VERTEX_ATTRIBUTES_H diff --git a/apps/MDL_sdf/src/Application.cpp b/apps/MDL_sdf/src/Application.cpp new file mode 100644 index 00000000..bdab6a67 --- /dev/null +++ b/apps/MDL_sdf/src/Application.cpp @@ -0,0 +1,3001 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Application.h" +#include "inc/LoaderIES.h" +#include "inc/Parser.h" +#include "inc/Raytracer.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "inc/CheckMacros.h" + +#include "inc/MyAssert.h" + +Application::Application(GLFWwindow* window, const Options& options) +: m_window(window) +, m_isValid(false) +, m_guiState(GUI_STATE_NONE) +, m_isVisibleGUI(true) +, m_width(512) +, m_height(512) +, m_mode(0) +, m_maskDevices(0x00FFFFFF) // A maximum of 24 devices is supported by default. Limited by the UUID arrays +, m_sizeArena(64) // Default to 64 MiB Arenas when nothing is specified inside the system description. +, m_interop(0) +, m_peerToPeer(P2P_TEX) // Enable only texture sharing via CUDA peer-to-peer access by default. This is fast via NVLINK. +, m_present(false) +, m_presentNext(true) +, m_presentAtSecond(1.0) +, m_previousComplete(false) +, m_typeLens(TYPE_LENS_PINHOLE) +, m_samplesSqrt(1) +, m_epsilonFactor(500.0f) +, m_clockFactor(1000.0f) +, m_useDirectLighting(true) +, m_sdfIterations(100) +, m_sdfEpsilon(0.001f) +, m_sdfOffset(2.0f) +, m_typeEnv(NUM_LIGHT_TYPES) +, m_mouseSpeedRatio(10.0f) +, m_idGroup(0) +, m_idInstance(0) +, m_idGeometry(0) +, m_indexMaterialGUI(0) +{ + try + { + m_timer.restart(); + + // Initialize the top-level keywords of the scene description for faster search. + + // Camera parameters + // These can be set in either the system or scene description. Latter takes precedence. + m_mapKeywordScene["lensShader"] = KS_LENS_SHADER; + m_mapKeywordScene["center"] = KS_CENTER; // Center of interest. + m_mapKeywordScene["camera"] = KS_CAMERA; // Camere location, polar orientation, field of view. + // Tonemapper parameters + // These can be set in either the system or scene description. Latter takes precedence. + m_mapKeywordScene["gamma"] = KS_GAMMA; + m_mapKeywordScene["colorBalance"] = KS_COLOR_BALANCE; + m_mapKeywordScene["whitePoint"] = KS_WHITE_POINT; + m_mapKeywordScene["burnHighlights"] = KS_BURN_HIGHLIGHTS; + m_mapKeywordScene["crushBlacks"] = KS_CRUSH_BLACKS; + m_mapKeywordScene["saturation"] = KS_SATURATION; + m_mapKeywordScene["brightness"] = KS_BRIGHTNESS; + // Emission parameters (used by the hardcoded lights) + m_mapKeywordScene["emission"] = KS_EMISSION; + m_mapKeywordScene["emissionMultiplier"] = KS_EMISSION_MULTIPLIER; + m_mapKeywordScene["emissionProfile"] = KS_EMISSION_PROFILE; + m_mapKeywordScene["emissionTexture"] = KS_EMISSION_TEXTURE; + m_mapKeywordScene["spotAngle"] = KS_SPOT_ANGLE; + m_mapKeywordScene["spotExponent"] = KS_SPOT_EXPONENT; + // Transformations + m_mapKeywordScene["identity"] = KS_IDENTITY; + m_mapKeywordScene["push"] = KS_PUSH; + m_mapKeywordScene["pop"] = KS_POP; + m_mapKeywordScene["rotate"] = KS_ROTATE; + m_mapKeywordScene["scale"] = KS_SCALE; + m_mapKeywordScene["translate"] = KS_TRANSLATE; + // Scene elements + m_mapKeywordScene["mdl"] = KS_MDL; + m_mapKeywordScene["light"] = KS_LIGHT; + m_mapKeywordScene["model"] = KS_MODEL; + + // ===== IMGUI INTERFACE + + // Setup ImGui binding. + ImGui::CreateContext(); + ImGui_ImplGlfwGL3_Init(window, true); + + // This initializes the GLFW part including the font texture. + ImGui_ImplGlfwGL3_NewFrame(); + ImGui::EndFrame(); + +#if 0 + // Style the GUI colors to a neutral greyscale with plenty of transparency to concentrate on the image. + ImGuiStyle& style = ImGui::GetStyle(); + + // Change these RGB values to get any other tint. + const float r = 1.0f; + const float g = 1.0f; + const float b = 1.0f; + + style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.6f); + style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_PopupBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_Border] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(r * 0.0f, g * 0.0f, b * 0.0f, 0.4f); + style.Colors[ImGuiCol_FrameBg] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_TitleBg] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.2f); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Button] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Header] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Column] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ColumnHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ColumnActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_CloseButton] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_CloseButtonHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_PlotLines] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 1.0f); + style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_PlotHistogram] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 1.0f); + style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(r * 0.5f, g * 0.5f, b * 0.5f, 1.0f); + style.Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.2f); + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(r * 1.0f, g * 1.0f, b * 0.0f, 1.0f); // Yellow + style.Colors[ImGuiCol_NavHighlight] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); +#endif + + // ===== SYSTEM DESCRIPTION + + m_width = std::max(1, options.getWidth()); + m_height = std::max(1, options.getHeight()); + m_mode = std::max(0, options.getMode()); + m_optimize = options.getOptimize(); + + // Initialize the system options to minimum defaults to work, but require useful settings inside the system options file. + // The minimum path length values will generate useful direct lighting results, but transmissions will be mostly black. + m_resolution = make_int2(1, 1); + m_tileSize = make_int2(8, 8); + m_pathLengths = make_int2(0, 2); + m_walkLength = 1; // Number of random walk steps until the maximum distance is selected. + + m_prefixScreenshot = std::string("./img"); // Default to current working directory and prefix "img". + + // Tonmapper neutral defaults. The system description overrides these. + m_tonemapperGUI.gamma = 1.0f; + m_tonemapperGUI.whitePoint = 1.0f; + m_tonemapperGUI.colorBalance[0] = 1.0f; + m_tonemapperGUI.colorBalance[1] = 1.0f; + m_tonemapperGUI.colorBalance[2] = 1.0f; + m_tonemapperGUI.burnHighlights = 1.0f; + m_tonemapperGUI.crushBlacks = 0.0f; + m_tonemapperGUI.saturation = 1.0f; + m_tonemapperGUI.brightness = 1.0f; + + m_rotationEnvironment[0] = 0.0f; + m_rotationEnvironment[1] = 0.0f; + m_rotationEnvironment[2] = 0.0f; + + // System wide parameters are loaded from this file to keep the number of command line options small. + const std::string filenameSystem = options.getSystem(); + if (!loadSystemDescription(filenameSystem)) + { + std::cerr << "ERROR: Application() failed to load system description file " << filenameSystem << '\n'; + MY_ASSERT(!"Failed to load system description"); + return; // m_isValid == false. + } + + // DEBUG + std::cout << "Arena size = " << m_sizeArena << " MiB\n"; + std::cout << "OpenGL interop = " << m_interop << '\n'; + + const double timeSystem = m_timer.getTime(); // Contains constructor and GUI initialization. + + // ===== SCENE DESCRIPTION + + // Host side scene graph information. + m_scene = std::make_shared(m_idGroup++); // Create the scene's root group first. + + // Load the scene description file and generate the host side materials, lights, and scene hierarchy with geometry. + const std::string filenameScene = options.getScene(); + if (!loadSceneDescription(filenameScene)) + { + std::cerr << "ERROR: Application() failed to load scene description file " << filenameScene << '\n'; + MY_ASSERT(!"Failed to load scene description"); + return; + } + + createCameras(); + + MY_ASSERT(m_idGeometry == m_geometries.size()); + + const double timeScene = m_timer.getTime(); + + // ===== RASTERIZER + + m_camera.setResolution(m_resolution.x, m_resolution.y); + m_camera.setSpeedRatio(m_mouseSpeedRatio); + + // Initialize the OpenGL rasterizer. + m_rasterizer = std::make_unique(m_width, m_height, m_interop); + + // Must set the resolution explicitly to be able to calculate + // the proper vertex attributes for display and the PBO size in case of interop. + m_rasterizer->setResolution(m_resolution.x, m_resolution.y); + m_rasterizer->setTonemapper(m_tonemapperGUI); + + const unsigned int tex = m_rasterizer->getTextureObject(); + const unsigned int pbo = m_rasterizer->getPixelBufferObject(); + + const double timeRasterizer = m_timer.getTime(); + + // ===== RAYTRACER + + m_typeEnv = (!m_lightsGUI.empty()) ? m_lightsGUI[0].typeLight : NUM_LIGHT_TYPES; // NUM_LIGHT_TYPES means not an environment light either. + + m_raytracer = std::make_unique(m_maskDevices, m_typeEnv, m_interop, tex, pbo, m_sizeArena, m_peerToPeer); + + // If the raytracer could not be initialized correctly, return and leave Application invalid. + if (!m_raytracer->m_isValid) + { + std::cerr << "ERROR: Application() Could not initialize Raytracer\n"; + return; // Exit application. + } + + // Load system description has set the MDL search paths vector. + if (!m_raytracer->initMDL(m_searchPaths)) + { + std::cerr << "ERROR: Application() Could not initialize MDL\n"; + return; // Exit application. + } + + // Determine which device is the one running the OpenGL implementation. + // The first OpenGL-CUDA device match wins. + int deviceMatch = -1; + +#if 1 + // UUID works under Windows and Linux + // FIXME Windows should prefer the LUID method because of potential issues with MS Hybrid setups. + const int numDevicesOGL = m_rasterizer->getNumDevices(); + + for (int i = 0; i < numDevicesOGL && deviceMatch == -1; ++i) + { + deviceMatch = m_raytracer->matchUUID(reinterpret_cast(m_rasterizer->getUUID(i))); + } +#else + // LUID only works under Windows because it requires the EXT_external_objects_win32 extension. + // DEBUG With multicast enabled, both devices have the same LUID and the OpenGL node mask is the OR of the individual device node masks. + // Means the result of the deviceMatch here is depending on the CUDA device order. + // Seems like multicast needs to handle CUDA - OpenGL interop differently. + // With multicast enabled, uploading the PBO with glTexImage2D halves the framerate when presenting each image in both the single-GPU and multi-GPU P2P strategy. + // Means there is an expensive PCI-E copy going on in that case. + const unsigned char* luid = m_rasterizer->getLUID(); + const int nodeMask = m_rasterizer->getNodeMask(); + + // The cuDeviceGetLuid() takes char* and unsigned int though. + deviceMatch = m_raytracer->matchLUID(reinterpret_cast(luid), nodeMask); +#endif + + if (deviceMatch == -1) + { + if (m_interop == INTEROP_MODE_TEX) + { + std::cerr << "ERROR: Application() OpenGL texture image interop without OpenGL device in active devices will not display the image!\n"; + return; // Exit application. + } + if (m_interop == INTEROP_MODE_PBO) + { + std::cerr << "WARNING: Application() OpenGL pixel buffer interop without OpenGL device in active devices will result in reduced performance!\n"; + } + } + + m_state.resolution = m_resolution; + m_state.tileSize = m_tileSize; + m_state.pathLengths = m_pathLengths; + m_state.walkLength = m_walkLength; + m_state.samplesSqrt = m_samplesSqrt; + m_state.typeLens = m_typeLens; + m_state.epsilonFactor = m_epsilonFactor; + m_state.clockFactor = m_clockFactor; + m_state.directLighting = (m_useDirectLighting) ? 1 : 0; + m_state.sdfIterations = m_sdfIterations; + m_state.sdfEpsilon = m_sdfEpsilon; + m_state.sdfOffset = m_sdfOffset; + + // Sync the state with the default GUI data. + m_raytracer->initState(m_state); + + // Device side scene information. + m_raytracer->initTextures(m_mapPictures); // These are the textures used for lights only, outside the MDL materials. + m_raytracer->initCameras(m_cameras); // Currently there is only one but this supports arbitrary many which could be used to select viewpoints or do animation (and camera motion blur) in the future. + m_raytracer->initMaterialsMDL(m_materialsMDL); // The MaterialMDL structure will receive all per material reference data. + + // Only when all MDL materials have been initialized, the information about which of them contains emissions is available inside the m_materialsMDL. + // Traverse the scene once and generate light definitions for the meshes with emissive materials. + createMeshLights(); + + m_raytracer->initScene(m_scene, m_idGeometry); // m_idGeometry is the number of geometries in the scene. + m_raytracer->initLights(m_lightsGUI); // With arbitrary mesh lights, the geometry attributes and indices can only be filled after initScene(). + + const double timeRaytracer = m_timer.getTime(); + + // Print out hiow long the initialization of each module took. + std::cout << "Application() " << timeRaytracer << " seconds overall\n"; + std::cout << "{\n"; + std::cout << " System = " << timeSystem << " seconds\n"; + std::cout << " Scene = " << timeScene - timeSystem << " seconds\n"; + std::cout << " Rasterizer = " << timeRasterizer - timeScene << " seconds\n"; + std::cout << " Raytracer = " << timeRaytracer - timeRasterizer << " seconds\n"; + std::cout << "}\n"; + + restartRendering(); // Trigger a new rendering. + + m_isValid = true; + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + +Application::~Application() +{ + if (m_raytracer != nullptr) + { + m_raytracer->shutdownMDL(); + } + + for (MaterialMDL* material: m_materialsMDL) + { + delete material; + } + + for (std::map::const_iterator it = m_mapPictures.begin(); it != m_mapPictures.end(); ++it) + { + delete it->second; + } + + ImGui_ImplGlfwGL3_Shutdown(); + ImGui::DestroyContext(); +} + +bool Application::isValid() const +{ + return m_isValid; +} + +void Application::reshape(const int w, const int h) +{ + // Do not allow zero size client windows! All other reshape() routines depend on this and do not check this again. + if ( (m_width != w || m_height != h) && w != 0 && h != 0) + { + m_width = w; + m_height = h; + + m_rasterizer->reshape(m_width, m_height); + } +} + +void Application::restartRendering() +{ + guiRenderingIndicator(true); + + m_presentNext = true; + m_presentAtSecond = 1.0; + + m_previousComplete = false; + + m_timer.restart(); +} + +bool Application::render() +{ + bool finish = false; + bool flush = false; + + try + { + CameraDefinition camera; + + const bool cameraChanged = m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W); + if (cameraChanged) + { + m_cameras[0] = camera; + m_raytracer->updateCamera(0, camera); + + restartRendering(); + } + + const unsigned int iterationIndex = m_raytracer->render(); + + // When the renderer has completed all iterations, change the GUI title bar to green. + const bool complete = ((unsigned int)(m_samplesSqrt * m_samplesSqrt) <= iterationIndex); + + if (complete) + { + guiRenderingIndicator(false); // Not rendering anymore. + + flush = !m_previousComplete && complete; // Completion status changed to true. + } + + m_previousComplete = complete; + + // When benchmark is enabled, exit the application when the requested samples per pixel have been rendered. + // Actually this render() function is not called when m_mode == 1 but keep the finish here to exit on exceptions. + finish = ((m_mode == 1) && complete); + + // Only update the texture when a restart happened, one second passed to reduce required bandwidth, or the rendering is newly complete. + if (m_presentNext || flush) + { + m_raytracer->updateDisplayTexture(); // This directly updates the display HDR texture for all rendering strategies. + + m_presentNext = m_present; + } + + double seconds = m_timer.getTime(); +#if 1 + // When in interactive mode, show the all rendered frames during the first half second to get some initial refinement. + if (m_mode == 0 && seconds < 0.5) + { + m_presentAtSecond = 1.0; + m_presentNext = true; + } +#endif + + // Present an updated image once a second or when rendering completed or the benchmark finished. + if (m_presentAtSecond < seconds || flush || finish) + { + m_presentAtSecond = ceil(seconds); + m_presentNext = true; // Present at least every second. + + if (flush || finish) // Only print the performance when the samples per pixels are reached. + { + const double fps = double(iterationIndex) / seconds; + + std::ostringstream stream; + stream.precision(3); // Precision is # digits in fraction part. + stream << std::fixed << iterationIndex << " / " << seconds << " = " << fps << " fps"; + std::cout << stream.str() << '\n'; + +#if 0 // Automated benchmark in interactive mode. Change m_isVisibleGUI default to false! + std::ostringstream filename; + filename << "result_interactive_" << m_interop << "_" << m_tileSize.x << "_" << m_tileSize.y << ".log"; + const bool success = saveString(filename.str(), stream.str()); + if (success) + { + std::cout << filename.str() << '\n'; // Print out the filename to indicate success. + } + finish = true; // Exit application after interactive rendering finished. +#endif + } + } + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + finish = true; + } + return finish; +} + +void Application::benchmark() +{ + try + { + CameraDefinition camera; + + const bool cameraChanged = m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W); + if (cameraChanged) + { + m_cameras[0] = camera; + m_raytracer->updateCamera(0, camera); + + // restartRendering(); + } + + const unsigned int spp = (unsigned int)(m_samplesSqrt * m_samplesSqrt); + unsigned int iterationIndex = 0; + + m_timer.restart(); + + // This renders all sub-frames as fast as possible by pushing all kernel launches into the CUDA stream asynchronously. + // Benchmark with m_interop == INTEROP_MODE_OFF to get the raw raytracing performance. + while (iterationIndex < spp) + { + iterationIndex = m_raytracer->render(m_mode); + } + + m_raytracer->synchronize(); // Wait until any asynchronous operations have finished. + + const double seconds = m_timer.getTime(); + const double fps = double(iterationIndex) / seconds; + + std::ostringstream stream; + stream.precision(3); // Precision is # digits in fraction part. + stream << std::fixed << iterationIndex << " / " << seconds << " = " << fps << " fps"; + std::cout << stream.str() << '\n'; + +#if 0 // Automated benchmark in batch mode. + std::ostringstream filename; + filename << "result_batch_" << m_interop << "_" << m_tileSize.x << "_" << m_tileSize.y << ".log"; + const bool success = saveString(filename.str(), stream.str()); + if (success) + { + std::cout << filename.str() << '\n'; // Print out the filename to indicate success. + } +#endif + + screenshot(true); + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + +void Application::display() +{ + m_rasterizer->display(); +} + +void Application::guiNewFrame() +{ + ImGui_ImplGlfwGL3_NewFrame(); +} + +void Application::guiReferenceManual() +{ + ImGui::ShowTestWindow(); +} + +void Application::guiRender() +{ + ImGui::Render(); + ImGui_ImplGlfwGL3_RenderDrawData(ImGui::GetDrawData()); +} + +void Application::createCameras() +{ + CameraDefinition camera; + + m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W, true); + + m_cameras.push_back(camera); +} + +void Application::guiEventHandler() +{ + const ImGuiIO& io = ImGui::GetIO(); + + if (ImGui::IsKeyPressed(' ', false)) // Key Space: Toggle the GUI window display (in interactive mode only). + { + m_isVisibleGUI = !m_isVisibleGUI; + } + if (ImGui::IsKeyPressed('S', false)) // Key S: Save the current system options to a file "system_MDL_sdf___.txt" + { + MY_VERIFY( saveSystemDescription() ); + } + if (ImGui::IsKeyPressed('P', false)) // Key P: Save the current output buffer with tonemapping into a *.png file. + { + MY_VERIFY( screenshot(true) ); + } + if (ImGui::IsKeyPressed('H', false)) // Key H: Save the current linear output buffer into a *.hdr file. + { + MY_VERIFY( screenshot(false) ); + } + + const ImVec2 mousePosition = ImGui::GetMousePos(); // Mouse coordinate window client rect. + const int x = int(mousePosition.x); + const int y = int(mousePosition.y); + + switch (m_guiState) + { + case GUI_STATE_NONE: + if (!io.WantCaptureMouse) // Only allow camera interactions to begin when interacting with the GUI. + { + if (ImGui::IsMouseDown(0)) // LMB down event? + { + m_camera.setBaseCoordinates(x, y); + m_guiState = GUI_STATE_ORBIT; + } + else if (ImGui::IsMouseDown(1)) // RMB down event? + { + m_camera.setBaseCoordinates(x, y); + m_guiState = GUI_STATE_DOLLY; + } + else if (ImGui::IsMouseDown(2)) // MMB down event? + { + m_camera.setBaseCoordinates(x, y); + m_guiState = GUI_STATE_PAN; + } + else if (io.MouseWheel != 0.0f) // Mouse wheel zoom. + { + m_camera.zoom(io.MouseWheel); + } + } + break; + + case GUI_STATE_ORBIT: + if (ImGui::IsMouseReleased(0)) // LMB released? End of orbit mode. + { + m_guiState = GUI_STATE_NONE; + } + else + { + m_camera.orbit(x, y); + } + break; + + case GUI_STATE_DOLLY: + if (ImGui::IsMouseReleased(1)) // RMB released? End of dolly mode. + { + m_guiState = GUI_STATE_NONE; + } + else + { + m_camera.dolly(x, y); + } + break; + + case GUI_STATE_PAN: + if (ImGui::IsMouseReleased(2)) // MMB released? End of pan mode. + { + m_guiState = GUI_STATE_NONE; + } + else + { + m_camera.pan(x, y); + } + break; + } +} + +void Application::guiWindow() +{ + // Do not display the GUI window when it has been toggled to off with SPACE key or when running in benchmark mode. + if (!m_isVisibleGUI || m_mode == 1) + { + return; + } + + bool refresh = false; + + ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiSetCond_FirstUseEver); + + ImGuiWindowFlags window_flags = 0; + if (!ImGui::Begin("MDL_sdf", nullptr, window_flags)) // No bool flag to omit the close button. + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + ImGui::PushItemWidth(-200); // Right-aligned, keep pixels for the labels. + + if (ImGui::CollapsingHeader("System")) + { + if (ImGui::DragFloat("Mouse Ratio", &m_mouseSpeedRatio, 0.1f, 0.1f, 1000.0f, "%.1f")) + { + m_camera.setSpeedRatio(m_mouseSpeedRatio); + } + if (ImGui::Checkbox("Present", &m_present)) + { + // No action needed, happens automatically on next render invocation. + } + if (ImGui::Checkbox("Direct Lighting", &m_useDirectLighting)) + { + m_state.directLighting = (m_useDirectLighting) ? 1 : 0; + m_raytracer->updateState(m_state); + refresh = true; + } + if (m_typeEnv == TYPE_LIGHT_ENV_SPHERE) + { + if (ImGui::DragFloat3("Environment Rotation", m_rotationEnvironment, 1.0f, 0.0f, 360.0f)) + { + const dp::math::Quatf xRot(dp::math::Vec3f(1.0f, 0.0f, 0.0f), dp::math::degToRad(m_rotationEnvironment[0])); + const dp::math::Quatf yRot(dp::math::Vec3f(0.0f, 1.0f, 0.0f), dp::math::degToRad(m_rotationEnvironment[1])); + const dp::math::Quatf zRot(dp::math::Vec3f(0.0f, 0.0f, 1.0f), dp::math::degToRad(m_rotationEnvironment[2])); + m_lightsGUI[0].orientation = xRot * yRot * zRot; + + const dp::math::Quatf xRotInv(dp::math::Vec3f(1.0f, 0.0f, 0.0f), dp::math::degToRad(-m_rotationEnvironment[0])); + const dp::math::Quatf yRotInv(dp::math::Vec3f(0.0f, 1.0f, 0.0f), dp::math::degToRad(-m_rotationEnvironment[1])); + const dp::math::Quatf zRotInv(dp::math::Vec3f(0.0f, 0.0f, 1.0f), dp::math::degToRad(-m_rotationEnvironment[2])); + m_lightsGUI[0].orientationInv = zRotInv * yRotInv * xRotInv; + + m_lightsGUI[0].matrix = dp::math::Mat44f(m_lightsGUI[0].orientation, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); + m_lightsGUI[0].matrixInv = dp::math::Mat44f(m_lightsGUI[0].orientationInv, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); + + m_raytracer->updateLight(0, m_lightsGUI[0]); + refresh = true; + } + } + if (ImGui::Combo("Camera", (int*) &m_typeLens, "Pinhole\0Fisheye\0Spherical\0\0")) + { + m_state.typeLens = m_typeLens; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::Button("Match Resolution")) + { + // Match the rendering resolution to the current client window size. + m_resolution.x = std::max(1, m_width); + m_resolution.y = std::max(1, m_height); + + m_camera.setResolution(m_resolution.x, m_resolution.y); + m_rasterizer->setResolution(m_resolution.x, m_resolution.y); + m_state.resolution = m_resolution; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::InputInt2("Resolution", &m_resolution.x, ImGuiInputTextFlags_EnterReturnsTrue)) // This requires RETURN to apply a new value. + { + m_resolution.x = std::max(1, m_resolution.x); + m_resolution.y = std::max(1, m_resolution.y); + + m_camera.setResolution(m_resolution.x, m_resolution.y); + m_rasterizer->setResolution(m_resolution.x, m_resolution.y); + m_state.resolution = m_resolution; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::InputInt("SamplesSqrt", &m_samplesSqrt, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue)) + { + m_samplesSqrt = clamp(m_samplesSqrt, 1, 256); // Samples per pixel are squares in the range [1, 65536]. + m_state.samplesSqrt = m_samplesSqrt; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragInt2("Path Lengths", &m_pathLengths.x, 1.0f, 0, 100)) + { + m_state.pathLengths = m_pathLengths; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragInt("Random Walk Length", &m_walkLength, 1.0f, 1, 100)) + { + m_state.walkLength = m_walkLength; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragFloat("Scene Epsilon", &m_epsilonFactor, 1.0f, 0.0f, 10000.0f)) + { + m_state.epsilonFactor = m_epsilonFactor; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragInt("SDF Iterations", &m_sdfIterations, 1.0f, 1, 1000)) + { + m_state.sdfIterations = clamp(m_sdfIterations, 1, 1000); // Keyboard input can be outside the drag range. + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragFloat("SDF Epsilon", &m_sdfEpsilon, 0.000001f, 0.000001f, 0.1f, "%.6f")) + { + m_state.sdfEpsilon = clamp(m_sdfEpsilon, 0.000001f, 0.1f); // Keyboard input can be outside the drag range. + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragFloat("SDF Offset", &m_sdfOffset, 0.1f, 0.0f, 1000.0f)) + { + m_state.sdfOffset = clamp(m_sdfOffset, 0.0f, 1000.0f); // Keyboard input can be outside the drag range. + m_raytracer->updateState(m_state); + refresh = true; + } +#if USE_TIME_VIEW + if (ImGui::DragFloat("Clock Factor", &m_clockFactor, 1.0f, 0.0f, 1000000.0f, "%.0f")) + { + m_state.clockFactor = m_clockFactor; + m_raytracer->updateState(m_state); + refresh = true; + } +#endif + } + +#if !USE_TIME_VIEW + if (ImGui::CollapsingHeader("Tonemapper")) + { + bool changed = false; + if (ImGui::ColorEdit3("Balance", (float*) &m_tonemapperGUI.colorBalance)) + { + changed = true; + } + if (ImGui::DragFloat("Gamma", &m_tonemapperGUI.gamma, 0.01f, 0.01f, 10.0f)) // Must not get 0.0f + { + changed = true; + } + if (ImGui::DragFloat("White Point", &m_tonemapperGUI.whitePoint, 0.01f, 0.01f, 255.0f, "%.2f", 2.0f)) // Must not get 0.0f + { + changed = true; + } + if (ImGui::DragFloat("Burn Lights", &m_tonemapperGUI.burnHighlights, 0.01f, 0.0f, 10.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("Crush Blacks", &m_tonemapperGUI.crushBlacks, 0.01f, 0.0f, 1.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("Saturation", &m_tonemapperGUI.saturation, 0.01f, 0.0f, 10.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("Brightness", &m_tonemapperGUI.brightness, 0.01f, 0.0f, 100.0f, "%.2f", 2.0f)) + { + changed = true; + } + if (changed) + { + m_rasterizer->setTonemapper(m_tonemapperGUI); // This doesn't need a refresh. + } + } +#endif // !USE_TIME_VIEW + + if (ImGui::CollapsingHeader("Materials")) + { + // My material reference names are unique identifiers. + std::string labelCombo = m_materialsMDL[m_indexMaterialGUI]->getReference(); + + if (ImGui::BeginCombo("Reference", labelCombo.c_str())) + { + // add selectable materials to the combo box + for (size_t i = 0; i < m_materialsMDL.size(); ++i) + { + bool isSelected = (i == m_indexMaterialGUI); + + std::string label = m_materialsMDL[i]->getReference(); + + if (ImGui::Selectable(label.c_str(), isSelected)) + { + m_indexMaterialGUI = i; + } + if (isSelected) + { + ImGui::SetItemDefaultFocus(); + } + }; + + ImGui::EndCombo(); + } + + MaterialMDL* material = m_materialsMDL[m_indexMaterialGUI]; + + const char *group_name = nullptr; + + bool changed = false; + + int id = 0; + for (std::list::iterator it = material->m_params.begin(), end = material->params().end(); it != end; ++it, ++id) + { + Param_info& param = *it; + + // Ensure unique ID even for parameters with same display names. + ImGui::PushID(id); + + // Group name changed? Start new group with new header. + if ((!param.group_name() != !group_name) || + (param.group_name() && (!group_name || strcmp(group_name, param.group_name()) != 0))) + { + ImGui::Separator(); + + if (param.group_name() != nullptr) + { + ImGui::Text("%s", param.group_name()); + } + + group_name = param.group_name(); + } + + // Choose proper edit control depending on the parameter kind + switch (param.kind()) + { + case Param_info::PK_FLOAT: + changed |= ImGui::SliderFloat( param.display_name(), ¶m.data(), param.range_min(), param.range_max()); + break; + case Param_info::PK_FLOAT2: + changed |= ImGui::SliderFloat2(param.display_name(), ¶m.data(), param.range_min(), param.range_max()); + break; + case Param_info::PK_FLOAT3: + changed |= ImGui::SliderFloat3( param.display_name(), ¶m.data(), param.range_min(), param.range_max()); + break; + case Param_info::PK_COLOR: + changed |= ImGui::ColorEdit3(param.display_name(), ¶m.data()); + break; + case Param_info::PK_BOOL: + changed |= ImGui::Checkbox(param.display_name(), ¶m.data()); + break; + case Param_info::PK_INT: + changed |= ImGui::SliderInt(param.display_name(), ¶m.data(), int(param.range_min()), int(param.range_max())); + break; + case Param_info::PK_ARRAY: + { + ImGui::Text("%s", param.display_name()); + ImGui::Indent(16.0f); + + char* ptr = ¶m.data(); + + for (mi::Size i = 0, n = param.array_size(); i < n; ++i) + { + std::string idx_str = std::to_string(i); + + switch (param.array_elem_kind()) + { + case Param_info::PK_FLOAT: + changed |= ImGui::SliderFloat(idx_str.c_str(), reinterpret_cast(ptr), param.range_min(), param.range_max()); + break; + case Param_info::PK_FLOAT2: + changed |= ImGui::SliderFloat2(idx_str.c_str(),reinterpret_cast(ptr), param.range_min(), param.range_max()); + break; + case Param_info::PK_FLOAT3: + changed |= ImGui::SliderFloat3(idx_str.c_str(), reinterpret_cast(ptr), param.range_min(), param.range_max()); + break; + case Param_info::PK_COLOR: + changed |= ImGui::ColorEdit3(idx_str.c_str(), reinterpret_cast(ptr)); + break; + case Param_info::PK_BOOL: + changed |= ImGui::Checkbox(param.display_name(), reinterpret_cast(ptr)); + break; + case Param_info::PK_INT: + changed |= ImGui::SliderInt(param.display_name(), reinterpret_cast(ptr), int(param.range_min()), int(param.range_max())); + break; + default: + std::cerr << "ERROR: guiWindow() Material parameter " << param.display_name() << " array element " << idx_str << " type invalid or unhandled.\n"; + } + ptr += param.array_pitch(); + } + ImGui::Unindent(16.0f); + } + break; + case Param_info::PK_ENUM: + { + int value = param.data(); + std::string curr_value; + + const Enum_type_info* info = param.enum_info(); + for (size_t i = 0, n = info->values.size(); i < n; ++i) + { + if (info->values[i].value == value) + { + curr_value = info->values[i].name; + break; + } + } + + if (ImGui::BeginCombo(param.display_name(), curr_value.c_str())) + { + for (size_t i = 0, n = info->values.size(); i < n; ++i) + { + const std::string& name = info->values[i].name; + + bool is_selected = (curr_value == name); + + if (ImGui::Selectable(info->values[i].name.c_str(), is_selected)) + { + param.data() = info->values[i].value; + changed = true; + } + if (is_selected) + { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + } + break; + + case Param_info::PK_STRING: + case Param_info::PK_TEXTURE: + case Param_info::PK_LIGHT_PROFILE: + case Param_info::PK_BSDF_MEASUREMENT: + // Editing these parameter types is currently not supported by this example + break; + + default: + break; + } + + ImGui::PopID(); + } + + if (changed) + { + m_raytracer->updateMaterial(m_indexMaterialGUI, material); + refresh = true; + } + } + + ImGui::PopItemWidth(); + + ImGui::End(); + + if (refresh) + { + restartRendering(); + } +} + +void Application::guiRenderingIndicator(const bool isRendering) +{ + // NVIDIA Green when rendering is complete. + float r = 0.462745f; + float g = 0.72549f; + float b = 0.0f; + + if (isRendering) + { + // Neutral grey while rendering. + r = 1.0f; + g = 1.0f; + b = 1.0f; + } + + ImGuiStyle& style = ImGui::GetStyle(); + + // Use the GUI window title bar color as rendering indicator. Green when rendering is completed. + style.Colors[ImGuiCol_TitleBg] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); +} + + +bool Application::loadSystemDescription(const std::string& filename) +{ + Parser parser; + + if (!parser.load(filename)) + { + std::cerr << "ERROR: loadSystemDescription() failed in loadString(" << filename << ")\n"; + return false; + } + + ParserTokenType tokenType; + std::string token; + + while ((tokenType = parser.getNextToken(token)) != PTT_EOF) + { + if (tokenType == PTT_UNKNOWN) + { + std::cerr << "ERROR: loadSystemDescription() " << filename << " (" << parser.getLine() << "): Unknown token type.\n"; + MY_ASSERT(!"Unknown token type."); + return false; + } + + if (tokenType == PTT_ID) + { + //if (token == "strategy") + //{ + // // Ignored in this renderer. Behaves like RS_INTERACTIVE_MULTI_GPU_LOCAL_COPY, but single-GPU is optimized to not invoke the compositor. + // tokenType = parser.getNextToken(token); + // MY_ASSERT(tokenType == PTT_VAL); + // const int strategy = atoi(token.c_str()); + + // std::cerr << "WARNING: loadSystemDescription() renderer strategy " << strategy << " ignored.\n"; + //} + //else + if (token == "devicesMask") // FIXME Kept the old name to be able to mix and match apps. + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_maskDevices = atoi(token.c_str()); + } + else if (token == "arenaSize") // In mebi-bytes. + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_sizeArena = std::max(1, atoi(token.c_str())); + } + else if (token == "interop") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_interop = atoi(token.c_str()); + if (m_interop < 0 || 2 < m_interop) + { + std::cerr << "WARNING: loadSystemDescription() Invalid interop value " << m_interop << ", using interop 0 (host).\n"; + m_interop = 0; + } + } + else if (token == "peerToPeer") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_peerToPeer = atoi(token.c_str()); + } + else if (token == "mouseSpeedRatio") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_mouseSpeedRatio = clamp((float) atof(token.c_str()), 0.1f, 1000.0f); + } + else if (token == "present") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_present = (atoi(token.c_str()) != 0); + } + else if (token == "resolution") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_resolution.x = std::max(1, atoi(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_resolution.y = std::max(1, atoi(token.c_str())); + } + else if (token == "tileSize") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tileSize.x = std::max(1, atoi(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tileSize.y = std::max(1, atoi(token.c_str())); + + // Make sure the values are power-of-two. + if (m_tileSize.x & (m_tileSize.x - 1)) + { + std::cerr << "ERROR: loadSystemDescription() tileSize.x = " << m_tileSize.x << " is not power-of-two, using 8.\n"; + m_tileSize.x = 8; + } + if (m_tileSize.y & (m_tileSize.y - 1)) + { + std::cerr << "ERROR: loadSystemDescription() tileSize.y = " << m_tileSize.y << " is not power-of-two, using 8.\n"; + m_tileSize.y = 8; + } + } + else if (token == "samplesSqrt") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_samplesSqrt = std::max(1, atoi(token.c_str())); // spp = m_samplesSqrt * m_samplesSqrt + } + else if (token == "clockFactor") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_clockFactor = (float) atof(token.c_str()); + } + else if (token == "pathLengths") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_pathLengths.x = atoi(token.c_str()); // min path length before Russian Roulette kicks in + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_pathLengths.y = atoi(token.c_str()); // max path length + } + else if (token == "walkLength") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_walkLength = max(1, atoi(token.c_str())); // max volume scattering random walk lengths, default and minimum 1. + } + else if (token == "epsilonFactor") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_epsilonFactor = (float) atof(token.c_str()); + } + else if (token == "sdfIterations") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_sdfIterations = clamp(atoi(token.c_str()), 1, 1000); // Maximum SDF sphere tracing iterations. Default 100. + } + else if (token == "sdfEpsilon") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_sdfEpsilon = clamp((float) atof(token.c_str()), 0.000001f, 0.1f); + } + else if (token == "sdfOffset") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_sdfOffset = clamp((float) atof(token.c_str()), 0.0f, 1000.0f); + } + // The lens shader, center of interest, and camera values can be set in either the system or scene definition file. + // The scene defintion will take precedence because it's loaded afterwards. + else if (token == "lensShader") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_typeLens = static_cast(atoi(token.c_str())); + if (m_typeLens < TYPE_LENS_PINHOLE || TYPE_LENS_SPHERE < m_typeLens) + { + m_typeLens = TYPE_LENS_PINHOLE; + } + } + else if (token == "center") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float z = (float) atof(token.c_str()); + m_camera.m_center = make_float3(x, y, z); + m_camera.markDirty(); + } + else if (token == "camera") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_phi = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_theta = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_fov = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_distance = (float) atof(token.c_str()); + m_camera.markDirty(); + } + else if (token == "prefixScreenshot") + { + tokenType = parser.getNextToken(token); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + m_prefixScreenshot = token; + } + else if (token == "searchPath") // MDL search paths for *.mdl files and their ressources. + { + tokenType = parser.getNextToken(token); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + addSearchPath(token); + } + // All Tonemapper values can be set in either the system or scene definition file. + // The scene defintion will take precedence because it's loaded afterwards. + else if (token == "gamma") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.gamma = (float) atof(token.c_str()); + } + else if (token == "colorBalance") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[2] = (float) atof(token.c_str()); + } + else if (token == "whitePoint") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.whitePoint = (float) atof(token.c_str()); + } + else if (token == "burnHighlights") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.burnHighlights = (float) atof(token.c_str()); + } + else if (token == "crushBlacks") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.crushBlacks = (float) atof(token.c_str()); + } + else if (token == "saturation") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.saturation = (float) atof(token.c_str()); + } + else if (token == "brightness") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.brightness = (float) atof(token.c_str()); + } + else + { + std::cerr << "WARNING: loadSystemDescription() Unknown system option name: " << token << '\n'; + } + } + } + return true; +} + + +bool Application::saveSystemDescription() +{ + std::ostringstream description; + + description << "strategy " << m_strategy << '\n'; // Ignored in this renderer. + description << "devicesMask " << m_maskDevices << '\n'; + description << "arenaSize " << m_sizeArena << '\n'; + description << "interop " << m_interop << '\n'; + description << "present " << ((m_present) ? "1" : "0") << '\n'; + description << "resolution " << m_resolution.x << " " << m_resolution.y << '\n'; + description << "tileSize " << m_tileSize.x << " " << m_tileSize.y << '\n'; + description << "samplesSqrt " << m_samplesSqrt << '\n'; + description << "clockFactor " << m_clockFactor << '\n'; + description << "pathLengths " << m_pathLengths.x << " " << m_pathLengths.y << '\n'; + description << "epsilonFactor " << m_epsilonFactor << '\n'; + + description << "lensShader " << m_typeLens << '\n'; + description << "center " << m_camera.m_center.x << " " << m_camera.m_center.y << " " << m_camera.m_center.z << '\n'; + description << "camera " << m_camera.m_phi << " " << m_camera.m_theta << " " << m_camera.m_fov << " " << m_camera.m_distance << '\n'; + if (!m_prefixScreenshot.empty()) + { + description << "prefixScreenshot \"" << m_prefixScreenshot << "\"\n"; + } + + description << "gamma " << m_tonemapperGUI.gamma << '\n'; + description << "colorBalance " << m_tonemapperGUI.colorBalance[0] << " " << m_tonemapperGUI.colorBalance[1] << " " << m_tonemapperGUI.colorBalance[2] << '\n'; + description << "whitePoint " << m_tonemapperGUI.whitePoint << '\n'; + description << "burnHighlights " << m_tonemapperGUI.burnHighlights << '\n'; + description << "crushBlacks " << m_tonemapperGUI.crushBlacks << '\n'; + description << "saturation " << m_tonemapperGUI.saturation << '\n'; + description << "brightness " << m_tonemapperGUI.brightness << '\n'; + + description << "sdfIterations " << m_sdfIterations << '\n'; + description << "sdfEpsilon " << m_sdfEpsilon << '\n'; + description << "sdfOffset " << m_sdfOffset << '\n'; + + const std::string filename = std::string("system_MDL_sdf_") + getDateTime() + std::string(".txt"); + const bool success = saveString(filename, description.str()); + if (success) + { + std::cout << filename << '\n'; // Print out the filename to indicate success. + } + return success; +} + +int Application::findMaterial(const std::string& reference) +{ + int indexMaterial = -1; // -1 means not found. This is a critical error! + + // Check if the referenced material is already present inside the active materials vector. + std::map::const_iterator itm = m_mapReferenceToMaterialIndex.find(reference); + if (itm != m_mapReferenceToMaterialIndex.end()) + { + indexMaterial = itm->second; + } + else + { + // Check if the referenced material is present inside the material declarations. + std::map::const_iterator itd = m_mapReferenceToDeclaration.find(reference); + if (itd != m_mapReferenceToDeclaration.end()) + { + // Only material declarations which are referenced inside the scene will be loaded later. + indexMaterial = static_cast(m_materialsMDL.size()); + + m_mapReferenceToMaterialIndex[reference] = indexMaterial; + + MaterialMDL *materialMDL = new MaterialMDL(itd->second); + + m_materialsMDL.push_back(materialMDL); + } + else if (reference != std::string("default")) // Prevent infinite recursion. + { + std::cerr << "WARNING: findMaterial() No material found for " << reference << ". Trying \"default\".\n"; + + indexMaterial = findMaterial(std::string("default")); + } + else + { + std::cerr << "FATAL: findMaterial() No material found! Invalid scene description!\n"; + } + } + + return indexMaterial; +} + +void Application::appendInstance(std::shared_ptr& group, + std::shared_ptr geometry, + const dp::math::Mat44f& matrix, + const int indexMaterial, + const int indexLight) +{ + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + const float trafo[12] = + { + matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], + matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1], + matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2] + }; + + MY_ASSERT(matrix[0][3] == 0.0f && + matrix[1][3] == 0.0f && + matrix[2][3] == 0.0f && + matrix[3][3] == 1.0f); + + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(geometry); + instance->setMaterial(indexMaterial); + instance->setLight(indexLight); + + group->addChild(instance); +} + + +bool Application::loadSceneDescription(const std::string& filename) +{ + Parser parser; + + if (!parser.load(filename)) + { + std::cerr << "ERROR: loadSceneDescription() failed in loadString(" << filename << ")\n"; + return false; + } + + ParserTokenType tokenType; + std::string token; + + // Reusing some math routines from the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + // Note that matrices in the nvpro-pipeline are defined row-major but are multiplied from the right, + // which means the order of transformations is simply from left to right matrix, means first matrix is applied first, + // but puts the translation into the last row elements (12 to 14). + + std::stack stackSceneState; + + SceneState cur; + + cur.reset(); // Reset state to identity transforms and default material parameters. + + while ((tokenType = parser.getNextToken(token)) != PTT_EOF) + { + if (tokenType == PTT_UNKNOWN) + { + std::cerr << "ERROR: loadSceneDescription() " << filename << " (" << parser.getLine() << "): Unknown token type.\n"; + MY_ASSERT(!"Unknown token type."); + return false; + } + + if (tokenType == PTT_ID) + { + std::map::const_iterator itKeyword = m_mapKeywordScene.find(token); + if (itKeyword == m_mapKeywordScene.end()) + { + std::cerr << "WARNING: loadSceneDescription() Unknown token " << token << " ignored.\n"; + // MY_ASSERT(!"loadSceneDescription() Unknown token ignored."); + continue; // Just keep getting the next token until a known keyword is found. + } + + const KeywordScene keyword = itKeyword->second; + + switch (keyword) + { + // Lens shader, center, camera and tonemapper values + // can be set in system and scene description. Latter takes precendence. + // These are global states not affected by push/pop. Last one wins. + case KS_LENS_SHADER: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_typeLens = static_cast(atoi(token.c_str())); + if (m_typeLens < TYPE_LENS_PINHOLE || TYPE_LENS_SPHERE < m_typeLens) + { + m_typeLens = TYPE_LENS_PINHOLE; + } + break; + + case KS_CENTER: + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float z = (float) atof(token.c_str()); + m_camera.m_center = make_float3(x, y, z); + m_camera.markDirty(); + break; + } + + case KS_CAMERA: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_phi = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_theta = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_fov = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_distance = (float) atof(token.c_str()); + m_camera.markDirty(); + break; + + case KS_GAMMA: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.gamma = (float) atof(token.c_str()); + break; + + case KS_COLOR_BALANCE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[2] = (float) atof(token.c_str()); + break; + + case KS_WHITE_POINT: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.whitePoint = (float) atof(token.c_str()); + break; + + case KS_BURN_HIGHLIGHTS: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.burnHighlights = (float) atof(token.c_str()); + break; + + case KS_CRUSH_BLACKS: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.crushBlacks = (float) atof(token.c_str()); + break; + + case KS_SATURATION: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.saturation = (float) atof(token.c_str()); + break; + + case KS_BRIGHTNESS: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.brightness = (float) atof(token.c_str()); + break; + + case KS_EMISSION: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + cur.colorEmission.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + cur.colorEmission.y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + cur.colorEmission.z = (float) atof(token.c_str()); + break; + + case KS_EMISSION_MULTIPLIER: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + cur.multiplierEmission = (float) atof(token.c_str()); + break; + + case KS_EMISSION_PROFILE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + cur.nameProfile = token; + break; + + case KS_EMISSION_TEXTURE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + cur.nameEmission = token; + break; + + case KS_SPOT_ANGLE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + cur.spotAngle = (float) atof(token.c_str()); + cur.spotAngle = clamp(cur.spotAngle, 0.0f, 180.0f); + break; + + case KS_SPOT_EXPONENT: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + cur.spotExponent = (float) atof(token.c_str()); + break; + + case KS_MDL: + { + MaterialDeclaration decl; + + tokenType = parser.getNextToken(decl.nameReference); // Internal material reference name. If there are duplicates the first name wins. + //MY_ASSERT(tokenType == PTT_ID); // Allow any type of identifier, including strings and numbers. + tokenType = parser.getNextToken(decl.nameMaterial); // MDL material name + MY_ASSERT(tokenType == PTT_ID); // Must be a standard identifier. MDL doesn't allow other names. + tokenType = parser.getNextToken(decl.pathMaterial); // Path to *.mdl file containing the above material name (relative to search paths). + MY_ASSERT(tokenType == PTT_STRING); // Must be a string given in quotation marks. + + convertPath(decl.pathMaterial); // Convert slashes or backslashes to the resp. OS format. + + // Track the declared materials inside a map. If there are duplicate reference names, the first one wins. + // Later findMaterial() will put only referenced materials into m_materialsMDL vector and only those are finally loaded. + std::map::const_iterator it = m_mapReferenceToDeclaration.find(decl.nameReference); + if (it == m_mapReferenceToDeclaration.end()) + { + m_mapReferenceToDeclaration[decl.nameReference] = decl; + } + else + { + // This is a benign error inside the scene description. The first reference name wins when there are duplicates. + std::cerr << "WARNING: loadSceneDescription() duplicate material reference name " << decl.nameReference << " ignored.\n"; + } + } + break; + + case KS_IDENTITY: + cur.matrix = dp::math::cIdentity44f; + cur.matrixInv = dp::math::cIdentity44f; + cur.orientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); + cur.orientationInv = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); + break; + + case KS_PUSH: + stackSceneState.push(cur); + break; + + case KS_POP: + if (!stackSceneState.empty()) + { + cur = stackSceneState.top(); + stackSceneState.pop(); + } + else + { + std::cerr << "ERROR: loadSceneDescription() pop on empty stack. Resetting SceneState to defaults.\n"; + cur.reset(); + } + break; + + case KS_ROTATE: + { + dp::math::Vec3f axis; + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[2] = (float) atof(token.c_str()); + axis.normalize(); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float angle = dp::math::degToRad((float) atof(token.c_str())); + + const dp::math::Quatf rotation(axis, angle); + cur.orientation *= rotation; + + // Inverse. Opposite order of multiplications. + const dp::math::Quatf rotationInv(axis, -angle); + cur.orientationInv = rotationInv * cur.orientationInv; + + const dp::math::Mat44f matrix(rotation, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); // Zero translation to get a Mat44f back. + cur.matrix *= matrix; // DEBUG No need for the local matrix variable. + + // Inverse. Opposite order of matrix multiplications to make M * M^-1 = I. + const dp::math::Mat44f matrixInv(rotationInv, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); // Zero translation to get a Mat44f back. + cur.matrixInv = matrixInv * cur.matrixInv; // DEBUG No need for the local matrixInv variable. + } + break; + + case KS_SCALE: + { + dp::math::Mat44f scaling(dp::math::cIdentity44f); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[0][0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[1][1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[2][2] = (float) atof(token.c_str()); + + cur.matrix *= scaling; + + // Inverse. // DEBUG Requires scalings to not contain zeros. + scaling[0][0] = 1.0f / scaling[0][0]; + scaling[1][1] = 1.0f / scaling[1][1]; + scaling[2][2] = 1.0f / scaling[2][2]; + + cur.matrixInv = scaling * cur.matrixInv; + } + break; + + case KS_TRANSLATE: + { + dp::math::Mat44f translation(dp::math::cIdentity44f); + + // Translation is in the third row in dp::math::Mat44f. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][2] = (float) atof(token.c_str()); + + cur.matrix *= translation; + + translation[3][0] = -translation[3][0]; + translation[3][1] = -translation[3][1]; + translation[3][2] = -translation[3][2]; + + cur.matrixInv = translation * cur.matrixInv; + } + break; + + case KS_MODEL: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_ID); + + if (token == "plane") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int upAxis = atoi(token.c_str()); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "plane_" << tessU << "_" << tessV << "_" << upAxis; + + std::shared_ptr geometry; + + std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + if (itg == m_mapGeometries.end()) + { + m_mapGeometries[keyGeometry.str()] = m_idGeometry; // PERF Equal to static_cast(m_geometries.size()); + + geometry = std::make_shared(m_idGeometry++); + geometry->createPlane(tessU, tessV, upAxis); + + m_geometries.push_back(geometry); + } + else + { + geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + appendInstance(m_scene, geometry, cur.matrix, indexMaterial, -1); + } + else if (token == "box") + { + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + // FIXME Implement tessellation. Must be a single value to get even distributions across edges. + std::string keyGeometry("box_1_1"); + + std::shared_ptr geometry; + + std::map::const_iterator itg = m_mapGeometries.find(keyGeometry); + if (itg == m_mapGeometries.end()) + { + m_mapGeometries[keyGeometry] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createBox(); + + m_geometries.push_back(geometry); + } + else + { + geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + appendInstance(m_scene, geometry, cur.matrix, indexMaterial, -1); + } + else if (token == "sphere") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + // Theta is in the range [0.0f, 1.0f] and 1.0f means closed sphere, smaller values open the noth pole. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float theta = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "sphere_" << tessU << "_" << tessV << "_" << theta; + + std::shared_ptr geometry; + + std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + if (itg == m_mapGeometries.end()) + { + m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createSphere(tessU, tessV, 1.0f, theta * M_PIf); + + m_geometries.push_back(geometry); + } + else + { + geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + appendInstance(m_scene, geometry, cur.matrix, indexMaterial, -1); + } + else if (token == "torus") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float innerRadius = float(atof(token.c_str())); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float outerRadius = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "torus_" << tessU << "_" << tessV << "_" << innerRadius << "_" << outerRadius; + + std::shared_ptr geometry; + + std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + if (itg == m_mapGeometries.end()) + { + m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createTorus(tessU, tessV, innerRadius, outerRadius); + + m_geometries.push_back(geometry); + } + else + { + geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + appendInstance(m_scene, geometry, cur.matrix, indexMaterial, -1); + } + else if (token == "hair") // model hair scale material filename + { + // This scales the thickness defined inside the hair files. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float scale = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::string filename; + tokenType = parser.getNextToken(filename); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(filename); + + std::ostringstream keyGeometry; + keyGeometry << "hair_" << scale << "_" << filename; + + std::shared_ptr geometry; + + std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + if (itg == m_mapGeometries.end()) + { + m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createHair(filename, scale); + + m_geometries.push_back(geometry); + } + else + { + geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + appendInstance(m_scene, geometry, cur.matrix, indexMaterial, -1); + } + else if (token == "assimp") + { + std::string filenameModel; + tokenType = parser.getNextToken(filenameModel); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(filenameModel); + + std::shared_ptr model = createASSIMP(filenameModel); + + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + const float trafo[12] = + { + cur.matrix[0][0], cur.matrix[1][0], cur.matrix[2][0], cur.matrix[3][0], + cur.matrix[0][1], cur.matrix[1][1], cur.matrix[2][1], cur.matrix[3][1], + cur.matrix[0][2], cur.matrix[1][2], cur.matrix[2][2], cur.matrix[3][2] + }; + + MY_ASSERT(cur.matrix[0][3] == 0.0f && + cur.matrix[1][3] == 0.0f && + cur.matrix[2][3] == 0.0f && + cur.matrix[3][3] == 1.0f); + + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + instance->setTransform(trafo); + instance->setChild(model); + + m_scene->addChild(instance); + } + else if (token == "sdf") + { + // Scene Description format: model sdf xmin ymin zmin xmax ymax zmax lipschitz width height depth "filename" material_reference + + // AABB extents. + float3 minimum = make_float3(-1.0f); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + minimum.x = float(atof(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + minimum.y = float(atof(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + minimum.z = float(atof(token.c_str())); + + float3 maximum = make_float3(1.0f); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + maximum.x = float(atof(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + maximum.y = float(atof(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + maximum.z = float(atof(token.c_str())); + + float lipschitz = 1.0f; + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + lipschitz = float(atof(token.c_str())); + + // Texture extents. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int width = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int height = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int depth = atoi(token.c_str()); + + // SDF 3D texture filename. + std::string filenameSDF; + tokenType = parser.getNextToken(filenameSDF); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(filenameSDF); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + if (!filenameSDF.empty()) + { + std::map::const_iterator it = m_mapPictures.find(filenameSDF); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + + picture->loadSDF(width, height, depth, filenameSDF, IMAGE_FLAG_3D | IMAGE_FLAG_SDF); + + m_mapPictures[filenameSDF] = picture; + } + else + { + MY_ASSERT((it->second->getFlags() & IMAGE_FLAG_ENV) == 0); // Cannot have the same texture for env and rect. There is only one CDF. + it->second->addFlags(IMAGE_FLAG_POINT); + } + } + + std::ostringstream keyGeometry; + keyGeometry << "sdf_" << filenameSDF; // The filename alone should be unique enough. + + std::shared_ptr geometry; + + std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + if (itg == m_mapGeometries.end()) + { + m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + + // DAR FIXME Check the boolean return value once the data is loaded from file. + geometry->createSDF(minimum, maximum, lipschitz, width, height, depth, filenameSDF); + + m_geometries.push_back(geometry); + } + else + { + geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); // Reuse existing geometry. + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + appendInstance(m_scene, geometry, cur.matrix, indexMaterial, -1); + } + else + { + std::cerr << "WARNING: loadSceneDescription() unknown model type "<< token << '\n'; + } + break; + + case KS_LIGHT: + { + // One of the predefined light types: env, point, spot, ies + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_ID); + + LightGUI lightGUI; + + // Transfer all values from the current scene state. The light type defines which fields get used. + // Transformation is taken from the current scene state. Only orientation is used for spherical environment lights. + lightGUI.matrix = cur.matrix; + lightGUI.matrixInv = cur.matrixInv; + lightGUI.orientation = cur.orientation; + lightGUI.orientationInv = cur.orientationInv; + lightGUI.idGeometry = ~0u; // Unused. Not a mesh light. + lightGUI.idMaterial = -1; // Not a mesh light. + lightGUI.idObject = -1; // Unused when not a mesh light. + lightGUI.nameEmission = cur.nameEmission; + lightGUI.nameProfile = cur.nameProfile; // Note that environment lights do not support light profiles. + lightGUI.colorEmission = cur.colorEmission; + lightGUI.multiplierEmission = cur.multiplierEmission; + lightGUI.spotAngle = cur.spotAngle; + lightGUI.spotExponent = cur.spotExponent; + + // FIXME Use the m_mapKeywords and a switch here. + if (token == "env") // Constant or spherical texture map environment light. + { + // There can be only one environment light and it must be the first light definition. + if (m_lightsGUI.size() == 0) + { + // FIXME This type selection would need to be handled via different env keywords for more than two environment lights (e.g. hemisphere). + lightGUI.typeLight = (cur.nameEmission.empty()) ? TYPE_LIGHT_ENV_CONST : TYPE_LIGHT_ENV_SPHERE; + lightGUI.area = 4.0f * M_PIf; // Spherical environment light. Unused. + + m_lightsGUI.push_back(lightGUI); + + if (!cur.nameEmission.empty()) + { + std::map::const_iterator it = m_mapPictures.find(cur.nameEmission); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + + picture->load(cur.nameEmission, IMAGE_FLAG_2D | IMAGE_FLAG_ENV); + + m_mapPictures[cur.nameEmission] = picture; + } + else + { + MY_ASSERT((it->second->getFlags() & IMAGE_FLAG_RECT) == 0); // Cannot have the same texture for env and rect. There is only one CDF. + it->second->addFlags(IMAGE_FLAG_ENV); + } + } + } + else + { + std::cerr << "ERROR: loadSceneDescription() Environment lights must be specified first.\n"; + } + } + else if (token == "point") + { + lightGUI.typeLight = TYPE_LIGHT_POINT; + lightGUI.area = 1.0f; // Unused for singular lights. + + const int indexLight = static_cast(m_lightsGUI.size()); + + m_lightsGUI.push_back(lightGUI); + + if (!cur.nameEmission.empty()) + { + std::map::const_iterator it = m_mapPictures.find(cur.nameEmission); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + + picture->load(cur.nameEmission, IMAGE_FLAG_2D | IMAGE_FLAG_POINT); + + m_mapPictures[cur.nameEmission] = picture; + } + else + { + MY_ASSERT((it->second->getFlags() & IMAGE_FLAG_ENV) == 0); // Cannot have the same texture for env and rect. There is only one CDF. + it->second->addFlags(IMAGE_FLAG_POINT); + } + } + // No geometry. A singular light cannot be hit implicitly. + } + else if (token == "spot") // Spot light singular light type. Emission texture is projected over the open spread angle. + { + lightGUI.typeLight = TYPE_LIGHT_SPOT; + lightGUI.area = 1.0f; // Unused for singular lights. + + const int indexLight = static_cast(m_lightsGUI.size()); + + m_lightsGUI.push_back(lightGUI); + + if (!cur.nameEmission.empty()) + { + std::map::const_iterator it = m_mapPictures.find(cur.nameEmission); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + + picture->load(cur.nameEmission, IMAGE_FLAG_2D | IMAGE_FLAG_SPOT); + + m_mapPictures[cur.nameEmission] = picture; + } + else + { + MY_ASSERT((it->second->getFlags() & IMAGE_FLAG_ENV) == 0); // Cannot have the same texture for env and rect. There is only one CDF. + it->second->addFlags(IMAGE_FLAG_SPOT); + } + } + // No geometry. A singular light cannot be hit implicitly. + } + else if (token == "ies") + { + lightGUI.typeLight = TYPE_LIGHT_IES; + lightGUI.area = 1.0f; // Unused for singular lights. + + lightGUI.area = 1.0f; // Unused for singular lights. + + const int indexLight = static_cast(m_lightsGUI.size()); + + m_lightsGUI.push_back(lightGUI); + + if (!cur.nameEmission.empty()) + { + std::map::const_iterator it = m_mapPictures.find(cur.nameEmission); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + + picture->load(cur.nameEmission, IMAGE_FLAG_2D | IMAGE_FLAG_POINT); + + m_mapPictures[cur.nameEmission] = picture; + } + else + { + it->second->addFlags(IMAGE_FLAG_POINT); + } + } + + if (!cur.nameProfile.empty()) + { + std::map::const_iterator it = m_mapPictures.find(cur.nameProfile); + if (it == m_mapPictures.end()) + { + // Load the IES profile and convert it to an omnidirectional projection texture for a point light. + LoaderIES loaderIES; + + if (loaderIES.load(cur.nameProfile)) + { + if (loaderIES.parse()) + { + Picture* picture = new Picture(); + + picture->createIES(loaderIES.getData()); // This generates the 2D 1-component luminance float image. + picture->addFlags(IMAGE_FLAG_IES); + + m_mapPictures[cur.nameProfile] = picture; + } + } + } + } + // No geometry. A singular light cannot be hit implicitly. + } + else + { + std::cerr << "WARNING: loadSceneDescription() unknown light type "<< token << '\n'; + } + } + break; + + default: + std::cerr << "ERROR: loadSceneDescription() Unexpected KeywordScene value " << keyword << " ignored.\n"; + MY_ASSERT(!"ERROR: loadSceneDescription() Unexpected KeywordScene value"); + break; + } + } + } + + std::cout << "loadSceneDescription() m_idGroup = " << m_idGroup << ", m_idInstance = " << m_idInstance << ", m_idGeometry = " << m_idGeometry << '\n'; + + return true; +} + + +void Application::createMeshLights() +{ + // Traverse the host scene graph and append light definitions for all meshes with emissive materials. + InstanceData instanceData(~0u, -1, -1, -1); + + float matrix[12]; + + // Set the affine matrix to identity by default. + memset(matrix, 0, sizeof(float) * 12); + matrix[ 0] = 1.0f; + matrix[ 5] = 1.0f; + matrix[10] = 1.0f; + + traverseGraph(m_scene, instanceData, matrix); +} + + +// m = a * b; +static void multiplyMatrix(float* m, const float* a, const float* b) +{ + m[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[ 8]; // + a[3] * 0 + m[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[ 9]; // + a[3] * 0 + m[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10]; // + a[3] * 0 + m[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3]; // * 1 + + m[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[ 8]; // + a[7] * 0 + m[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[ 9]; // + a[7] * 0 + m[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10]; // + a[7] * 0 + m[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7]; // * 1 + + m[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; // + a[11] * 0 + m[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; // + a[11] * 0 + m[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; // + a[11] * 0 + m[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; // * 1 +} + + +// Depth-first traversal of the scene graph to concatenate the trabsformation matrices for mesh lights. +void Application::traverseGraph(std::shared_ptr node, InstanceData& instanceData, float matrix[12]) +{ + switch (node->getType()) + { + case sg::NodeType::NT_GROUP: + { + std::shared_ptr group = std::dynamic_pointer_cast(node); + //std::cout << "Group " << group->getId() << ", " << group->getNumChildren() << '\n'; + + for (size_t i = 0; i < group->getNumChildren(); ++i) + { + traverseGraph(group->getChild(i), instanceData, matrix); + } + } + break; + + case sg::NodeType::NT_INSTANCE: + { + std::shared_ptr instance = std::dynamic_pointer_cast(node); + + // Track the assigned material, light, and object indices. Only the bottom-most instance node matters. + instanceData.idMaterial = instance->getMaterial(); + instanceData.idLight = instance->getLight(); + instanceData.idObject = instance->getId(); + + // Concatenate the transformations along the path. + float trafo[12]; + + multiplyMatrix(trafo, matrix, instance->getTransform()); + + traverseGraph(instance->getChild(), instanceData, trafo); + + // If there was no light ID on the instance, but the geometry is a mesh light, + // assign the new light ID to the bottom-most instance. + if (instance->getLight() == -1 && instanceData.idLight != -1) + { + instance->setLight(instanceData.idLight); + } + instanceData.idLight = -1; // Clear the returned value to not populate it upwards. + } + break; + + case sg::NodeType::NT_TRIANGLES: + { + // Only create a new light definition if the instance is not already holding a valid light index! + // This is the case for rectangle lights which add a light definition and add geometry to the scene. + if (instanceData.idLight == -1) + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + //std::cout << "Triangles " << geometry->getId() << '\n'; + + instanceData.idGeometry = geometry->getId(); + + // Check if the material assigned to this mesh is emissive and + // return the new idLight to the caller to set it inside the instance. + instanceData.idLight = createMeshLight(geometry, instanceData, matrix); + } + } + break; + + case sg::NodeType::NT_CURVES: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + instanceData.idLight = -1; // No support for emissive curves. + } + break; + + case sg::NodeType::NT_SDF: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + instanceData.idLight = -1; // No support for emissive SDFs. + } + break; + } +} + +bool Application::isEmissiveMaterial(const int indexMaterial) const +{ + bool result = false; + + if (0 <= indexMaterial && indexMaterial < static_cast(m_materialsMDL.size())) + { + result = m_raytracer->isEmissiveShader(m_materialsMDL[indexMaterial]->m_indexShader); + } + + return result; +} + + +int Application::createMeshLight(const std::shared_ptr geometry, const InstanceData& instanceData, const float matrix[12]) +{ + int indexLight = -1; + + if (isEmissiveMaterial(instanceData.idMaterial)) + { + LightGUI lightGUI = {}; + + lightGUI.typeLight = TYPE_LIGHT_MESH; + + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + dp::math::Mat44f mat44f( { matrix[0], matrix[4], matrix[ 8], 0.0f, + matrix[1], matrix[5], matrix[ 9], 0.0f, + matrix[2], matrix[6], matrix[10], 0.0f, + matrix[3], matrix[7], matrix[11], 1.0f } ); + + lightGUI.matrix = mat44f; + lightGUI.matrixInv = mat44f; + + if (!lightGUI.matrixInv.invert()) + { + std::cerr << "ERROR: Application::createMeshLight() matrix inversion failed."; + MY_ASSERT(!"ERROR: Application::createMeshLight() matrix inversion failed."); + } + + // HACK PERF It would be involved to decompose the pure rotational part from a generic 4x4 matrix. (dp::math decompose() can do that.) + // But rectangle and arbitrary mesh lights use only the 4x4 matrices above, so no need to set the orientation in this mesh light case at all. + // Just set the orientation quaternions to identity. + lightGUI.orientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); + lightGUI.orientationInv = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); + + lightGUI.idGeometry = geometry->getId(); + lightGUI.idMaterial = instanceData.idMaterial; + lightGUI.idObject = instanceData.idObject; + + // Fill in the areaTriangles and cdfAreas and area. + geometry->calculateLightArea(lightGUI); + + indexLight = static_cast(m_lightsGUI.size()); // Success, set the proper light index. + + m_lightsGUI.push_back(lightGUI); + } + + return indexLight; +} + + +bool Application::loadString(const std::string& filename, std::string& text) +{ + std::ifstream inputStream(filename); + + if (!inputStream) + { + std::cerr << "ERROR: loadString() Failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: loadString() Failed to read file " << filename << '\n'; + return false; + } + + text = data.str(); + return true; +} + +bool Application::saveString(const std::string& filename, const std::string& text) +{ + std::ofstream outputStream(filename); + + if (!outputStream) + { + std::cerr << "ERROR: saveString() Failed to open file " << filename << '\n'; + return false; + } + + outputStream << text; + + if (outputStream.fail()) + { + std::cerr << "ERROR: saveString() Failed to write file " << filename << '\n'; + return false; + } + + return true; +} + +std::string Application::getDateTime() +{ +#if defined(_WIN32) + SYSTEMTIME time; + GetLocalTime(&time); +#elif defined(__linux__) + time_t rawtime; + struct tm* ts; + time(&rawtime); + ts = localtime(&rawtime); +#else + #error "OS not supported." +#endif + + std::ostringstream oss; + +#if defined( _WIN32 ) + oss << time.wYear; + if (time.wMonth < 10) + { + oss << '0'; + } + oss << time.wMonth; + if (time.wDay < 10) + { + oss << '0'; + } + oss << time.wDay << '_'; + if (time.wHour < 10) + { + oss << '0'; + } + oss << time.wHour; + if (time.wMinute < 10) + { + oss << '0'; + } + oss << time.wMinute; + if (time.wSecond < 10) + { + oss << '0'; + } + oss << time.wSecond << '_'; + if (time.wMilliseconds < 100) + { + oss << '0'; + } + if (time.wMilliseconds < 10) + { + oss << '0'; + } + oss << time.wMilliseconds; +#elif defined(__linux__) + oss << ts->tm_year; + if (ts->tm_mon < 10) + { + oss << '0'; + } + oss << ts->tm_mon; + if (ts->tm_mday < 10) + { + oss << '0'; + } + oss << ts->tm_mday << '_'; + if (ts->tm_hour < 10) + { + oss << '0'; + } + oss << ts->tm_hour; + if (ts->tm_min < 10) + { + oss << '0'; + } + oss << ts->tm_min; + if (ts->tm_sec < 10) + { + oss << '0'; + } + oss << ts->tm_sec << '_'; + oss << "000"; // No milliseconds available. +#else + #error "OS not supported." +#endif + + return oss.str(); +} + +static void updateAABB(float3& minimum, float3& maximum, const float3& v) +{ + if (v.x < minimum.x) + { + minimum.x = v.x; + } + else if (maximum.x < v.x) + { + maximum.x = v.x; + } + + if (v.y < minimum.y) + { + minimum.y = v.y; + } + else if (maximum.y < v.y) + { + maximum.y = v.y; + } + + if (v.z < minimum.z) + { + minimum.z = v.z; + } + else if (maximum.z < v.z) + { + maximum.z = v.z; + } +} + +//static void calculateTexcoordsSpherical(std::vector& attributes, const std::vector& indices) +//{ +// dp::math::Vec3f center(0.0f, 0.0f, 0.0f); +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// center += attributes[i].vertex; +// } +// center /= (float) attributes.size(); +// +// float u; +// float v; +// +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// dp::math::Vec3f p = attributes[i].vertex - center; +// if (FLT_EPSILON < fabsf(p[1])) +// { +// u = 0.5f * atan2f(p[0], -p[1]) / dp::math::PI + 0.5f; +// } +// else +// { +// u = (0.0f <= p[0]) ? 0.75f : 0.25f; +// } +// float d = sqrtf(dp::math::square(p[0]) + dp::math::square(p[1])); +// if (FLT_EPSILON < d) +// { +// v = atan2f(p[2], d) / dp::math::PI + 0.5f; +// } +// else +// { +// v = (0.0f <= p[2]) ? 1.0f : 0.0f; +// } +// attributes[i].texcoord0 = dp::math::Vec3f(u, v, 0.0f); +// } +// +// //// The code from the environment texture lookup. +// //for (size_t i = 0; i < attributes.size(); ++i) +// //{ +// // dp::math::Vec3f R = attributes[i].vertex - center; +// // dp::math::normalize(R); +// +// // // The seam u == 0.0 == 1.0 is in positive z-axis direction. +// // // Compensate for the environment rotation done inside the direct lighting. +// // const float u = (atan2f(R[0], -R[2]) + dp::math::PI) * 0.5f / dp::math::PI; +// // const float theta = acosf(-R[1]); // theta == 0.0f is south pole, theta == M_PIf is north pole. +// // const float v = theta / dp::math::PI; // Texture is with origin at lower left, v == 0.0f is south pole. +// +// // attributes[i].texcoord0 = dp::math::Vecf(u, v, 0.0f); +// //} +//} + + +// Calculate texture tangents based on the texture coordinate gradients. +// Doesn't work when all texture coordinates are identical! Thats the reason for the other routine below. +//static void calculateTangents(std::vector& attributes, const std::vector& indices) +//{ +// for (size_t i = 0; i < indices.size(); i += 4) +// { +// unsigned int i0 = indices[i ]; +// unsigned int i1 = indices[i + 1]; +// unsigned int i2 = indices[i + 2]; +// +// dp::math::Vec3f e0 = attributes[i1].vertex - attributes[i0].vertex; +// dp::math::Vec3f e1 = attributes[i2].vertex - attributes[i0].vertex; +// dp::math::Vec2f d0 = dp::math::Vec2f(attributes[i1].texcoord0) - dp::math::Vec2f(attributes[i0].texcoord0); +// dp::math::Vec2f d1 = dp::math::Vec2f(attributes[i2].texcoord0) - dp::math::Vec2f(attributes[i0].texcoord0); +// attributes[i0].tangent += d1[1] * e0 - d0[1] * e1; +// +// e0 = attributes[i2].vertex - attributes[i1].vertex; +// e1 = attributes[i0].vertex - attributes[i1].vertex; +// d0 = dp::math::Vec2f(attributes[i2].texcoord0) - dp::math::Vec2f(attributes[i1].texcoord0); +// d1 = dp::math::Vec2f(attributes[i0].texcoord0) - dp::math::Vec2f(attributes[i1].texcoord0); +// attributes[i1].tangent += d1[1] * e0 - d0[1] * e1; +// +// e0 = attributes[i0].vertex - attributes[i2].vertex; +// e1 = attributes[i1].vertex - attributes[i2].vertex; +// d0 = dp::math::Vec2f(attributes[i0].texcoord0) - dp::math::Vec2f(attributes[i2].texcoord0); +// d1 = dp::math::Vec2f(attributes[i1].texcoord0) - dp::math::Vec2f(attributes[i2].texcoord0); +// attributes[i2].tangent += d1[1] * e0 - d0[1] * e1; +// } +// +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// dp::math::Vec3f tangent(attributes[i].tangent); +// dp::math::normalize(tangent); // This normalizes the sums from above! +// +// dp::math::Vec3f normal(attributes[i].normal); +// +// dp::math::Vec3f bitangent = normal ^ tangent; +// dp::math::normalize(bitangent); +// +// tangent = bitangent ^ normal; +// dp::math::normalize(tangent); +// +// attributes[i].tangent = tangent; +// +//#if USE_BITANGENT +// attributes[i].bitangent = bitantent; +//#endif +// } +//} + +// Calculate (geometry) tangents with the global tangent direction aligned to the biggest AABB extend of this part. +void Application::calculateTangents(std::vector& attributes, const std::vector& indices) +{ + MY_ASSERT(3 <= indices.size()); + + // Initialize with the first vertex to be able to use else-if comparisons in updateAABB(). + float3 aabbLo = attributes[indices[0]].vertex; + float3 aabbHi = attributes[indices[0]].vertex; + + // Build an axis aligned bounding box. + for (size_t i = 0; i < indices.size(); i += 3) + { + unsigned int i0 = indices[i ]; + unsigned int i1 = indices[i + 1]; + unsigned int i2 = indices[i + 2]; + + updateAABB(aabbLo, aabbHi, attributes[i0].vertex); + updateAABB(aabbLo, aabbHi, attributes[i1].vertex); + updateAABB(aabbLo, aabbHi, attributes[i2].vertex); + } + + // Get the longest extend and use that as general tangent direction. + const float3 extents = aabbHi - aabbLo; + + float f = extents.x; + int maxComponent = 0; + + if (f < extents.y) + { + f = extents.y; + maxComponent = 1; + } + if (f < extents.z) + { + maxComponent = 2; + } + + float3 direction; + float3 bidirection; + + switch (maxComponent) + { + case 0: // x-axis + default: + direction = make_float3(1.0f, 0.0f, 0.0f); + bidirection = make_float3(0.0f, 1.0f, 0.0f); + break; + case 1: // y-axis // DEBUG It might make sense to keep these directions aligned to the global coordinate system. Use the same coordinates as for z-axis then. + direction = make_float3(0.0f, 1.0f, 0.0f); + bidirection = make_float3(0.0f, 0.0f, -1.0f); + break; + case 2: // z-axis + direction = make_float3(0.0f, 0.0f, -1.0f); + bidirection = make_float3(0.0f, 1.0f, 0.0f); + break; + } + + // Build an ortho-normal basis with the existing normal. + for (size_t i = 0; i < attributes.size(); ++i) + { + float3 tangent = direction; + float3 bitangent = bidirection; + // float3 normal = attributes[i].normal; + float3 normal; + normal.x = attributes[i].normal.x; + normal.y = attributes[i].normal.y; + normal.z = attributes[i].normal.z; + + if (DENOMINATOR_EPSILON < 1.0f - fabsf(dot(normal, tangent))) + { + bitangent = normalize(cross(normal, tangent)); + tangent = normalize(cross(bitangent, normal)); + } + else // Normal and tangent direction too collinear. + { + MY_ASSERT(DENOMINATOR_EPSILON < 1.0f - fabsf(dot(bitangent, normal))); + tangent = normalize(cross(bitangent, normal)); + //bitangent = normalize(cross(normal, tangent)); + } + attributes[i].tangent = tangent; + } +} + +bool Application::screenshot(const bool tonemap) +{ + ILboolean hasImage = false; + + const int spp = m_samplesSqrt * m_samplesSqrt; // Add the samples per pixel to the filename for quality comparisons. + + std::ostringstream path; + + path << m_prefixScreenshot << "_" << spp << "spp_" << getDateTime(); + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + + ilBindImage(imageID); + ilActiveImage(0); + ilActiveFace(0); + + ilDisable(IL_ORIGIN_SET); + +#if USE_FP32_OUTPUT + const float4* bufferHost = reinterpret_cast(m_raytracer->getOutputBufferHost()); +#else + const Half4* bufferHost = reinterpret_cast(m_raytracer->getOutputBufferHost()); +#endif + + if (tonemap) + { + // Store a tonemapped RGB8 *.png image + path << ".png"; + + if (ilTexImage(m_resolution.x, m_resolution.y, 1, 3, IL_RGB, IL_UNSIGNED_BYTE, nullptr)) + { + uchar3* dst = reinterpret_cast(ilGetData()); + + const float invGamma = 1.0f / m_tonemapperGUI.gamma; + const float3 colorBalance = make_float3(m_tonemapperGUI.colorBalance[0], m_tonemapperGUI.colorBalance[1], m_tonemapperGUI.colorBalance[2]); + const float invWhitePoint = m_tonemapperGUI.brightness / m_tonemapperGUI.whitePoint; + const float burnHighlights = m_tonemapperGUI.burnHighlights; + const float crushBlacks = m_tonemapperGUI.crushBlacks + m_tonemapperGUI.crushBlacks + 1.0f; + const float saturation = m_tonemapperGUI.saturation; + + for (int y = 0; y < m_resolution.y; ++y) + { + for (int x = 0; x < m_resolution.x; ++x) + { + const int idx = y * m_resolution.x + x; + + // Tonemapper. // PERF Add a native CUDA kernel doing this. +#if USE_FP32_OUTPUT + float3 hdrColor = make_float3(bufferHost[idx]); +#else + float3 hdrColor = make_float3(__half2float(bufferHost[idx].x), + __half2float(bufferHost[idx].y), + __half2float(bufferHost[idx].z)); +#endif + + float3 ldrColor = invWhitePoint * colorBalance * hdrColor; + ldrColor *= ((ldrColor * burnHighlights) + 1.0f) / (ldrColor + 1.0f); + + float luminance = dot(ldrColor, make_float3(0.3f, 0.59f, 0.11f)); + ldrColor = lerp(make_float3(luminance), ldrColor, saturation); // This can generate negative values for saturation > 1.0f! + ldrColor = fmaxf(make_float3(0.0f), ldrColor); // Prevent negative values. + + luminance = dot(ldrColor, make_float3(0.3f, 0.59f, 0.11f)); + if (luminance < 1.0f) + { + const float3 crushed = powf(ldrColor, crushBlacks); + ldrColor = lerp(crushed, ldrColor, sqrtf(luminance)); + ldrColor = fmaxf(make_float3(0.0f), ldrColor); // Prevent negative values. + } + ldrColor = clamp(powf(ldrColor, invGamma), 0.0f, 1.0f); // Saturate, clamp to range [0.0f, 1.0f]. + + dst[idx] = make_uchar3((unsigned char) (ldrColor.x * 255.0f), + (unsigned char) (ldrColor.y * 255.0f), + (unsigned char) (ldrColor.z * 255.0f)); + } + } + hasImage = true; + } + } + else + { + // Store the float4 linear output buffer as *.hdr image. + // FIXME Add a half float conversion and store as *.exr. (Pre-built DevIL 1.7.8 supports EXR, DevIL 1.8.0 doesn't!) + path << ".hdr"; + + hasImage = ilTexImage(m_resolution.x, m_resolution.y, 1, 4, IL_RGBA, IL_FLOAT, (void*) bufferHost); + } + + if (hasImage) + { + ilEnable(IL_FILE_OVERWRITE); // By default, always overwrite + + std::string filename = path.str(); + convertPath(filename); + + if (ilSaveImage((const ILstring) filename.c_str())) + { + ilDeleteImages(1, &imageID); + + std::cout << filename << '\n'; // Print out filename to indicate that a screenshot has been taken. + return true; + } + } + + // There was an error when reaching this code. + ILenum error = ilGetError(); // DEBUG + std::cerr << "ERROR: screenshot() failed with IL error " << error << '\n'; + + while (ilGetError() != IL_NO_ERROR) // Clean up errors. + { + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + + return false; +} + +// Convert between slashes and backslashes in paths depending on the operating system +void Application::convertPath(std::string& path) +{ +#if defined(_WIN32) + std::string::size_type pos = path.find("/", 0); + while (pos != std::string::npos) + { + path[pos] = '\\'; + pos = path.find("/", pos); + } +#elif defined(__linux__) + std::string::size_type pos = path.find("\\", 0); + while (pos != std::string::npos) + { + path[pos] = '/'; + pos = path.find("\\", pos); + } +#endif +} + +void Application::convertPath(char* path) +{ +#if defined(_WIN32) + for (size_t i = 0; i < strlen(path); ++i) + { + if (path[i] == '/') + { + path[i] = '\\'; + } + } +#elif defined(__linux__) + for (size_t i = 0; i < strlen(path); ++i) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +#endif +} + +void Application::addSearchPath(const std::string& path) +{ + // Add the search path if it's not already inside the vector. + if (std::find(m_searchPaths.begin(), m_searchPaths.end(), path) == m_searchPaths.end()) + { + m_searchPaths.push_back(path); + } +} diff --git a/apps/MDL_sdf/src/Arena.cpp b/apps/MDL_sdf/src/Arena.cpp new file mode 100644 index 00000000..31fda62c --- /dev/null +++ b/apps/MDL_sdf/src/Arena.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include + +#include "inc/Arena.h" + +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include +#include +#include + + +#if !defined(NDEBUG) +// Only used inside a MY_ASSERT(). Prevent unused function warning in debug targets. +static bool isPowerOfTwo(const size_t s) +{ + return (s != 0) && ((s & (s - 1)) == 0); +} +#endif + + +namespace cuda +{ + + Block::Block(cuda::Arena* arena, const CUdeviceptr addr, const size_t size, const CUdeviceptr ptr) + : m_arena(arena) + , m_addr(addr) + , m_size(size) + , m_ptr(ptr) + { + } + + bool Block::isValid() const + { + return (m_ptr != 0); + } + + bool Block::operator<(const cuda::Block& rhs) + { + return (m_addr < rhs.m_addr); + } + + + Arena::Arena(const size_t size) + : m_size((size + 255) & ~255) // Make sure the Arena has a multiple of 256 bytes size. + { + // This can fail with CUDA OOM errors and then none of the further allocations will work. + // Same as if there would be no arena. + CU_CHECK( cuMemAlloc(&m_addr, m_size) ); + + // Make sure the base address of the arena is 256-byte aligned. + MY_ASSERT((m_addr & 255) == 0); + + // When the Arena is created, there is one big free block of the full size. + // Free blocks do not have a user pointer assigned, only allocated blocks are valid. + m_blocksFree.push_back(cuda::Block(this, m_addr, m_size, 0)); + } + + Arena::~Arena() + { + // By design there is no need to clear this here. + // These are not pointers and there is no Block destructor. + // m_blocksFree.clear(); + + // Wipe the arena. + // All active blocks after this point are invalid and must not be in use anymore. + CU_CHECK_NO_THROW( cuMemFree(m_addr) ); + } + + bool Arena::allocBlock(cuda::Block& block, const size_t size, const size_t alignment, const cuda::Usage usage) + { + const size_t maskAligned = alignment - 1; + + for (std::list::iterator it = m_blocksFree.begin(); it != m_blocksFree.end(); ++it) + { + if (usage == cuda::USAGE_STATIC) // Static blocks are allocated at the front of free blocks. + { + const size_t offset = it->m_addr & maskAligned; // If the address of this free block is not on the required alignment + const size_t adjust = (offset) ? alignment - offset : 0; // the size needs to be adjusted by this many bytes. + + const size_t sizeAdjusted = size + adjust; // The resulting block needs to be this big to fit the requested size with the proper alignment. + + if (sizeAdjusted <= it->m_size) + { + // The new block address starts at the beginning of the free block. + // The adjusted address is the properly aligned pointer in that free block. + block = cuda::Block(this, it->m_addr, sizeAdjusted, it->m_addr + adjust); + MY_ASSERT((block.m_ptr & maskAligned) == 0); // DEBUG + + it->m_addr += sizeAdjusted; // Advance the free block pointer. + it->m_size -= sizeAdjusted; // Reduce the free block size. + + if (it->m_size == 0) // If the block was fully used, remove it from the list of free blocks. + { + m_blocksFree.erase(it); + } + + return true; + } + } + else // if (usage == cuda::USAGE_TEMP) // Temporary allocations are placed at the end of free blocks to reduce fragmentation inside the arena. + { + const CUdeviceptr addrEnd = it->m_addr + it->m_size; // The poiner behind the last byte of the free block. + CUdeviceptr addrTmp = addrEnd - size; // The pointer to the start of the allocated block if the alignment fits. + + const size_t adjust = addrTmp & maskAligned; // If the start address is not properly aligned, this is the amount of bytes we need to start earlier to get an aligned pointer. + + const size_t sizeAdjusted = size + adjust; // For performance, do not split the free block into size and adjust, although there will be adjust many bytes free at the end of the block. + + if (sizeAdjusted <= it->m_size) + { + addrTmp -= adjust; // The block address needs to be that many bytes ealier to be aligned. + MY_ASSERT(it->m_addr <= addrTmp); // DEBUG Cannot happen with the size check above. + + // The new block starts at the aligned address at the end of the free block. + block = cuda::Block(this, addrTmp, sizeAdjusted, addrTmp); + MY_ASSERT((block.m_ptr & maskAligned) == 0); // DEBUG Check the user pointer for proper alignment. + + it->m_size -= sizeAdjusted; // Reduce the free block size. The address stays the same because we allocated at the end. + + if (it->m_size == 0) // If the block was fully used, remove it from the list of free blocks. + { + MY_ASSERT(it->m_addr == addrTmp); // If we used the whole size, these two addresses must match. + m_blocksFree.erase(it); + } + + return true; + } + } + } + + return false; + } + + void Arena::freeBlock(const cuda::Block& block) + { + // Search for the list element which has the next higher m_addr than the block. + std::list::iterator itNext = m_blocksFree.begin(); + + while (itNext != m_blocksFree.end() && itNext->m_addr < block.m_addr) + { + ++itNext; + } + + // Insert block before itNext. Returned iterator "it" points to block. + std::list::iterator it = m_blocksFree.insert(itNext, block); + + // If itNext is not end(), then it points to a free block and "it" is directly before that. + if (itNext != m_blocksFree.end()) + { + if (it->m_addr + it->m_size == itNext->m_addr) // Check if the memory blocks are adjacent. + { + it->m_size += itNext->m_size; // Merge the two blocks to the first + m_blocksFree.erase(itNext); // and erase the second. + } + } + + // If "it" is not begin(), then there is an element before it which could be adjacent. + if (it != m_blocksFree.begin()) + { + itNext = it--; // Now "it" can be at least begin() and itNext is always the element directly after it. + if (it->m_addr + it->m_size == itNext->m_addr) + { + it->m_size += itNext->m_size; // Merge the two blocks to the first + m_blocksFree.erase(itNext); // and erase the second. + } + } + } + + + ArenaAllocator::ArenaAllocator(const size_t sizeArenaBytes) + : m_sizeArenaBytes(sizeArenaBytes) + , m_sizeMemoryAllocated(0) + { + } + + ArenaAllocator::~ArenaAllocator() + { + for (auto arena : m_arenas) + { + delete arena; + } + } + + CUdeviceptr ArenaAllocator::alloc(const size_t size, const size_t alignment, const cuda::Usage usage) + { + // All memory alignments needed in this implementation are at max 256 and power-of-two + // which means adjustments can be done with bitmasks instead of modulo operators. + MY_ASSERT(0 < size && alignment <= 256 && isPowerOfTwo(alignment)); + + // This allocator does not support allocating a pointer with zero bytes capacity. (cuMemAlloc() doesn't either.) + // That would break the uniqueness of the user pointer inside the m_blocksAllocated because the free block wouldn't advance its address. + // If really needed, override the zero size here. + if (size == 0) + { + return 0; + } + + cuda::Block block; + + // PERF Normally the biggest free block is inside the most recently created arena. + // Means if the search starts from the back of the vector, the chance to find a free block is much higher. + size_t i = m_arenas.size(); + while (0 < i--) + { + if (m_arenas[i]->allocBlock(block, size, alignment, usage)) + { + break; + } + } + + // If none of the existing Arenas had a sufficient contiguous memory block, create a new Arena which can hold the size. + if (!block.isValid()) + { + try + { + const size_t sizeArenaBytes = std::max(m_sizeArenaBytes, size); + + // Allocate a new arena which can hold the requested size of bytes. + // No user pointer adjustment is required if m_sizeArenaBytes <= size. This is always aligned to 256 bytes. + Arena* arena = new Arena(sizeArenaBytes); // This can fail with a CUDA out-of-memory error! + + m_arenas.push_back(arena); // Append it to the vector of arenas. + + (void) arena->allocBlock(block, size, alignment, usage); + MY_ASSERT(block.isValid()); // This allocation should not fail. + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + } + + if (block.isValid()) + { + // DEBUG Make sure the user pointer is unique. (It wasn't when using size == 0.) + //std::map::const_iterator it = m_blocksAllocated.find(block.m_ptr); + //if (it != m_blocksAllocated.end()) + //{ + // MY_ASSERT(false); + //} + + m_blocksAllocated[block.m_ptr] = block; // Track all successful allocations inside the ArenaAllocator with the user pointer as key. + + m_sizeMemoryAllocated += block.m_size; // Track the overall number of bytes allocated. + } + + return block.m_ptr; // This is 0 when the block is invalid which can only happen with a CUDA OOM error. + } + + void ArenaAllocator::free(const CUdeviceptr ptr) + { + // Allow free() to be called with nullptr. This actually happens on purpose. + if (ptr == 0) + { + return; + } + + std::map::const_iterator it = m_blocksAllocated.find(ptr); + if (it != m_blocksAllocated.end()) + { + const cuda::Block& block = it->second; + + MY_ASSERT(block.m_size <= m_sizeMemoryAllocated); + m_sizeMemoryAllocated -= block.m_size; // Track overall number of byts allocated. + + block.m_arena->freeBlock(block); // Merge this block to the list of free blocks in this arena. + + m_blocksAllocated.erase(it); // Remove it from the map of allocated blocks. + } + else + { + std::cerr << "ERROR: ArenaAllocator::free() failed to find the pointer " << ptr << "\n"; + } + } + + size_t ArenaAllocator::getSizeMemoryAllocated() const + { + return m_sizeMemoryAllocated; + } + +} // namespace cuda diff --git a/apps/MDL_sdf/src/Assimp.cpp b/apps/MDL_sdf/src/Assimp.cpp new file mode 100644 index 00000000..c51b326a --- /dev/null +++ b/apps/MDL_sdf/src/Assimp.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Application.h" + +// assimp include files. +#include +//#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "inc/MyAssert.h" + + +std::shared_ptr Application::createASSIMP(const std::string& filename) +{ + std::map< std::string, std::shared_ptr >::const_iterator itGroup = m_mapGroups.find(filename); + if (itGroup != m_mapGroups.end()) + { + return itGroup->second; // Full model instancing under an Instance node. + } + + std::ifstream fin(filename); + if (!fin.fail()) + { + fin.close(); // Ok, file found. + } + else + { + std::cerr << "ERROR: createASSIMP() could not open " << filename << '\n'; + + // Generate a Group node in any case. It will not have children when the file loading fails. + std::shared_ptr group(new sg::Group(m_idGroup++)); + m_mapGroups[filename] = group; // Allow instancing of this whole model (to fail again quicker next time). + return group; + } + + Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL; // or Assimp::Logger::VERBOSE; + + Assimp::DefaultLogger::create("", severity, aiDefaultLogStream_STDOUT); // Create a logger instance for Console Output + //Assimp::DefaultLogger::create("assimp_log.txt", severity, aiDefaultLogStream_FILE); // Create a logger instance for File Output (found in project folder or near .exe) + + Assimp::DefaultLogger::get()->info("Assimp::DefaultLogger initialized."); // Will add message with "info" tag. + // Assimp::DefaultLogger::get()->debug(""); // Will add message with "debug" tag. + + unsigned int postProcessSteps = 0 + //| aiProcess_CalcTangentSpace + //| aiProcess_JoinIdenticalVertices + //| aiProcess_MakeLeftHanded + | aiProcess_Triangulate + //| aiProcess_RemoveComponent + //| aiProcess_GenNormals + | aiProcess_GenSmoothNormals + //| aiProcess_SplitLargeMeshes + //| aiProcess_PreTransformVertices + //| aiProcess_LimitBoneWeights + //| aiProcess_ValidateDataStructure + //| aiProcess_ImproveCacheLocality + | aiProcess_RemoveRedundantMaterials + //| aiProcess_FixInfacingNormals + | aiProcess_SortByPType + //| aiProcess_FindDegenerates + //| aiProcess_FindInvalidData + //| aiProcess_GenUVCoords + //| aiProcess_TransformUVCoords + //| aiProcess_FindInstances + //| aiProcess_OptimizeMeshes + //| aiProcess_OptimizeGraph + //| aiProcess_FlipUVs + //| aiProcess_FlipWindingOrder + //| aiProcess_SplitByBoneCount + //| aiProcess_Debone + //| aiProcess_GlobalScale + //| aiProcess_EmbedTextures + //| aiProcess_ForceGenNormals + //| aiProcess_DropNormals + ; + + Assimp::Importer importer; + + if (m_optimize) + { + postProcessSteps |= aiProcess_FindDegenerates | aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; + + // Removing degenerate triangles. + // If you don't support lines and points, then + // specify the aiProcess_FindDegenerates flag, + // specify the aiProcess_SortByPType flag, + // set the AI_CONFIG_PP_SBP_REMOVE importer property to (aiPrimitiveType_POINT | aiPrimitiveType_LINE). + importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE); + // This step also removes very small triangles with a surface area smaller than 10^-6. + // If you rely on having these small triangles, or notice holes in your model, + // set the property AI_CONFIG_PP_FD_CHECKAREA to false. + importer.SetPropertyBool(AI_CONFIG_PP_FD_CHECKAREA, false); + // The degenerate triangles are put into point or line primitives which are then ignored when building the meshes. + // Finally the traverseScene() function filters out any instance node in the hierarchy which doesn't have a polygonal mesh assigned. + } + + const aiScene* scene = importer.ReadFile(filename, postProcessSteps); + + // If the import failed, report it. + if (!scene) + { + Assimp::DefaultLogger::get()->info(importer.GetErrorString()); + Assimp::DefaultLogger::kill(); // Kill it after the work is done. + + std::shared_ptr group(new sg::Group(m_idGroup++)); + m_mapGroups[filename] = group; // Allow instancing of this whole model (to fail again quicker next time). + return group; + } + + // DAR HACK Convert the input file to GLTF. + // PERF Use this once on OBJ files to save time loading ASCII data. + //{ + // Assimp::Exporter exporter; + // + // exporter.Export(scene, "gltf2", filename + std::string(".gltf")); + //} + + // Each scene needs to know where its geometries begin in the m_geometries to calculate the correct mesh index in traverseScene() + const unsigned int indexSceneBase = static_cast(m_geometries.size()); + + m_remappedMeshIndices.clear(); // Clear the local remapping vector from iMesh to m_geometries index. + + // Create all geometries in the assimp scene with triangle data. Ignore the others and remap their geometry indices. + for (unsigned int iMesh = 0; iMesh < scene->mNumMeshes; ++iMesh) + { + const aiMesh* mesh = scene->mMeshes[iMesh]; + + unsigned int remapMeshToGeometry = ~0u; // Remap mesh index to geometry index. ~0 means there was no geometry for a mesh. + + // The post-processor took care of meshes per primitive type and triangulation. + // Need to do a bitwise comparison of the mPrimitiveTypes here because newer ASSIMP versions + // indicate triangulated former polygons with the additional aiPrimitiveType_NGON EncodingFlag. + if ((mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) && 2 < mesh->mNumVertices) + { + std::vector attributes(mesh->mNumVertices); + + bool needsTangents = false; + bool needsNormals = false; + bool needsTexcoords = false; + + for (unsigned int iVertex = 0; iVertex < mesh->mNumVertices; ++iVertex) + { + TriangleAttributes& attrib = attributes[iVertex]; + + const aiVector3D& v = mesh->mVertices[iVertex]; + attrib.vertex = make_float3(v.x, v.y, v.z); + + if (mesh->HasTangentsAndBitangents()) + { + const aiVector3D& t = mesh->mTangents[iVertex]; + attrib.tangent = normalize(make_float3(t.x, t.y, t.z)); + } + else + { + needsTangents = true; + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + } + + if (mesh->HasNormals()) + { + const aiVector3D& n = mesh->mNormals[iVertex]; + // There exist OBJ files with unnormalized normals! + attrib.normal = normalize(make_float3(n.x, n.y, n.z)); + } + else + { + needsNormals = true; + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + } + + if (mesh->HasTextureCoords(0)) + { + const aiVector3D& t = mesh->mTextureCoords[0][iVertex]; + attrib.texcoord = make_float3(t.x, t.y, t.z); + } + else + { + needsTexcoords = true; + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + } + } + + std::vector indices; + + for (unsigned int iFace = 0; iFace < mesh->mNumFaces; ++iFace) + { + const struct aiFace* face = &mesh->mFaces[iFace]; + MY_ASSERT(face->mNumIndices == 3); // Must be true because of aiProcess_Triangulate. + + for (unsigned int iIndex = 0; iIndex < face->mNumIndices; ++iIndex) + { + indices.push_back(face->mIndices[iIndex]); + } + } + + //if (needsNormals) // Assimp handled that via the aiProcess_GenSmoothNormals flag. + //{ + // calculateNormals(attributes, indices); + //} + if (needsTangents) + { + calculateTangents(attributes, indices); // This calculates geometry tangents though. + } + + remapMeshToGeometry = static_cast(m_geometries.size()); + + std::shared_ptr geometry(new sg::Triangles(m_idGeometry++)); + geometry->setAttributes(attributes); + geometry->setIndices(indices); + + m_geometries.push_back(geometry); + } + + m_remappedMeshIndices.push_back(remapMeshToGeometry); + } + + std::shared_ptr group = traverseScene(scene, indexSceneBase, scene->mRootNode); + m_mapGroups[filename] = group; // Allow instancing of this whole model. + + Assimp::DefaultLogger::kill(); // Kill it after the work is done + + return group; +} + +std::shared_ptr Application::traverseScene(const struct aiScene *scene, const unsigned int indexSceneBase, const struct aiNode* node) +{ + // Create a group to hold all children and all meshes of this node. + std::shared_ptr group(new sg::Group(m_idGroup++)); + + const aiMatrix4x4& m = node->mTransformation; + + const float trafo[12] = + { + float(m.a1), float(m.a2), float(m.a3), float(m.a4), + float(m.b1), float(m.b2), float(m.b3), float(m.b4), + float(m.c1), float(m.c2), float(m.c3), float(m.c4) + }; + + // Need to do a depth-first traversal here to attach the bottom-most nodes to each node's group. + for (unsigned int iChild = 0; iChild < node->mNumChildren; ++iChild) + { + std::shared_ptr child = traverseScene(scene, indexSceneBase, node->mChildren[iChild]); + + // Create an instance which holds the subtree. + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(child); + + group->addChild(instance); + } + + // Now also gather all meshes assigned to this node. + for (unsigned int iMesh = 0; iMesh < node->mNumMeshes; ++iMesh) + { + const unsigned int indexMesh = node->mMeshes[iMesh]; // Original mesh index in the assimp scene. + MY_ASSERT(indexMesh < m_remappedMeshIndices.size()) + + if (m_remappedMeshIndices[indexMesh] != ~0u) // If there exists a Triangles geometry for this assimp mesh, then build the Instance. + { + const unsigned int indexGeometry = m_remappedMeshIndices[indexMesh]; + + // Create an instance with the current nodes transformation and append it to the parent group. + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(m_geometries[indexGeometry]); + + const struct aiMesh* mesh = scene->mMeshes[indexMesh]; + + // Allow to specify different materials per assimp model by using the filename (no path no extension) and the material index. + struct aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + + std::string nameMaterialReference; + aiString materialName; + + if (material->Get(AI_MATKEY_NAME, materialName) == aiReturn_SUCCESS) + { + nameMaterialReference = std::string(materialName.C_Str()); + } + + const int indexMaterial = findMaterial(nameMaterialReference); + + instance->setMaterial(indexMaterial); + + group->addChild(instance); + } + } + return group; +} diff --git a/apps/MDL_sdf/src/Box.cpp b/apps/MDL_sdf/src/Box.cpp new file mode 100644 index 00000000..fb8b152d --- /dev/null +++ b/apps/MDL_sdf/src/Box.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // A simple unit cube built from 12 triangles. + void Triangles::createBox() + { + m_attributes.clear(); + m_indices.clear(); + + const float left = -1.0f; + const float right = 1.0f; + const float bottom = -1.0f; + const float top = 1.0f; + const float back = -1.0f; + const float front = 1.0f; + + TriangleAttributes attrib; + + // Left. + attrib.tangent = make_float3(0.0f, 0.0f, 1.0f); + attrib.normal = make_float3(-1.0f, 0.0f, 0.0f); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Right. + attrib.tangent = make_float3(0.0f, 0.0f, -1.0f); + attrib.normal = make_float3(1.0f, 0.0f, 0.0f); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Back. + attrib.tangent = make_float3(-1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, -1.0f); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Front. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Bottom. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, -1.0f, 0.0f); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Top. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 1.0f, 0.0f); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + for (unsigned int i = 0; i < 6; ++i) + { + const unsigned int idx = i * 4; // Four m_attributes per box face. + + m_indices.push_back(idx); + m_indices.push_back(idx + 1); + m_indices.push_back(idx + 2); + + m_indices.push_back(idx + 2); + m_indices.push_back(idx + 3); + m_indices.push_back(idx); + } + } + +} // namespace sg diff --git a/apps/MDL_sdf/src/Camera.cpp b/apps/MDL_sdf/src/Camera.cpp new file mode 100644 index 00000000..75aa0f80 --- /dev/null +++ b/apps/MDL_sdf/src/Camera.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Camera.h" + +#include + +#include "shaders/shader_common.h" + + +Camera::Camera() +: m_distance(10.0f) // Camera is 10 units aways from the point of interest +, m_phi(0.75f) // on the positive z-axis +, m_theta(0.6f) // slightly above the equator (at 0.5f). +, m_fov(60.0f) +, m_widthResolution(1) +, m_heightResolution(1) +, m_aspect(1.0f) +, m_baseX(0) +, m_baseY(0) +, m_speedRatio(10.0f) +, m_dx(0) +, m_dy(0) +, m_changed(false) +{ + m_center = make_float3(0.0f, 0.0f, 0.0f); + + m_cameraP = make_float3(0.0f, 0.0f, 1.0f); + m_cameraU = make_float3(1.0f, 0.0f, 0.0f); + m_cameraV = make_float3(0.0f, 1.0f, 0.0f); + m_cameraW = make_float3(0.0f, 0.0f, -1.0f); +} + +//Camera::~Camera() +//{ +//} + +void Camera::setResolution(int w, int h) +{ + if (m_widthResolution != w || m_heightResolution != h) + { + // Never drop to zero viewport size. This avoids lots of checks for zero in other routines. + m_widthResolution = (0 < w) ? w : 1; + m_heightResolution = (0 < h) ? h : 1; + m_aspect = float(m_widthResolution) / float(m_heightResolution); + m_changed = true; + } +} + +void Camera::setBaseCoordinates(int x, int y) +{ + m_baseX = x; + m_baseY = y; +} + +void Camera::orbit(int x, int y) +{ + if (setDelta(x, y)) + { + m_phi -= float(m_dx) / float(m_widthResolution); // Negative to match the mouse movement to the phi progression. + // Wrap phi. + if (m_phi < 0.0f) + { + m_phi += 1.0f; + } + else if (1.0f < m_phi) + { + m_phi -= 1.0f; + } + + m_theta += float(m_dy) / float(m_heightResolution); + // Clamp theta. + if (m_theta < 0.0f) + { + m_theta = 0.0f; + } + else if (1.0f < m_theta) + { + m_theta = 1.0f; + } + } +} + +void Camera::pan(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float u = float(m_dx) / m_speedRatio; + float v = float(m_dy) / m_speedRatio; + // Pan the center of interest, the rest will follow. + m_center = m_center - u * m_cameraU + v * m_cameraV; + } +} + +void Camera::dolly(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float w = float(m_dy) / m_speedRatio; + // Adjust the distance, the center of interest stays fixed so that the orbit is around the same center. + m_distance -= w * length(m_cameraW); // Dragging down moves the camera forwards. "Drag-in the object". + if (m_distance < 0.001f) // Avoid swapping sides. Scene units are meters [m]. + { + m_distance = 0.001f; + } + } +} + +void Camera::focus(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float w = float(m_dy) / m_speedRatio; + // Adjust the center of interest. + setFocusDistance(m_distance - w * length(m_cameraW)); + } +} + +void Camera::setFocusDistance(float f) +{ + if (m_distance != f && 0.001f < f) // Avoid swapping sides. + { + m_distance = f; + m_center = m_cameraP + m_distance * m_cameraW; // Keep the camera position fixed and calculate a new center of interest which is the focus plane. + m_changed = true; // m_changed is only reset when asking for the frustum + } +} + +void Camera::zoom(float x) +{ + m_fov += float(x); + if (m_fov < 1.0f) + { + m_fov = 1.0f; + } + else if (179.0 < m_fov) + { + m_fov = 179.0f; + } + m_changed = true; +} + +float Camera::getAspectRatio() const +{ + return m_aspect; +} + +void Camera::markDirty() +{ + m_changed = true; +} + +bool Camera::getFrustum(float3& p, float3& u, float3& v, float3& w, bool force) +{ + bool changed = force || m_changed; + if (changed) + { + // Recalculate the camera parameters. + const float cosPhi = cosf(m_phi * 2.0f * M_PIf); + const float sinPhi = sinf(m_phi * 2.0f * M_PIf); + const float cosTheta = cosf(m_theta * M_PIf); + const float sinTheta = sinf(m_theta * M_PIf); + + const float3 normal = make_float3(cosPhi * sinTheta, -cosTheta, -sinPhi * sinTheta); // "normal", unit vector from origin to spherical coordinates (phi, theta) + + const float tanFovHalf = tanf((m_fov * 0.5f) * M_PIf / 180.0f); // m_fov is in the range [1.0f, 179.0f]. + + m_cameraP = m_center + m_distance * normal; + + m_cameraU = m_aspect * make_float3(-sinPhi, 0.0f, -cosPhi) * tanFovHalf; // "tangent" + m_cameraV = make_float3(cosTheta * cosPhi, sinTheta, cosTheta * -sinPhi) * tanFovHalf; // "bitangent" + m_cameraW = -normal; // "-normal" to look at the center. + + p = m_cameraP; + u = m_cameraU; + v = m_cameraV; + w = m_cameraW; + + m_changed = false; // Next time asking for the frustum will return false unless the camera has changed again. + } + return changed; +} + +bool Camera::setDelta(int x, int y) +{ + if (m_baseX != x || m_baseY != y) + { + m_dx = x - m_baseX; + m_dy = y - m_baseY; + + m_baseX = x; + m_baseY = y; + + m_changed = true; // m_changed is only reset when asking for the frustum. + return true; // There is a delta. + } + return false; +} + +void Camera::setSpeedRatio(float f) +{ + m_speedRatio = f; + if (m_speedRatio < 0.01f) + { + m_speedRatio = 0.01f; + } + else if (1000.0f < m_speedRatio) + { + m_speedRatio = 1000.0f; + } +} diff --git a/apps/MDL_sdf/src/Curves.cpp b/apps/MDL_sdf/src/Curves.cpp new file mode 100644 index 00000000..13be15ce --- /dev/null +++ b/apps/MDL_sdf/src/Curves.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "inc/Hair.h" + +#include "dp/math/math.h" + +#include "shaders/vector_math.h" +#include "shaders/shader_common.h" + + +#include +#include +#include +#include + + +namespace sg +{ + +static float3 cubeProjection(const float3 r) +{ + // Spherical projection was pretty unpredictable with hair models. + // Try a less distorted cubemap projection instead. + // Note that each of the faces is the same 2D texture though. + + // See OpenGL 4.6 specs chapter "8.13 Cube Map Texture Selection". + const float x = fabsf(r.x); + const float y = fabsf(r.y); + const float z = fabsf(r.z); + + float ma = 0.0f; + float sc = 0.0f; + float tc = 0.0f; + + if (x >= y && x >= z) + { + ma = x; // major axis rx + if (r.x >= 0.0f) + { + // major axis +rx + sc = -r.z; + tc = -r.y; + } + else + { + // major axis -rx + sc = r.z; + tc = -r.y; + } + } + else if (y >= z) + { + ma = y; // major axis ry + if (r.y >= 0.0f) + { + // major axis +ry + sc = r.x; + tc = r.z; + } + else + { + // major axis -ry + sc = r.x; + tc = -r.z; + } + } + else + { + ma = z; // major axis rz + if (r.z >= 0.0f) + { + // major axis +rz + sc = r.x; + tc = -r.y; + } + else + { + // major axis -rz + sc = -r.x; + tc = -r.y; + } + } + + const float s = 0.5f * (sc / ma + 1.0f); + const float t = 0.5f * (tc / ma + 1.0f); + + return make_float3(s, t, 0.0f); +} + + +bool Curves::createHair(std::string const& filename, const float scale) +{ + // Note that hair files usually have a z-up coordinate system and they seem to be defined in centimeters. + // That can be adjusted inside the scene description with a scale and rotate transform. + // The "scale" parameter coming from the "model hair scale material filename" option + // is modulating the thickness parameter defined inside the hair file. + // Use scale == 1.0 to get the original thickness. + + // push + // scale 0.01 0.01 0.01 + // rotate 1 0 0 -90 + // model hair 1.0 material_reference "file.hair" + // pop + + Hair hair; + + if (!hair.load(filename)) + { + return false; + } + + // Iterate over all strands and build the curve attributes for the scene graph. + const unsigned int numStrands = hair.getNumStrands(); + + // Calculate a texture coordinate for each strand. + std::vector texcoords; + texcoords.resize(numStrands); + + // Calculate the center of the root points. + float3 center = make_float3(0.0f); + + // This variable is always the running index over the segments. + unsigned int idx = 0; // Set to root index of first strand. + + for (unsigned int strand = 0; strand < numStrands; ++strand) + { + const unsigned short numSegments = hair.getNumSegments(strand); + + if (numSegments == 0) + { + continue; + } + + center += hair.getPoint(idx); + + idx += numSegments + 1; // Advance to next strand's root point. + } + + // Center of mass of the root strand points. + center /= float(numStrands); + + idx = 0; // Reset to root index of first strand. + + for (unsigned int strand = 0; strand < numStrands; ++strand) + { + const unsigned short numSegments = hair.getNumSegments(strand); + + if (numSegments == 0) + { + continue; + } + + const float3 r = normalize(hair.getPoint(idx) - center); + + texcoords[strand] = cubeProjection(r); + + idx += numSegments + 1; // Advance to next strand's root point. + } + + // The idxRoot value is always the root point index of the current strand. + unsigned int idxRoot = 0; // Set to root point of the first strand. + + for (unsigned int strand = 0; strand < numStrands; ++strand) + { + const unsigned short numSegments = hair.getNumSegments(strand); + + // If there is ever a strand defintion with zero segments, + // just skip that and don't change any of the indices. + if (numSegments == 0) + { + continue; + } + + // Calculate the length of each strand. + // Linear length along the control points, not the actual cubic curve. + // Needed for uFiber value in state::texture_coordinate(0).x + float lengthStrand = 0.0f; + + // Calculate some fixed reference vector per strand. + // Needed for vFiber value inside state::texture_coordinate(0).y + // It's calculated as a "face normal" of the control points "polygon", which results in something like a + // fixed bitangent to the fiber tangent direction which works OK for usual hair definitions. + float3 reference = make_float3(0.0f, 0.0f, 0.0f); + + idx = idxRoot; // Start local running index over the current strand. + + float3 v0 = hair.getPoint(idx); // Root point of the strand. + float3 v1; + + for (unsigned short segment = 0; segment < numSegments; ++segment) + { + v1 = hair.getPoint(idx + 1); + + lengthStrand += length(v1 - v0); + + // Interpret the hair control points as a polygon and calculate a face normal of that. + // The face normal is proportional to the projected surface of the polygon onto the ortho-normal basis planes. + reference.x += (v0.y - v1.y) * (v0.z + v1.z); + reference.y += (v0.z - v1.z) * (v0.x + v1.x); + reference.z += (v0.x - v1.x) * (v0.y + v1.y); + + // Advance to next segment. + v0 = v1; + ++idx; + } + + // v0 contains the endpoint of the last segment here. + // Close the "polygon" to the root point. + v1 = hair.getPoint(idxRoot); // First point of the strand. + + reference.x += (v0.y - v1.y) * (v0.z + v1.z); + reference.y += (v0.z - v1.z) * (v0.x + v1.x); + reference.z += (v0.x - v1.x) * (v0.y + v1.y); + + // If the Control points are not building some polygon face (maybe a straight line with no area), + // just pick some orthogonal vector to a strand "tangent", the vector from root to tip control point. + if (reference.x == 0.0f && reference.y == 0.0f && reference.z == 0.0f) + { + float3 tangent = hair.getPoint(idxRoot + hair.getNumSegments(strand) + 1) - hair.getPoint(idxRoot); + + // Generate an orthogonal vector to the reference tangent. + reference = (fabsf(tangent.z) < fabsf(tangent.x)) + ? make_float3(tangent.z, 0.0f, -tangent.x) + : make_float3(0.0f, tangent.z, -tangent.y); + } + reference = normalize(reference); + + // Build the cubic B-spline data. + CurveAttributes attrib; + + // Remember the attribute start index of the B-Spline attributes building this strand. + unsigned int index = static_cast(m_attributes.size()); + + idx = idxRoot; // Start local running index over the current strand again. + + // Start point, radius and fiber interpolant. + float3 p0 = hair.getPoint(idx); // Start point of this curve segment. + float r0 = hair.getThickness(idx) * 0.5f * scale; // radius = thickness * 0.5f. The scale allows modulating modulate the hair thickness in the file. + float u0 = 0.0f; // Interpolant along the hair strand from 0.0f at the root to 1.0 at the tip. + + // Initialize to keep the compiler happy. + float3 p1 = p0; + float r1 = r0; + float u1 = u0; + + for (unsigned short segment = 0; segment < numSegments; ++segment) + { + // End point, radius and fiber interpolant. + p1 = hair.getPoint(idx + 1); + r1 = hair.getThickness(idx + 1) * 0.5f * scale; + u1 = u0 + length(p1 - p0) / lengthStrand; + + if (segment == 0) + { + // Push an additional phantom point before the hair control points + // to let the cubic B-spline start exactly at the first control point. + attrib.vertex = make_float4(p0 + (p0 - p1), std::max(0.0f, r0 + (r0 - r1))); + attrib.reference = make_float4(reference, 0.0f); + attrib.texcoord = make_float4(texcoords[strand], u0 + (u0 - u1)); + m_attributes.push_back(attrib); + } + + // Push the start point of this segment. + attrib.vertex = make_float4(p0, r0); + attrib.reference = make_float4(reference, 0.0f); + attrib.texcoord = make_float4(texcoords[strand], u0); + m_attributes.push_back(attrib); + + // The last segment will store the strand endpoint and append another phantom control point. + if (segment + 1 == numSegments) + { + // Push the end point of the last segment. + attrib.vertex = make_float4(p1, r1); + attrib.reference = make_float4(reference, 0.0f); + attrib.texcoord = make_float4(texcoords[strand], u1); + m_attributes.push_back(attrib); + + // Push an additional phantom point after the hair control points + // to let the cubic B-spline end exactly at the last control point. + attrib.vertex = make_float4(p1 + (p1 - p0), std::max(0.0f, r1 + (r1 - r0))); + attrib.reference = make_float4(reference, 0.0f); + attrib.texcoord = make_float4(texcoords[strand], u1 + (u1 - u0)); + m_attributes.push_back(attrib); + } + + // Let the end point become the new start point, except for the last segment + p0 = p1; + r0 = r1; + u0 = u1; + + ++idx; // Advance to the next segment's start point. + } + + // Generate the indices for this strand. + const unsigned int indexEnd = static_cast(m_attributes.size()) - 3; // Cubic B-spline curve uses 4 control points for each primitive. + + while (index < indexEnd) + { + m_indices.push_back(index); + ++index; + } + + // Done with one strand's control points. + // (When we reach this numSegments != 0.) + + idxRoot += numSegments + 1; // Skip over all control points of the current strand. + } + + return true; +} + +} // namespace sg diff --git a/apps/MDL_sdf/src/Device.cpp b/apps/MDL_sdf/src/Device.cpp new file mode 100644 index 00000000..2807c346 --- /dev/null +++ b/apps/MDL_sdf/src/Device.cpp @@ -0,0 +1,3538 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Device.h" + +#include "inc/CheckMacros.h" + +#include "shaders/compositor_data.h" + + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +// The cfgmgr32 header is necessary for interrogating driver information in the registry. +#include +// For convenience the library is also linked in automatically using the #pragma command. +#pragma comment(lib, "Cfgmgr32.lib") +#else +#include +#endif + +#include +#if defined( _WIN32 ) +#include +#endif + +// CUDA Driver API version of the OpenGL interop header. +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +// Original code from optix_stubs.h +static void* optixLoadWindowsDll(void) +{ + const char* optixDllName = "nvoptix.dll"; + void* handle = NULL; + + // Get the size of the path first, then allocate + unsigned int size = GetSystemDirectoryA(NULL, 0); + if (size == 0) + { + // Couldn't get the system path size, so bail + return NULL; + } + + size_t pathSize = size + 1 + strlen(optixDllName); + char* systemPath = (char*) malloc(pathSize); + + if (GetSystemDirectoryA(systemPath, size) != size - 1) + { + // Something went wrong + free(systemPath); + return NULL; + } + + strcat(systemPath, "\\"); + strcat(systemPath, optixDllName); + + handle = LoadLibraryA(systemPath); + + free(systemPath); + + if (handle) + { + return handle; + } + + // If we didn't find it, go looking in the register store. Since nvoptix.dll doesn't + // have its own registry entry, we are going to look for the OpenGL driver which lives + // next to nvoptix.dll. 0 (null) will be returned if any errors occured. + + static const char* deviceInstanceIdentifiersGUID = "{4d36e968-e325-11ce-bfc1-08002be10318}"; + const ULONG flags = CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT; + ULONG deviceListSize = 0; + + if (CM_Get_Device_ID_List_SizeA(&deviceListSize, deviceInstanceIdentifiersGUID, flags) != CR_SUCCESS) + { + return NULL; + } + + char* deviceNames = (char*) malloc(deviceListSize); + + if (CM_Get_Device_ID_ListA(deviceInstanceIdentifiersGUID, deviceNames, deviceListSize, flags)) + { + free(deviceNames); + return NULL; + } + + DEVINST devID = 0; + + // Continue to the next device if errors are encountered. + for (char* deviceName = deviceNames; *deviceName; deviceName += strlen(deviceName) + 1) + { + if (CM_Locate_DevNodeA(&devID, deviceName, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) + { + continue; + } + + HKEY regKey = 0; + if (CM_Open_DevNode_Key(devID, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, ®Key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) + { + continue; + } + + const char* valueName = "OpenGLDriverName"; + DWORD valueSize = 0; + + LSTATUS ret = RegQueryValueExA(regKey, valueName, NULL, NULL, NULL, &valueSize); + if (ret != ERROR_SUCCESS) + { + RegCloseKey(regKey); + continue; + } + + char* regValue = (char*) malloc(valueSize); + ret = RegQueryValueExA(regKey, valueName, NULL, NULL, (LPBYTE) regValue, &valueSize); + if (ret != ERROR_SUCCESS) + { + free(regValue); + RegCloseKey(regKey); + continue; + } + + // Strip the OpenGL driver dll name from the string then create a new string with + // the path and the nvoptix.dll name + for (int i = valueSize - 1; i >= 0 && regValue[i] != '\\'; --i) + { + regValue[i] = '\0'; + } + + size_t newPathSize = strlen(regValue) + strlen(optixDllName) + 1; + char* dllPath = (char*) malloc(newPathSize); + strcpy(dllPath, regValue); + strcat(dllPath, optixDllName); + + free(regValue); + RegCloseKey(regKey); + + handle = LoadLibraryA((LPCSTR) dllPath); + free(dllPath); + + if (handle) + { + break; + } + } + + free(deviceNames); + + return handle; +} +#endif + + +// Global logger function instead of the Logger class to be able to submit per device data via the cbdata pointer. +static std::mutex g_mutexLogger; + +static void callbackLogger(unsigned int level, const char* tag, const char* message, void* cbdata) +{ + std::lock_guard lock(g_mutexLogger); + + Device* device = static_cast(cbdata); + + std::cerr << tag << " (" << level << ") [" << device->m_ordinal << "]: " << ((message) ? message : "(no message)") << '\n'; +} + + +static std::vector readData(std::string const& filename) +{ + std::ifstream fileStream(filename, std::ios::binary); + + if (fileStream.fail()) + { + std::cerr << "ERROR: readData() Failed to open file " << filename << '\n'; + return std::vector(); + } + + // Get the size of the file in bytes. + fileStream.seekg(0, fileStream.end); + std::streamsize size = fileStream.tellg(); + fileStream.seekg (0, fileStream.beg); + + if (size <= 0) + { + std::cerr << "ERROR: readData() File size of " << filename << " is <= 0.\n"; + return std::vector(); + } + + std::vector data(size); + + fileStream.read(data.data(), size); + + if (fileStream.fail()) + { + std::cerr << "ERROR: readData() Failed to read file " << filename << '\n'; + return std::vector(); + } + + return data; +} + + +Device::Device(const int ordinal, + const int index, + const int count, + const TypeLight typeEnv, + const int interop, + const unsigned int tex, + const unsigned int pbo, + const size_t sizeArena) +: m_ordinal(ordinal) +, m_index(index) +, m_count(count) +, m_typeEnv(typeEnv) +, m_interop(interop) +, m_tex(tex) +, m_pbo(pbo) +, m_nodeMask(0) +, m_launchWidth(0) +, m_ownsSharedBuffer(false) +, m_d_compositorData(0) +, m_cudaGraphicsResource(nullptr) +, m_sizeMemoryTextureArrays(0) +{ + // Get the CUdevice handle from the CUDA device ordinal. + CU_CHECK( cuDeviceGet(&m_cudaDevice, m_ordinal) ); + + initDeviceAttributes(); // Query all CUDA capabilities of this device. + + OPTIX_CHECK( initFunctionTable() ); + + // Create a CUDA Context and make it current to this thread. + // PERF What is the best CU_CTX_SCHED_* setting here? + // CU_CTX_MAP_HOST host to allow pinned memory. + CU_CHECK( cuCtxCreate(&m_cudaContext, CU_CTX_SCHED_SPIN | CU_CTX_MAP_HOST, m_cudaDevice) ); + + // PERF To make use of asynchronous copies. Used mainly in the benchmark mode. Interactive rendering is synchronizing per sub-frame. + CU_CHECK( cuStreamCreate(&m_cudaStream, CU_STREAM_NON_BLOCKING) ); + + size_t sizeFree = 0; + size_t sizeTotal = 0; + + CU_CHECK( cuMemGetInfo(&sizeFree, &sizeTotal) ); + + std::cout << "Device ordinal " << m_ordinal << ": " << sizeFree << " bytes free; " << sizeTotal << " bytes total\n"; + + m_allocator = new cuda::ArenaAllocator(sizeArena * 1024 * 1024); // The ArenaAllocator gets the default Arena size in bytes! + +#if 1 + // UUID works under Windows and Linux. + memset(&m_deviceUUID, 0, 16); + CU_CHECK( cuDeviceGetUuid(&m_deviceUUID, m_cudaDevice) ); +#else + // LUID only works under Windows and only in WDDM mode, not in TCC mode! + // Get the LUID and node mask to be able to determine which device needs to allocate the peer-to-peer staging buffer for the OpenGL interop PBO. + memset(m_deviceLUID, 0, 8); + CU_CHECK( cuDeviceGetLuid(m_deviceLUID, &m_nodeMask, m_cudaDevice) ); +#endif + + // FIXME Only load this on the primary device. + CU_CHECK( cuModuleLoad(&m_moduleCompositor, "./MDL_sdf_core/compositor.ptx") ); + CU_CHECK( cuModuleGetFunction(&m_functionCompositor, m_moduleCompositor, "compositor") ); + + OptixDeviceContextOptions options = {}; + + options.logCallbackFunction = &callbackLogger; + options.logCallbackData = this; // This allows per device logs. It's currently printing the device ordinal. + options.logCallbackLevel = 3; // Keep at warning level (3) to suppress the disk cache messages. +#if USE_DEBUG_EXCEPTIONS + options.validationMode = OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL; +#endif + + OPTIX_CHECK( m_api.optixDeviceContextCreate(m_cudaContext, &options, &m_optixContext) ); + + initDeviceProperties(); // OptiX + + m_d_systemData = reinterpret_cast(memAlloc(sizeof(SystemData), 16)); // Currently 8 byte alignment would be enough. + + m_isDirtySystemData = true; // Trigger SystemData update before the next launch. + + // Initialize all renderer system data. + //m_systemData.rect = make_int4(0, 0, 1, 1); // Unused, this is not a tiled renderer. + m_systemData.topObject = 0; + m_systemData.outputBuffer = 0; // Deferred allocation. Only done in render() of the derived Device classes to allow for different memory spaces! + m_systemData.tileBuffer = 0; // For the final frame tiled renderer the intermediate buffer is only tileSize. + m_systemData.texelBuffer = 0; // For the final frame tiled renderer. Contains the accumulated result of the current tile. + m_systemData.geometryInstanceData = nullptr; + m_systemData.cameraDefinitions = nullptr; + m_systemData.lightDefinitions = nullptr; + m_systemData.materialDefinitionsMDL = nullptr; // The MDL material parameter argument block, texture handler and index into the shader. + m_systemData.shaderConfigurations = nullptr; // Indexed by MaterialDefinitionMDL::indexShader. + m_systemData.resolution = make_int2(1, 1); // Deferred allocation after setResolution() when m_isDirtyOutputBuffer == true. + m_systemData.tileSize = make_int2(8, 8); // Default value for multi-GPU tiling. Must be power-of-two values. (8x8 covers either 8x4 or 4x8 internal 2D warp shapes.) + m_systemData.tileShift = make_int2(3, 3); // The right-shift for the division by tileSize. + m_systemData.pathLengths = make_int2(2, 5); // min, max + m_systemData.walkLength = 1; + m_systemData.deviceCount = m_count; // The number of active devices. + m_systemData.deviceIndex = m_index; // This allows to distinguish multiple devices. + m_systemData.iterationIndex = 0; + m_systemData.samplesSqrt = 0; // Invalid value! Enforces that there is at least one setState() call before rendering. + m_systemData.sceneEpsilon = 500.0f * SCENE_EPSILON_SCALE; + m_systemData.clockScale = 1000.0f * CLOCK_FACTOR_SCALE; + m_systemData.typeLens = 0; + m_systemData.numCameras = 0; + m_systemData.numLights = 0; + m_systemData.numMaterials = 0; + m_systemData.numBitsShaders = 0; + m_systemData.directLighting = 1; + + m_isDirtyOutputBuffer = true; // First render call initializes it. This is done in the derived render() functions. + + m_moduleFilenames.resize(NUM_MODULE_IDENTIFIERS); + + // Starting with OptiX SDK 7.5.0 and CUDA 11.7 either PTX or OptiX IR input can be used to create modules. + // Just initialize the m_moduleFilenames depending on the definition of USE_OPTIX_IR. + // That is added to the project definitions inside the CMake script when OptiX SDK 7.5.0 and CUDA 11.7 or newer are found. +#if defined(USE_OPTIX_IR) + m_moduleFilenames[MODULE_ID_RAYGENERATION] = std::string("./MDL_sdf_core/raygeneration.optixir"); + m_moduleFilenames[MODULE_ID_EXCEPTION] = std::string("./MDL_sdf_core/exception.optixir"); + m_moduleFilenames[MODULE_ID_MISS] = std::string("./MDL_sdf_core/miss.optixir"); + m_moduleFilenames[MODULE_ID_HIT] = std::string("./MDL_sdf_core/hit.optixir"); + m_moduleFilenames[MODULE_ID_INTERSECTION] = std::string("./MDL_sdf_core/intersection.optixir"); + m_moduleFilenames[MODULE_ID_LENS_SHADER] = std::string("./MDL_sdf_core/lens_shader.optixir"); + m_moduleFilenames[MODULE_ID_LIGHT_SAMPLE] = std::string("./MDL_sdf_core/light_sample.optixir"); +#else + m_moduleFilenames[MODULE_ID_RAYGENERATION] = std::string("./MDL_sdf_core/raygeneration.ptx"); + m_moduleFilenames[MODULE_ID_EXCEPTION] = std::string("./MDL_sdf_core/exception.ptx"); + m_moduleFilenames[MODULE_ID_MISS] = std::string("./MDL_sdf_core/miss.ptx"); + m_moduleFilenames[MODULE_ID_INTERSECTION] = std::string("./MDL_sdf_core/intersection.ptx"); + m_moduleFilenames[MODULE_ID_HIT] = std::string("./MDL_sdf_core/hit.ptx"); + m_moduleFilenames[MODULE_ID_LENS_SHADER] = std::string("./MDL_sdf_core/lens_shader.ptx"); + m_moduleFilenames[MODULE_ID_LIGHT_SAMPLE] = std::string("./MDL_sdf_core/light_sample.ptx"); +#endif + + // OptixModuleCompileOptions + m_mco = {}; + + m_mco.maxRegisterCount = OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT; +#if USE_DEBUG_EXCEPTIONS + m_mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_0; // No optimizations. + m_mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; // Full debug. Never profile kernels with this setting! +#else + m_mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_3; // All optimizations, is the default. + // Keep generated line info. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) +#if (OPTIX_VERSION >= 70400) + m_mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; // PERF Must use OPTIX_COMPILE_DEBUG_LEVEL_MODERATE to profile code with Nsight Compute! +#else + m_mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; +#endif +#endif // USE_DEBUG_EXCEPTIONS + + // OptixPipelineCompileOptions + m_pco = {}; + + m_pco.usesMotionBlur = 0; + m_pco.traversableGraphFlags = OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; + m_pco.numPayloadValues = 2; // The renderer uses only two 32bit registers to encode a 64bit pointer to the per ray payload structure. + m_pco.numAttributeValues = 4; // The minimum is two for the built-in triangle barycentrics, built-in curves use only one. + // Custom SDF primitives require four for the sign (frontface/backface) and the uvw texture coordinate. +#if USE_DEBUG_EXCEPTIONS + m_pco.exceptionFlags = + OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW + | OPTIX_EXCEPTION_FLAG_TRACE_DEPTH + | OPTIX_EXCEPTION_FLAG_USER +#if (OPTIX_VERSION < 80000) + // Removed in OptiX SDK 8.0.0. + // Use OptixDeviceContextOptions validationMode = OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL instead. + | OPTIX_EXCEPTION_FLAG_DEBUG +#endif + ; +#else + m_pco.exceptionFlags = OPTIX_EXCEPTION_FLAG_NONE; +#endif + m_pco.pipelineLaunchParamsVariableName = "sysData"; +#if (OPTIX_VERSION >= 70100) + // New in OptiX 7.1.0. + // This renderer supports custom primitives, built-in cubic B-splines, and built-in triangles. + m_pco.usesPrimitiveTypeFlags = OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM | + OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE | + OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; +#endif + + // OptixPipelineLinkOptions + m_plo = {}; + + m_plo.maxTraceDepth = 2; +#if (OPTIX_VERSION < 70700) + // OptixPipelineLinkOptions debugLevel is only present in OptiX SDK versions before 7.7.0. + #if USE_DEBUG_EXCEPTIONS + m_plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; // Full debug. Never profile kernels with this setting! + #else + // Keep generated line info for Nsight Compute profiling. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) + #if (OPTIX_VERSION >= 70400) + m_plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; + #else + m_plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; + #endif + #endif // USE_DEBUG_EXCEPTIONS +#endif // 70700 + + // OptixProgramGroupOptions + m_pgo = {}; // This is a just placeholder. +} + + +Device::~Device() +{ + CU_CHECK_NO_THROW( cuCtxSetCurrent(m_cudaContext) ); // Activate this CUDA context. Not using activate() because this needs a no-throw check. + CU_CHECK_NO_THROW( cuCtxSynchronize() ); // Make sure everthing running on this CUDA context has finished. + + if (m_cudaGraphicsResource != nullptr) + { + CU_CHECK_NO_THROW( cuGraphicsUnregisterResource(m_cudaGraphicsResource) ); + } + + CU_CHECK_NO_THROW( cuModuleUnload(m_moduleCompositor) ); + + for (std::map::const_iterator it = m_mapTextures.begin(); it != m_mapTextures.end(); ++it) + { + if (it->second) + { + // The texture array data might be owned by a peer device. + // Explicitly destroy only the parts which belong to this device. + m_sizeMemoryTextureArrays -= it->second->destroy(this); + + delete it->second; // This will delete the CUtexObject which exists per device. + } + } + + // Destroy MDL CUDA Resources which are not allocated by the arena allocator. + for (TextureMDLHost& host : m_textureMDLHosts) + { + if (host.m_texture.filtered_object) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(host.m_texture.filtered_object) ); + } + if (host.m_texture.unfiltered_object) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(host.m_texture.unfiltered_object) ); + } + // Only destroy the CUarray data if the current device is the owner. + if (this == host.m_owner) + { + CU_CHECK_NO_THROW( cuArrayDestroy(host.m_d_array) ); + m_sizeMemoryTextureArrays -= host.m_sizeBytesArray; + } + } + + for (MbsdfHost& host : m_mbsdfHosts) + { + for (int i = 0; i < 2; ++i) + { + if (host.m_mbsdf.eval_data[i]) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(host.m_mbsdf.eval_data[i]) ); + } + + if (this == host.m_owner && host.m_d_array[i]) + { + CU_CHECK_NO_THROW( cuArrayDestroy(host.m_d_array[i]) ); + m_sizeMemoryTextureArrays -= host.m_sizeBytesArray[i]; + } + } + } + + for (LightprofileHost& host : m_lightprofileHosts) + { + if (host.m_profile.eval_data) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(host.m_profile.eval_data) ); + } + + if (this == host.m_owner && host.m_d_array) + { + CU_CHECK_NO_THROW( cuArrayDestroy(host.m_d_array) ); + m_sizeMemoryTextureArrays -= host.m_sizeBytesArray; + } + } + + MY_ASSERT(m_sizeMemoryTextureArrays == 0); // Make sure the texture memory tracking is correct. + + OPTIX_CHECK_NO_THROW( m_api.optixPipelineDestroy(m_pipeline) ); + OPTIX_CHECK_NO_THROW( m_api.optixDeviceContextDestroy(m_optixContext) ); + + delete m_allocator; // This frees all CUDA allocations done with the arena allocator! + + CU_CHECK_NO_THROW( cuStreamDestroy(m_cudaStream) ); + CU_CHECK_NO_THROW( cuCtxDestroy(m_cudaContext) ); +} + + +void Device::initDeviceAttributes() +{ + char buffer[1024]; + buffer[1023] = 0; + + CU_CHECK( cuDeviceGetName(buffer, 1023, m_cudaDevice) ); + m_deviceName = std::string(buffer); + + CU_CHECK(cuDeviceGetPCIBusId(buffer, 1023, m_cudaDevice)); + m_devicePciBusId = std::string(buffer); + + std::cout << "Device ordinal " << m_ordinal << ": " << m_deviceName << " visible\n"; + + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxThreadsPerBlock, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimX, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimY, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Y, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimZ, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Z, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimX, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimY, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimZ, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerBlock, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.sharedMemoryPerBlock, CU_DEVICE_ATTRIBUTE_SHARED_MEMORY_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.totalConstantMemory, CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.warpSize, CU_DEVICE_ATTRIBUTE_WARP_SIZE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxPitch, CU_DEVICE_ATTRIBUTE_MAX_PITCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxRegistersPerBlock, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.registersPerBlock, CU_DEVICE_ATTRIBUTE_REGISTERS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.clockRate, CU_DEVICE_ATTRIBUTE_CLOCK_RATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.textureAlignment, CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.gpuOverlap, CU_DEVICE_ATTRIBUTE_GPU_OVERLAP, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiprocessorCount, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.kernelExecTimeout, CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.integrated, CU_DEVICE_ATTRIBUTE_INTEGRATED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canMapHostMemory, CU_DEVICE_ATTRIBUTE_CAN_MAP_HOST_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeMode, CU_DEVICE_ATTRIBUTE_COMPUTE_MODE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dDepth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayNumslices, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_NUMSLICES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.surfaceAlignment, CU_DEVICE_ATTRIBUTE_SURFACE_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.concurrentKernels, CU_DEVICE_ATTRIBUTE_CONCURRENT_KERNELS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.eccEnabled, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciBusId, CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciDeviceId, CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.tccDriver, CU_DEVICE_ATTRIBUTE_TCC_DRIVER, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.memoryClockRate, CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.globalMemoryBusWidth, CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.l2CacheSize, CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxThreadsPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.asyncEngineCount, CU_DEVICE_ATTRIBUTE_ASYNC_ENGINE_COUNT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.unifiedAddressing, CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canTex2dGather, CU_DEVICE_ATTRIBUTE_CAN_TEX2D_GATHER, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dGatherWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dGatherHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dWidthAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dHeightAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dDepthAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciDomainId, CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.texturePitchAlignment, CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dDepth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_DEPTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLinearWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LINEAR_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearPitch, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_PITCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dMipmappedWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dMipmappedHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeCapabilityMajor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeCapabilityMinor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dMipmappedWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_MIPMAPPED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.streamPrioritiesSupported, CU_DEVICE_ATTRIBUTE_STREAM_PRIORITIES_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.globalL1CacheSupported, CU_DEVICE_ATTRIBUTE_GLOBAL_L1_CACHE_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.localL1CacheSupported, CU_DEVICE_ATTRIBUTE_LOCAL_L1_CACHE_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxRegistersPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.managedMemory, CU_DEVICE_ATTRIBUTE_MANAGED_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiGpuBoard, CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiGpuBoardGroupId, CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD_GROUP_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.hostNativeAtomicSupported, CU_DEVICE_ATTRIBUTE_HOST_NATIVE_ATOMIC_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.singleToDoublePrecisionPerfRatio, CU_DEVICE_ATTRIBUTE_SINGLE_TO_DOUBLE_PRECISION_PERF_RATIO, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pageableMemoryAccess, CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.concurrentManagedAccess, CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computePreemptionSupported, CU_DEVICE_ATTRIBUTE_COMPUTE_PREEMPTION_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUseHostPointerForRegisteredMem, CU_DEVICE_ATTRIBUTE_CAN_USE_HOST_POINTER_FOR_REGISTERED_MEM, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUse64BitStreamMemOps, CU_DEVICE_ATTRIBUTE_CAN_USE_64_BIT_STREAM_MEM_OPS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUseStreamWaitValueNor, CU_DEVICE_ATTRIBUTE_CAN_USE_STREAM_WAIT_VALUE_NOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.cooperativeLaunch, CU_DEVICE_ATTRIBUTE_COOPERATIVE_LAUNCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.cooperativeMultiDeviceLaunch, CU_DEVICE_ATTRIBUTE_COOPERATIVE_MULTI_DEVICE_LAUNCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerBlockOptin, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK_OPTIN, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canFlushRemoteWrites, CU_DEVICE_ATTRIBUTE_CAN_FLUSH_REMOTE_WRITES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.hostRegisterSupported, CU_DEVICE_ATTRIBUTE_HOST_REGISTER_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pageableMemoryAccessUsesHostPageTables, CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS_USES_HOST_PAGE_TABLES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.directManagedMemAccessFromHost, CU_DEVICE_ATTRIBUTE_DIRECT_MANAGED_MEM_ACCESS_FROM_HOST, m_cudaDevice) ); +} + +void Device::initDeviceProperties() +{ + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_RTCORE_VERSION, &m_deviceProperty.rtcoreVersion, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH, &m_deviceProperty.limitMaxTraceDepth, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH, &m_deviceProperty.limitMaxTraversableGraphDepth, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS, &m_deviceProperty.limitMaxPrimitivesPerGas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS, &m_deviceProperty.limitMaxInstancesPerIas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID, &m_deviceProperty.limitMaxInstanceId, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK, &m_deviceProperty.limitNumBitsInstanceVisibilityMask, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS, &m_deviceProperty.limitMaxSbtRecordsPerGas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET, &m_deviceProperty.limitMaxSbtOffset, sizeof(unsigned int)) ); +#if (OPTIX_VERSION >= 80000) + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_SHADER_EXECUTION_REORDERING, &m_deviceProperty.shaderExecutionReordering, sizeof(unsigned int)) ); +#endif + +#if 0 + std::cout << "OPTIX_DEVICE_PROPERTY_RTCORE_VERSION = " << m_deviceProperty.rtcoreVersion << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH = " << m_deviceProperty.limitMaxTraceDepth << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH = " << m_deviceProperty.limitMaxTraversableGraphDepth << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS = " << m_deviceProperty.limitMaxPrimitivesPerGas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS = " << m_deviceProperty.limitMaxInstancesPerIas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID = " << m_deviceProperty.limitMaxInstanceId << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK = " << m_deviceProperty.limitNumBitsInstanceVisibilityMask << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS = " << m_deviceProperty.limitMaxSbtRecordsPerGas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET = " << m_deviceProperty.limitMaxSbtOffset << '\n'; +#if (OPTIX_VERSION >= 80000) + std::cout << "OPTIX_DEVICE_PROPERTY_SHADER_EXECUTION_REORDERING = " << m_deviceProperty.shaderExecutionReordering << '\n'; +#endif +#endif +} + + +OptixResult Device::initFunctionTable() +{ +#ifdef _WIN32 + void* handle = optixLoadWindowsDll(); + if (!handle) + { + return OPTIX_ERROR_LIBRARY_NOT_FOUND; + } + + void* symbol = reinterpret_cast(GetProcAddress((HMODULE) handle, "optixQueryFunctionTable")); + if (!symbol) + { + return OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND; + } +#else + void* handle = dlopen("libnvoptix.so.1", RTLD_NOW); + if (!handle) + { + return OPTIX_ERROR_LIBRARY_NOT_FOUND; + } + + void* symbol = dlsym(handle, "optixQueryFunctionTable"); + if (!symbol) + + { + return OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND; + } +#endif + + OptixQueryFunctionTable_t* optixQueryFunctionTable = reinterpret_cast(symbol); + + return optixQueryFunctionTable(OPTIX_ABI_VERSION, 0, 0, 0, &m_api, sizeof(OptixFunctionTable)); +} + + +void Device::initPipeline() +{ + // This functin needs to be called after all MDL materials have been built, + // because only then all callable programs are present and can be compiled into the pipeline. + + MY_ASSERT(NUM_RAY_TYPES == 2); // The following code only works for two raytypes. + + // Each source file results in one OptixModule. + std::vector modules(NUM_MODULE_IDENTIFIERS); + + // Create all modules: + for (size_t i = 0; i < m_moduleFilenames.size(); ++i) + { + // Since OptiX 7.5.0 the program input can either be *.ptx source code or *.optixir binary code. + // The module filenames are automatically switched between *.ptx or *.optixir extension based on the definition of USE_OPTIX_IR + std::vector programData = readData(m_moduleFilenames[i]); + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixModuleCreate(m_optixContext, &m_mco, &m_pco, programData.data(), programData.size(), nullptr, nullptr, &modules[i]) ); +#else + OPTIX_CHECK( m_api.optixModuleCreateFromPTX(m_optixContext, &m_mco, &m_pco, programData.data(), programData.size(), nullptr, nullptr, &modules[i]) ); +#endif + } + + // Get the OptiX internal module with the intersection program for cubic B-spline curves; + OptixBuiltinISOptions builtinISOptions = {}; + + builtinISOptions.builtinISModuleType = OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE; + builtinISOptions.usesMotionBlur = 0; + + OptixModule moduleIntersectionCubicCurves; + + OPTIX_CHECK( m_api.optixBuiltinISModuleGet(m_optixContext, &m_mco, &m_pco, &builtinISOptions, &moduleIntersectionCubicCurves) ); + + std::vector programGroupDescriptions(NUM_PROGRAM_GROUP_IDS); + memset(programGroupDescriptions.data(), 0, sizeof(OptixProgramGroupDesc) * programGroupDescriptions.size()); + + OptixProgramGroupDesc* pgd; + + pgd = &programGroupDescriptions[PGID_RAYGENERATION]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->raygen.module = modules[MODULE_ID_RAYGENERATION]; + if (1 < m_count) + { + // Only use the multi-GPU specific raygen program when there are multiple devices enabled. + pgd->raygen.entryFunctionName = "__raygen__path_tracer_local_copy"; + } + else + { + // Use a single-GPU raygen program which doesn't need compositing. + pgd->raygen.entryFunctionName = "__raygen__path_tracer"; + } + + pgd = &programGroupDescriptions[PGID_EXCEPTION]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_EXCEPTION; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->exception.module = modules[MODULE_ID_EXCEPTION]; + pgd->exception.entryFunctionName = "__exception__all"; + + pgd = &programGroupDescriptions[PGID_MISS_RADIANCE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_MISS; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->miss.module = modules[MODULE_ID_MISS]; + switch (m_typeEnv) + { + case TYPE_LIGHT_ENV_CONST: + pgd->miss.entryFunctionName = "__miss__env_constant"; + break; + case TYPE_LIGHT_ENV_SPHERE: + pgd->miss.entryFunctionName = "__miss__env_sphere"; + break; + default: // Every other ID means there is no environment light, esp. using m_typeEnv == NUM_LIGHT_TYPES for that. + pgd->miss.entryFunctionName = "__miss__env_null"; + break; + } + + pgd = &programGroupDescriptions[PGID_MISS_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_MISS; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->miss.module = nullptr; + pgd->miss.entryFunctionName = nullptr; // No miss program for shadow rays. + + // The hit records for the radiance and shadow ray for opaque (instance sbtOffset 0) and cutout opacity (instance sbtOffset 1) hit records. + // 0 = no emission, no cutout + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE_0]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance_no_emission"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW_0]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow"; + + // 1 = emission, no cutout + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE_1]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW_1]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow"; + + // 2 = no emission, cutout + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE_2]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance_no_emission"; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__radiance_cutout"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW_2]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow_cutout"; + + // 3 = emission, cutout + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE_3]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance"; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__radiance_cutout"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW_3]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow_cutout"; + + // Cubic B-Splines + pgd = &programGroupDescriptions[PGID_HIT_CURVES]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__curves"; + pgd->hitgroup.moduleIS = moduleIntersectionCubicCurves; + pgd->hitgroup.entryFunctionNameIS = nullptr; // Uses built-in IS for cubic curves. + + pgd = &programGroupDescriptions[PGID_HIT_CURVES_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow"; + pgd->hitgroup.moduleIS = moduleIntersectionCubicCurves; + pgd->hitgroup.entryFunctionNameIS = nullptr; // Uses built-in IS for cubic curves. + + // Custom SDF primitives. + pgd = &programGroupDescriptions[PGID_HIT_SDF]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance_no_emission"; + pgd->hitgroup.moduleIS = modules[MODULE_ID_INTERSECTION]; + pgd->hitgroup.entryFunctionNameIS = "__intersection__sdf"; + + pgd = &programGroupDescriptions[PGID_HIT_SDF_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_HIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow"; // FIXME SDF primitives are not supporting MDL materials with cutout-opacity. + pgd->hitgroup.moduleIS = modules[MODULE_ID_INTERSECTION]; + pgd->hitgroup.entryFunctionNameIS = "__intersection__sdf"; + + // CALLABLES + // Lens Shader + pgd = &programGroupDescriptions[PGID_LENS_PINHOLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__pinhole"; + + pgd = &programGroupDescriptions[PGID_LENS_FISHEYE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__fisheye"; + + pgd = &programGroupDescriptions[PGID_LENS_SPHERE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sphere"; + + // Light Sampler + // Only one of the environment callables will ever be used, but both are required + // for the proper direct callable index calculation for BXDFs using NUM_LIGHT_TYPES. + pgd = &programGroupDescriptions[PGID_LIGHT_ENV_CONSTANT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_env_constant"; + + pgd = &programGroupDescriptions[PGID_LIGHT_ENV_SPHERE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_env_sphere"; + + pgd = &programGroupDescriptions[PGID_LIGHT_MESH]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_HIT]; // Inside the module including texture_support.h. + pgd->callables.entryFunctionNameDC = "__direct_callable__light_mesh"; + + pgd = &programGroupDescriptions[PGID_LIGHT_POINT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_point"; + + pgd = &programGroupDescriptions[PGID_LIGHT_SPOT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_spot"; + + pgd = &programGroupDescriptions[PGID_LIGHT_IES]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_ies"; + + std::vector programGroups(programGroupDescriptions.size()); + + OPTIX_CHECK( m_api.optixProgramGroupCreate(m_optixContext, programGroupDescriptions.data(), (unsigned int) programGroupDescriptions.size(), &m_pgo, nullptr, nullptr, programGroups.data()) ); + + // Now append all the program groups with the direct callables from the MDL materials. + programGroups.insert(programGroups.end(), m_programGroupsMDL.begin(), m_programGroupsMDL.end()); + + OPTIX_CHECK( m_api.optixPipelineCreate(m_optixContext, &m_pco, &m_plo, programGroups.data(), (unsigned int) programGroups.size(), nullptr, nullptr, &m_pipeline) ); + + // STACK SIZES + OptixStackSizes ssp = {}; // Whole pipeline. + + for (auto pg: programGroups) + { + OptixStackSizes ss; + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixProgramGroupGetStackSize(pg, &ss, m_pipeline) ); +#else + OPTIX_CHECK( m_api.optixProgramGroupGetStackSize(pg, &ss) ); +#endif + + ssp.cssRG = std::max(ssp.cssRG, ss.cssRG); + ssp.cssMS = std::max(ssp.cssMS, ss.cssMS); + ssp.cssCH = std::max(ssp.cssCH, ss.cssCH); + ssp.cssAH = std::max(ssp.cssAH, ss.cssAH); + ssp.cssIS = std::max(ssp.cssIS, ss.cssIS); + ssp.cssCC = std::max(ssp.cssCC, ss.cssCC); + ssp.dssDC = std::max(ssp.dssDC, ss.dssDC); + } + + // Temporaries + unsigned int cssCCTree = ssp.cssCC; // Should be 0. No continuation callables in this pipeline. // maxCCDepth == 0 + unsigned int cssCHOrMSPlusCCTree = std::max(ssp.cssCH, ssp.cssMS) + cssCCTree; + + const unsigned int maxDCDepth = 2; // The __direct_callable__light_mesh calls other direct callables from MDL expressions. + + // Arguments + + unsigned int directCallableStackSizeFromTraversal = ssp.dssDC * maxDCDepth; // FromTraversal: DC is invoked from IS or AH. // Possible stack size optimizations. + unsigned int directCallableStackSizeFromState = ssp.dssDC * maxDCDepth; // FromState: DC is invoked from RG, MS, or CH. // Possible stack size optimizations. + unsigned int continuationStackSize = ssp.cssRG + cssCCTree + cssCHOrMSPlusCCTree * (std::max(1u, m_plo.maxTraceDepth) - 1u) + + std::min(1u, m_plo.maxTraceDepth) * std::max(cssCHOrMSPlusCCTree, ssp.cssAH + ssp.cssIS); + unsigned int maxTraversableGraphDepth = 2; + + OPTIX_CHECK( m_api.optixPipelineSetStackSize(m_pipeline, directCallableStackSizeFromTraversal, directCallableStackSizeFromState, continuationStackSize, maxTraversableGraphDepth) ); + + // Set up the Shader Binding Table (SBT) + + // Put all SbtRecordHeader types in one CUdeviceptr. + const int numHeaders = static_cast(programGroups.size()); + + std::vector sbtRecordHeaders(numHeaders); + + for (int i = 0; i < numHeaders; ++i) + { + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_RAYGENERATION + i], &sbtRecordHeaders[i]) ); + } + + m_d_sbtRecordHeaders = memAlloc(sizeof(SbtRecordHeader) * numHeaders, OPTIX_SBT_RECORD_ALIGNMENT); + CU_CHECK( cuMemcpyHtoDAsync(m_d_sbtRecordHeaders, sbtRecordHeaders.data(), sizeof(SbtRecordHeader) * numHeaders, m_cudaStream) ); + + // Setup the OptixShaderBindingTable. + // The order of SBT records match the ProgramGroupId enums. + m_sbt.raygenRecord = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_RAYGENERATION; + + m_sbt.exceptionRecord = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_EXCEPTION; + + m_sbt.missRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_MISS_RADIANCE; + m_sbt.missRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + m_sbt.missRecordCount = NUM_RAY_TYPES; + + m_sbt.hitgroupRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_HIT_RADIANCE_0; + m_sbt.hitgroupRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + // Six hitRecords: + // 0 to 3 == (no emission, emission) x (no cutout, cutout) + // 4 == non-emissive opaque cubic curves. + // 5 == non-emissive opaque SDF custom primitives (single AABB). + m_sbt.hitgroupRecordCount = NUM_RAY_TYPES * 6; + + m_sbt.callablesRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_LENS_PINHOLE; // The pinhole lens shader is the first callable. + m_sbt.callablesRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + m_sbt.callablesRecordCount = static_cast(programGroups.size()) - PGID_LENS_PINHOLE; + + // After all required optixSbtRecordPackHeader, optixProgramGroupGetStackSize, and optixPipelineCreate + // calls have been done, the OptixProgramGroup and OptixModule objects can be destroyed. + for (auto pg: programGroups) + { + OPTIX_CHECK( m_api.optixProgramGroupDestroy(pg) ); + } + // This also destroyed the program groups in m_programGroupsMDL, so these can be cleared. + m_programGroupsMDL.clear(); + + for (auto m : modules) + { + OPTIX_CHECK(m_api.optixModuleDestroy(m)); + } + // Destroy the modules with the MDL generated direct callables which were used to build the m_programGroupsMDL. + for (auto m : m_modulesMDL) + { + OPTIX_CHECK(m_api.optixModuleDestroy(m)); + } + m_modulesMDL.clear(); +} + + +void Device::initCameras(const std::vector& cameras) +{ + // PERF For simplicity, the public Device functions make sure to set the CUDA context and wait for the previous operation to finish. + // Faster would be to do that only when needed, which means the caller would be responsible to do the proper synchronization, + // while the functions themselves work as asynchronously as possible. + activateContext(); + synchronizeStream(); + + const int numCameras = static_cast(cameras.size()); + MY_ASSERT(0 < numCameras); // There must be at least one camera defintion or the lens shaders won't work. + + // The default initialization of numCameras is 0. + if (m_systemData.numCameras != numCameras) + { + memFree(reinterpret_cast(m_systemData.cameraDefinitions)); + m_systemData.cameraDefinitions = reinterpret_cast(memAlloc(sizeof(CameraDefinition) * numCameras, 16)); + } + + // Update the camera data. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.cameraDefinitions), cameras.data(), sizeof(CameraDefinition) * numCameras, m_cudaStream) ); + m_systemData.numCameras = numCameras; + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::initLights(const std::vector& lightsGUI, const std::vector& geometryData, const unsigned int stride, const unsigned int index) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT((sizeof(LightDefinition) & 15) == 0); // Verify float4 alignment. + + const int numLights = static_cast(lightsGUI.size()); // This is allowed to be zero. + + // The default initialization of m_systemData.numLights is 0. + if (m_systemData.numLights != numLights) + { + memFree(reinterpret_cast(m_systemData.lightDefinitions)); + m_systemData.lightDefinitions = nullptr; + + m_systemData.lightDefinitions = (0 < numLights) ? reinterpret_cast(memAlloc(sizeof(LightDefinition) * numLights, 16)) : nullptr; + + m_lights.resize(numLights); + } + + for (int i = 0; i < numLights; ++i) + { + const LightGUI& lightGUI = lightsGUI[i]; // LightGUI data on the host. + LightDefinition& light = m_lights[i]; // LightDefinition data on the host in device layout. + + light.typeLight = lightGUI.typeLight; + light.idMaterial = lightGUI.idMaterial; + light.idObject = lightGUI.idObject; + + // My device side matrices are row-major left-multiplied and 3x4 for affine transformations. + // nvpro-pipeline matrices are row-major right-multiplied. operator~() is transpose. + memcpy(light.matrix, (~lightGUI.matrix).getPtr(), sizeof(float) * 12); + memcpy(light.matrixInv, (~lightGUI.matrixInv).getPtr(), sizeof(float) * 12); + + const dp::math::Mat33f rotation(lightGUI.orientation); + const dp::math::Mat33f rotationInv(lightGUI.orientationInv); + + memcpy(light.ori, (~rotation).getPtr(), sizeof(float) * 9); + memcpy(light.oriInv, (~rotationInv).getPtr(), sizeof(float) * 9); + + light.attributes = 0; + light.indices = 0; + light.textureEmission = 0; + light.textureProfile = 0; + light.cdfU = 0; // 2D, (width + 1) * height float elements. + light.cdfV = 0; // 1D, (height + 1) float elements. + light.emission = lightGUI.colorEmission * lightGUI.multiplierEmission; + light.width = 0; + light.height = 0; + light.area = lightGUI.area; + light.invIntegral = 1.0f; + light.spotAngleHalf = dp::math::degToRad(lightGUI.spotAngle * 0.5f); + light.spotExponent = lightGUI.spotExponent; + + if (!lightGUI.nameEmission.empty()) + { + std::map::const_iterator it = m_mapTextures.find(lightGUI.nameEmission); + MY_ASSERT(it != m_mapTextures.end()); + + const Texture* texture = it->second; + + light.textureEmission = texture->getTextureObject(); + light.cdfU = texture->getCDF_U(); + light.cdfV = texture->getCDF_V(); + light.width = texture->getWidth(); + light.height = texture->getHeight(); + light.invIntegral = 1.0f / texture->getIntegral(); + } + + if (light.typeLight == TYPE_LIGHT_MESH) + { + const GeometryData& geom = geometryData[lightGUI.idGeometry * stride + index]; + + light.attributes = geom.d_attributes; + light.indices = geom.d_indices; + + // Allocate and upload the areas and cdf data. + // Reusing the cdfU field. + // Note that mesh lights are not importance sampled over the emission texture. + // They are uniformly sampled over the light surface. + size_t sizeBytes = sizeof(float) * lightGUI.cdfAreas.size(); + light.cdfU = memAlloc(sizeBytes, 4); + CU_CHECK( cuMemcpyHtoDAsync(light.cdfU, lightGUI.cdfAreas.data(), sizeBytes, m_cudaStream) ); + + light.width = static_cast(lightGUI.cdfAreas.size() - 1); // The last element index in the CDF matches the number of triangles. + } + + if (light.typeLight == TYPE_LIGHT_IES) + { + if (!lightGUI.nameProfile.empty()) + { + std::map::const_iterator it = m_mapTextures.find(lightGUI.nameProfile); + MY_ASSERT(it != m_mapTextures.end()); + + const Texture* texture = it->second; + + light.textureProfile = texture->getTextureObject(); + } + } + } + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.lightDefinitions), m_lights.data(), sizeof(LightDefinition) * numLights, m_cudaStream) ); + m_systemData.numLights = numLights; + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + + +void Device::updateCamera(const int idCamera, const CameraDefinition& camera) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idCamera < m_systemData.numCameras); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.cameraDefinitions[idCamera]), &camera, sizeof(CameraDefinition), m_cudaStream) ); +} + +void Device::updateLight(const int idLight, const LightGUI& lightGUI) +{ + activateContext(); + synchronizeStream(); + + LightDefinition& light = m_lights[idLight]; + + // Curently only these material parameters affecting the light can be changed inside the GUI. + memcpy(light.matrix, (~lightGUI.matrix).getPtr(), sizeof(float) * 12); + memcpy(light.matrixInv, (~lightGUI.matrixInv).getPtr(), sizeof(float) * 12); + + const dp::math::Mat33f rotation(lightGUI.orientation); + const dp::math::Mat33f rotationInv(lightGUI.orientationInv); + + memcpy(light.ori, (~rotation).getPtr(), sizeof(float) * 9); + memcpy(light.oriInv, (~rotationInv).getPtr(), sizeof(float) * 9); + + light.emission = lightGUI.colorEmission * lightGUI.multiplierEmission; + light.spotAngleHalf = dp::math::degToRad(lightGUI.spotAngle * 0.5f); + light.spotExponent = lightGUI.spotExponent; + + MY_ASSERT(idLight < m_systemData.numLights); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.lightDefinitions[idLight]), &light, sizeof(LightDefinition), m_cudaStream) ); +} + +//void Device::updateLight(const int idLight, const LightDefinition& light) +//{ +// activateContext(); +// synchronizeStream(); +// +// MY_ASSERT(idLight < m_systemData.numLights); +// CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.lightDefinitions[idLight]), &light, sizeof(LightDefinition), m_cudaStream) ); +//} + +void Device::updateMaterial(const int idMaterial, const MaterialMDL* materialMDL) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idMaterial < m_materialDefinitions.size()); + + CU_CHECK( cuMemcpyHtoDAsync(m_materialDefinitions[idMaterial].arg_block, + materialMDL->getArgumentBlockData(), + materialMDL->getArgumentBlockSize(), + m_cudaStream) ); +} + +static int2 calculateTileShift(const int2 tileSize) +{ + int xShift = 0; + while (xShift < 32 && (tileSize.x & (1 << xShift)) == 0) + { + ++xShift; + } + + int yShift = 0; + while (yShift < 32 && (tileSize.y & (1 << yShift)) == 0) + { + ++yShift; + } + + MY_ASSERT(xShift < 32 && yShift < 32); // Can only happen for zero input. + + return make_int2(xShift, yShift); +} + + +void Device::setState(const DeviceState& state) +{ + activateContext(); + synchronizeStream(); + + // The system can switch dynamically betweeen brute force path tracing and direct lighting (next event estimation) + // That's used to compare direct lighting results with the normally correct brute force path tracing at runtime. + if (m_systemData.directLighting != state.directLighting) + { + m_systemData.directLighting = state.directLighting; + + m_isDirtySystemData = true; + } + + // Special handling from the previous DeviceMultiGPULocalCopy class. + if (m_systemData.resolution != state.resolution || + m_systemData.tileSize != state.tileSize) + { + if (1 < m_count) + { + // Calculate the new launch width for the tiled rendering. + // It must be a multiple of the tileSize width, otherwise the right-most tiles will not get filled correctly. + const int width = (state.resolution.x + m_count - 1) / m_count; + const int mask = state.tileSize.x - 1; + m_launchWidth = (width + mask) & ~mask; // == ((width + (tileSize - 1)) / tileSize.x) * tileSize.x; + } + else + { + // Single-GPU launch width is the same as the rendering resolution width. + m_launchWidth = state.resolution.x; + } + } + + if (m_systemData.resolution != state.resolution) + { + m_systemData.resolution = state.resolution; + + m_isDirtyOutputBuffer = true; + m_isDirtySystemData = true; + } + + if (m_systemData.tileSize != state.tileSize) + { + m_systemData.tileSize = state.tileSize; + m_systemData.tileShift = calculateTileShift(m_systemData.tileSize); + m_isDirtySystemData = true; + } + + if (m_systemData.samplesSqrt != state.samplesSqrt) + { + m_systemData.samplesSqrt = state.samplesSqrt; + + // Update the m_subFrames host index array. + const int spp = m_systemData.samplesSqrt * m_systemData.samplesSqrt; + + m_subFrames.resize(spp); + + for (int i = 0; i < spp; ++i) + { + m_subFrames[i] = i; + } + + m_isDirtySystemData = true; + } + + if (m_systemData.typeLens != state.typeLens) + { + m_systemData.typeLens = state.typeLens; + m_isDirtySystemData = true; + } + + if (m_systemData.pathLengths != state.pathLengths) + { + m_systemData.pathLengths = state.pathLengths; + m_isDirtySystemData = true; + } + + if (m_systemData.walkLength != state.walkLength) + { + m_systemData.walkLength = state.walkLength; + m_isDirtySystemData = true; + } + + if (m_systemData.sceneEpsilon != state.epsilonFactor * SCENE_EPSILON_SCALE) + { + m_systemData.sceneEpsilon = state.epsilonFactor * SCENE_EPSILON_SCALE; + m_isDirtySystemData = true; + } + + if (m_systemData.sdfIterations != state.sdfIterations) + { + m_systemData.sdfIterations = state.sdfIterations; + m_isDirtySystemData = true; + } + + if (m_systemData.sdfEpsilon != state.sdfEpsilon) + { + m_systemData.sdfEpsilon = state.sdfEpsilon; + m_isDirtySystemData = true; + } + + if (m_systemData.sdfOffset != state.sdfOffset) + { + m_systemData.sdfOffset = state.sdfOffset; + m_isDirtySystemData = true; + } + +#if USE_TIME_VIEW + if (m_systemData.clockScale != state.clockFactor * CLOCK_FACTOR_SCALE) + { + m_systemData.clockScale = state.clockFactor * CLOCK_FACTOR_SCALE; + m_isDirtySystemData = true; + } +#endif +} + + +GeometryData Device::createGeometry(std::shared_ptr geometry) +{ + activateContext(); + synchronizeStream(); + + GeometryData data; + + data.primitiveType = PT_TRIANGLES; + data.owner = m_index; + + const std::vector& attributes = geometry->getAttributes(); + const std::vector& indices = geometry->getIndices(); + + const size_t attributesSizeInBytes = sizeof(TriangleAttributes) * attributes.size(); + const size_t indicesSizeInBytes = sizeof(unsigned int) * indices.size(); + + data.d_attributes = memAlloc(attributesSizeInBytes, 16); + data.d_indices = memAlloc(indicesSizeInBytes, sizeof(unsigned int)); + + data.numAttributes = attributes.size(); + data.numIndices = indices.size(); + + CU_CHECK( cuMemcpyHtoDAsync(data.d_attributes, attributes.data(), attributesSizeInBytes, m_cudaStream) ); + CU_CHECK( cuMemcpyHtoDAsync(data.d_indices, indices.data(), indicesSizeInBytes, m_cudaStream) ); + + OptixBuildInput buildInput = {}; + + buildInput.type = OPTIX_BUILD_INPUT_TYPE_TRIANGLES; + + buildInput.triangleArray.vertexFormat = OPTIX_VERTEX_FORMAT_FLOAT3; + buildInput.triangleArray.vertexStrideInBytes = sizeof(TriangleAttributes); + buildInput.triangleArray.numVertices = static_cast(attributes.size()); + buildInput.triangleArray.vertexBuffers = &data.d_attributes; + + buildInput.triangleArray.indexFormat = OPTIX_INDICES_FORMAT_UNSIGNED_INT3; + buildInput.triangleArray.indexStrideInBytes = sizeof(unsigned int) * 3; + + buildInput.triangleArray.numIndexTriplets = static_cast(indices.size()) / 3; + buildInput.triangleArray.indexBuffer = data.d_indices; + + unsigned int inputFlags[1] = { OPTIX_GEOMETRY_FLAG_NONE }; + + buildInput.triangleArray.flags = inputFlags; + buildInput.triangleArray.numSbtRecords = 1; + + //std::cout << "createGeometry(Triangles) device index = " << m_index + // << ": numVertices = " << buildInput.triangleArray.numVertices + // << ", numIndexTriplets = " << buildInput.triangleArray.numIndexTriplets << '\n'; // DEBUG + + OptixAccelBuildOptions accelBuildOptions = {}; + + accelBuildOptions.buildFlags = OPTIX_BUILD_FLAG_ALLOW_COMPACTION; + if (m_count == 1) + { + // PERF Enable OPTIX_BUILD_FLAG_PREFER_FAST_TRACE on single-GPU only. + // Note that OPTIX_BUILD_FLAG_PREFER_FAST_TRACE will use more memory, + // which performs worse when sharing across the NVLINK bridge which is much slower than VRAM accesses. + // This means comparisons between single-GPU and multi-GPU are not doing exactly the same! + accelBuildOptions.buildFlags |= OPTIX_BUILD_FLAG_PREFER_FAST_TRACE; + } + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &buildInput, 1, &accelBufferSizes) ); + + data.d_gas = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); // This is a temporary buffer. The Compaction will be the static one! + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OptixAccelEmitDesc accelEmit = {}; + + accelEmit.result = memAlloc(sizeof(size_t), sizeof(size_t), cuda::USAGE_TEMP); + accelEmit.type = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE; + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &buildInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + data.d_gas, accelBufferSizes.outputSizeInBytes, + &data.traversable, &accelEmit, 1) ); + + size_t sizeCompact; + + CU_CHECK( cuMemcpyDtoHAsync(&sizeCompact, accelEmit.result, sizeof(size_t), m_cudaStream) ); + + synchronizeStream(); + + memFree(accelEmit.result); + memFree(d_tmp); + + // Compact the AS only when possible. This can save more than half the memory on RTX boards. + if (sizeCompact < accelBufferSizes.outputSizeInBytes) + { + CUdeviceptr d_gasCompact = memAlloc(sizeCompact, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); // This is the static GAS allocation! + + OPTIX_CHECK( m_api.optixAccelCompact(m_optixContext, m_cudaStream, data.traversable, d_gasCompact, sizeCompact, &data.traversable) ); + + synchronizeStream(); // Must finish accessing data.d_gas source before it can be freed and overridden. + + memFree(data.d_gas); + + data.d_gas = d_gasCompact; + + //std::cout << "Compaction saved " << accelBufferSizes.outputSizeInBytes - sizeCompact << '\n'; // DEBUG + accelBufferSizes.outputSizeInBytes = sizeCompact; // DEBUG for the std::cout below. + } + + // Return the relocation info for this GAS traversable handle from this device's OptiX context. + // It's used to assert that the GAS is compatible across devices which means NVLINK peer-to-peer sharing is allowed. + // (This is more meant as example code, because in NVLINK islands the GPU configuration must be homogeneous and addresses are unique with UVA.) + OPTIX_CHECK( m_api.optixAccelGetRelocationInfo(m_optixContext, data.traversable, &data.info) ); + + //std::cout << "createGeometry() device ordinal = " << m_ordinal << ": attributes = " << attributesSizeInBytes << ", indices = " << indicesSizeInBytes << ", GAS = " << accelBufferSizes.outputSizeInBytes << "\n"; // DEBUG + + return data; +} + + +GeometryData Device::createGeometry(std::shared_ptr geometry) +{ + activateContext(); + synchronizeStream(); + + GeometryData data; + + data.primitiveType = PT_CURVES; + data.owner = m_index; + + const std::vector& attributes = geometry->getAttributes(); + const std::vector& indices = geometry->getIndices(); + + const size_t attributesSizeInBytes = sizeof(CurveAttributes) * attributes.size(); + + data.d_attributes = memAlloc(attributesSizeInBytes, 16); + data.numAttributes = attributes.size(); + + CU_CHECK( cuMemcpyHtoDAsync(data.d_attributes, attributes.data(), attributesSizeInBytes, m_cudaStream) ); + + CUdeviceptr d_radii = data.d_attributes + sizeof(float3); // Pointer to the radius in the .w component of the float4 vertex attribute + + const size_t indicesSizeInBytes = sizeof(unsigned int) * indices.size(); + + data.d_indices = memAlloc(indicesSizeInBytes, sizeof(unsigned int)); + data.numIndices = indices.size(); + + CU_CHECK( cuMemcpyHtoDAsync(data.d_indices, indices.data(), indicesSizeInBytes, m_cudaStream) ); + + OptixBuildInput buildInput = {}; + + buildInput.type = OPTIX_BUILD_INPUT_TYPE_CURVES; + + buildInput.curveArray.curveType = OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE; + buildInput.curveArray.numPrimitives = static_cast(indices.size()); + buildInput.curveArray.vertexBuffers = &data.d_attributes; + buildInput.curveArray.numVertices = static_cast(attributes.size()); + buildInput.curveArray.vertexStrideInBytes = sizeof(CurveAttributes); + buildInput.curveArray.widthBuffers = &d_radii; + buildInput.curveArray.widthStrideInBytes = sizeof(CurveAttributes); + buildInput.curveArray.normalBuffers = nullptr; // Reserved for future use + buildInput.curveArray.normalStrideInBytes = 0; // Reserved for future use + buildInput.curveArray.indexBuffer = data.d_indices; + buildInput.curveArray.indexStrideInBytes = sizeof(unsigned int); + buildInput.curveArray.flag = OPTIX_GEOMETRY_FLAG_NONE; // Only one flag because Curves have only one SBT entry. + buildInput.curveArray.primitiveIndexOffset = 0; + + //std::cout << "createGeometry(Curves) device index = " << m_index + // << ": numPrimitves = " << buildInput.curveArray.numPrimitives + // << ": numVertices = " << buildInput.curveArray.numVertices << '\n'; // DEBUG + + OptixAccelBuildOptions accelBuildOptions = {}; + + accelBuildOptions.buildFlags = OPTIX_BUILD_FLAG_ALLOW_COMPACTION; + if (m_count == 1) + { + // PERF Enable OPTIX_BUILD_FLAG_PREFER_FAST_TRACE on single-GPU only. + // Note that OPTIX_BUILD_FLAG_PREFER_FAST_TRACE will use more memory, + // which performs worse when sharing across the NVLINK bridge which is much slower than VRAM accesses. + // This means comparisons between single-GPU and multi-GPU are not doing exactly the same! + accelBuildOptions.buildFlags |= OPTIX_BUILD_FLAG_PREFER_FAST_TRACE; + } + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &buildInput, 1, &accelBufferSizes) ); + + data.d_gas = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); // This is a temporary buffer. The Compaction will be the static one! + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OptixAccelEmitDesc accelEmit = {}; + + accelEmit.result = memAlloc(sizeof(size_t), sizeof(size_t), cuda::USAGE_TEMP); + accelEmit.type = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE; + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &buildInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + data.d_gas, accelBufferSizes.outputSizeInBytes, + &data.traversable, &accelEmit, 1) ); + + size_t sizeCompact; + + CU_CHECK( cuMemcpyDtoHAsync(&sizeCompact, accelEmit.result, sizeof(size_t), m_cudaStream) ); + + synchronizeStream(); + + memFree(accelEmit.result); + memFree(d_tmp); + + // Compact the AS only when possible. This can save more than half the memory on RTX boards. + if (sizeCompact < accelBufferSizes.outputSizeInBytes) + { + CUdeviceptr d_gasCompact = memAlloc(sizeCompact, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); // This is the static GAS allocation! + + OPTIX_CHECK( m_api.optixAccelCompact(m_optixContext, m_cudaStream, data.traversable, d_gasCompact, sizeCompact, &data.traversable) ); + + synchronizeStream(); // Must finish accessing data.d_gas source before it can be freed and overridden. + + memFree(data.d_gas); + + data.d_gas = d_gasCompact; + + //std::cout << "Compaction saved " << accelBufferSizes.outputSizeInBytes - sizeCompact << '\n'; // DEBUG + accelBufferSizes.outputSizeInBytes = sizeCompact; // DEBUG for the std::cout below. + } + + // Return the relocation info for this GAS traversable handle from this device's OptiX context. + // It's used to assert that the GAS is compatible across devices which means NVLINK peer-to-peer sharing is allowed. + // (This is more meant as example code, because in NVLINK islands the GPU configuration must be homogeneous and addresses are unique with UVA.) + OPTIX_CHECK( m_api.optixAccelGetRelocationInfo(m_optixContext, data.traversable, &data.info) ); + + //std::cout << "createGeometry() device index = " << m_index << ": attributes = " << attributesSizeInBytes << ", indices = " << indicesSizeInBytes << ", GAS = " << accelBufferSizes.outputSizeInBytes << "\n"; // DEBUG + + return data; +} + + +GeometryData Device::createGeometry(std::shared_ptr geometry) +{ + activateContext(); + synchronizeStream(); + + GeometryData data; + + data.primitiveType = PT_SDF; + data.owner = m_index; + + // We need to provide the SDF 3D texture object inside the SignedDistanceFieldAttributes, but it's not known before this point, + // so get a copy of the attributes which is just a single structure and manipulate the sdfTexture field inside it before uploading to the device. + std::vector attributes = geometry->getAttributes(); + MY_ASSERT(attributes.size() == 1); // There is only one element in an SDF primtive at this time. + + std::map::const_iterator it = m_mapTextures.find(geometry->getFilename()); + MY_ASSERT(it != m_mapTextures.end()); + if (it != m_mapTextures.end()) + { + attributes[0].sdfTexture = it->second->getTextureObject(); + } + + const size_t attributesSizeInBytes = sizeof(SignedDistanceFieldAttributes) * attributes.size(); + + data.d_attributes = memAlloc(attributesSizeInBytes, 16); + data.numAttributes = attributes.size(); + + CU_CHECK( cuMemcpyHtoDAsync(data.d_attributes, attributes.data(), attributesSizeInBytes, m_cudaStream) ); + + OptixBuildInput buildInput = {}; + + buildInput.type = OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES; + + buildInput.customPrimitiveArray.aabbBuffers = &data.d_attributes; + buildInput.customPrimitiveArray.numPrimitives = data.numAttributes; + buildInput.customPrimitiveArray.strideInBytes = sizeof(SignedDistanceFieldAttributes); + + unsigned int inputFlags[1] = { OPTIX_GEOMETRY_FLAG_NONE }; + + buildInput.customPrimitiveArray.flags = inputFlags; + buildInput.customPrimitiveArray.numSbtRecords = 1; + + //buildInput.customPrimitiveArray.sbtIndexOffsetBuffer = 0; + //buildInput.customPrimitiveArray.sbtIndexOffsetSizeInBytes = 0; + //buildInput.customPrimitiveArray.sbtIndexOffsetStrideInBytes = 0; + //buildInput.customPrimitiveArray.primitiveIndexOffset = 0; + + OptixAccelBuildOptions accelBuildOptions = {}; + + accelBuildOptions.buildFlags = OPTIX_BUILD_FLAG_NONE; // There is only a single AABB inside the SDF. No need for compaction. + if (m_count == 1) + { + // PERF Enable OPTIX_BUILD_FLAG_PREFER_FAST_TRACE on single-GPU only. + // Note that OPTIX_BUILD_FLAG_PREFER_FAST_TRACE will use more memory, + // which performs worse when sharing across the NVLINK bridge which is much slower than VRAM accesses. + // This means comparisons between single-GPU and multi-GPU are not doing exactly the same! + accelBuildOptions.buildFlags |= OPTIX_BUILD_FLAG_PREFER_FAST_TRACE; + } + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &buildInput, 1, &accelBufferSizes) ); + + data.d_gas = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_STATIC); + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &buildInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + data.d_gas, accelBufferSizes.outputSizeInBytes, + &data.traversable, nullptr, 0)); + + synchronizeStream(); + + memFree(d_tmp); + + // Return the relocation info for this GAS traversable handle from this device's OptiX context. + // It's used to assert that the GAS is compatible across devices which means NVLINK peer-to-peer sharing is allowed. + // (This is more meant as example code, because in NVLINK islands the GPU configuration must be homogeneous and addresses are unique with UVA.) + OPTIX_CHECK( m_api.optixAccelGetRelocationInfo(m_optixContext, data.traversable, &data.info) ); + + //std::cout << "createGeometry(SDF) device index = " << m_index << ": attributes = " << attributesSizeInBytes << ", GAS = " << accelBufferSizes.outputSizeInBytes << " [bytes]\n"; // DEBUG + + return data; +} + + +void Device::destroyGeometry(GeometryData& data) +{ + memFree(data.d_gas); + memFree(data.d_indices); + memFree(data.d_attributes); +} + +void Device::createInstance(const GeometryData& geometryData, const InstanceData& instanceData, const float matrix[12]) +{ + activateContext(); + synchronizeStream(); + + // If the GeometryData is owned by a different device, that means it has been created in a different OptiX context. + // Then check if the data is compatible with the OptiX context on this device. + // If yes, it can be shared via peer-to-peer as well because the device pointers are all unique with UVA. It's not actually relocated. + // If not, no instance with this geometry data is created and it'll be missing from the rendering. + // Same when there is no valid material or shader index assigned. + if (m_index != geometryData.owner) // No need to check compatibility on the same device. + { + int compatible = 0; + +#if (OPTIX_VERSION >= 70600) + OPTIX_CHECK( m_api.optixCheckRelocationCompatibility(m_optixContext, &geometryData.info, &compatible) ); +#else + OPTIX_CHECK( m_api.optixAccelCheckRelocationCompatibility(m_optixContext, &geometryData.info, &compatible) ); +#endif + + if (compatible == 0) + { + std::cerr << "ERROR: createInstance() device index " << m_index << " is not AS-compatible with the GeometryData owner " << geometryData.owner << ". Instance ignored!\n"; + MY_ASSERT(!"createInstance() AS incompatible"); + return; // This means this geometry will not actually be present in the OptiX render graph of this device! + } + } + + // First check if there is a valid material assigned to this instance. + const int idMaterial = instanceData.idMaterial; + + if (idMaterial < 0 || static_cast(m_materialDefinitions.size()) < idMaterial) + { + std::cerr << "ERROR: createInstance() idMaterial " << idMaterial << " is invalid. Instance ignored!\n"; + MY_ASSERT(!"createInstance() idMaterial invalid."); + return; + } + + // Then check if we actually have a valid shader compiled for this material. + const int indexShader = m_materialDefinitions[idMaterial].indexShader; + + if (indexShader < 0 || static_cast(m_deviceShaderConfigurations.size()) < indexShader) + { + std::cerr << "ERROR: createInstance() indexShader " << indexShader << " is invalid. Instance ignored!\n"; + MY_ASSERT(!"createInstance() indexShader invalid."); + return; + } + + OptixInstance instance = {}; + + const unsigned int id = static_cast(m_instances.size()); + memcpy(instance.transform, matrix, sizeof(float) * 12); + instance.instanceId = id; // User defined instance index, queried with optixGetInstanceId(). + instance.visibilityMask = 255; + + // PERF Determine which hit record to use. + // Triangles: Hit records 0 to 3. (no emission, emission) x (no cutout, cutout) + // Cubic B-splines: Hit record 4. + + unsigned int hitRecord = 0; + + if (geometryData.primitiveType == PT_SDF) + { + hitRecord = 5; // Custom primitive SDF. + } + else if (geometryData.primitiveType == PT_CURVES) + { + hitRecord = 4; // Cubic B-spline curves. + } + else + { + hitRecord |= ((m_deviceShaderConfigurations[indexShader].flags & USE_EMISSION ) == 0) ? 0 : 1; // no emission, emission + hitRecord |= ((m_deviceShaderConfigurations[indexShader].flags & USE_CUTOUT_OPACITY) == 0) ? 0 : 2; // no cutout , cutout + } + + instance.sbtOffset = NUM_RAY_TYPES * hitRecord; + instance.flags = OPTIX_INSTANCE_FLAG_NONE; + instance.traversableHandle = geometryData.traversable; + + m_instances.push_back(instance); // OptixInstance data + + m_instanceData.push_back(instanceData); // Per instance data, indexed with instanceId: idGeometry, idMaterial, idLight, idObject. +} + + +void Device::createTLAS() +{ + activateContext(); + synchronizeStream(); + + // Construct the TLAS by attaching all flattened instances. + const size_t instancesSizeInBytes = sizeof(OptixInstance) * m_instances.size(); + + CUdeviceptr d_instances = memAlloc(instancesSizeInBytes, OPTIX_INSTANCE_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + CU_CHECK( cuMemcpyHtoDAsync(d_instances, m_instances.data(), instancesSizeInBytes, m_cudaStream) ); + + OptixBuildInput instanceInput = {}; + + instanceInput.type = OPTIX_BUILD_INPUT_TYPE_INSTANCES; + instanceInput.instanceArray.instances = d_instances; + instanceInput.instanceArray.numInstances = static_cast(m_instances.size()); + + OptixAccelBuildOptions accelBuildOptions = {}; + + accelBuildOptions.buildFlags = OPTIX_BUILD_FLAG_NONE; + if (m_count == 1) + { + accelBuildOptions.buildFlags |= OPTIX_BUILD_FLAG_PREFER_FAST_TRACE; + } + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &instanceInput, 1, &accelBufferSizes ) ); + + m_d_ias = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &instanceInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + m_d_ias, accelBufferSizes.outputSizeInBytes, + &m_systemData.topObject, nullptr, 0)); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); + + memFree(d_tmp); + memFree(d_instances); +} + + +void Device::createGeometryInstanceData(const std::vector& geometryData, const unsigned int stride, const unsigned int index) +{ + activateContext(); + synchronizeStream(); + + const unsigned int numInstances = static_cast(m_instances.size()); + + m_geometryInstanceData.resize(numInstances); + + for (unsigned int i = 0; i < numInstances; ++i) + { + const InstanceData& inst = m_instanceData[i]; + const GeometryData& geom = geometryData[inst.idGeometry * stride + index]; // This addressing supports both peer-to-peer shared and non-shared GAS. + + GeometryInstanceData& gid = m_geometryInstanceData[i]; + + gid.ids = make_int4(inst.idMaterial, inst.idLight, inst.idObject, 0); + gid.attributes = geom.d_attributes; + gid.indices = geom.d_indices; + } + + m_d_geometryInstanceData = reinterpret_cast(memAlloc(sizeof(GeometryInstanceData) * numInstances, 16) ); // int4 requires 16 byte alignment. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_d_geometryInstanceData), m_geometryInstanceData.data(), sizeof(GeometryInstanceData) * numInstances, m_cudaStream) ); + + m_systemData.geometryInstanceData = m_d_geometryInstanceData; +} + + +// Given an OpenGL UUID find the matching CUDA device. +bool Device::matchUUID(const char* uuid) +{ + for (size_t i = 0; i < 16; ++i) + { + if (m_deviceUUID.bytes[i] != uuid[i]) + { + return false; + } + } + return true; +} + +// Given an OpenGL LUID find the matching CUDA device. +bool Device::matchLUID(const char* luid, const unsigned int nodeMask) +{ + if ((m_nodeMask & nodeMask) == 0) + { + return false; + } + for (size_t i = 0; i < 8; ++i) + { + if (m_deviceLUID[i] != luid[i]) + { + return false; + } + } + return true; +} + + +void Device::activateContext() const +{ + CU_CHECK( cuCtxSetCurrent(m_cudaContext) ); +} + +void Device::synchronizeStream() const +{ + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); +} + +void Device::render(const unsigned int iterationIndex, void** buffer, const int mode) +{ + activateContext(); + + m_systemData.iterationIndex = iterationIndex; + + if (m_isDirtyOutputBuffer) + { + MY_ASSERT(buffer != nullptr); + if (*buffer == nullptr) // The buffer is nullptr for the device which should allocate the full resolution buffers. This device is called first! + { + // Only allocate the host buffer once, not per each device. + m_bufferHost.resize(m_systemData.resolution.x * m_systemData.resolution.y); + + // Note that this requires that all other devices have finished accessing this buffer, but that is automatically the case + // after calling Device::setState() which is the only place which can change the resolution. + memFree(m_systemData.outputBuffer); // This is asynchronous and the pointer can be 0. +#if USE_FP32_OUTPUT + m_systemData.outputBuffer = memAlloc(sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, sizeof(float4)); +#else + m_systemData.outputBuffer = memAlloc(sizeof(Half4) * m_systemData.resolution.x * m_systemData.resolution.y, sizeof(Half4)); +#endif + + *buffer = reinterpret_cast(m_systemData.outputBuffer); // Set the pointer, so that other devices don't allocate it. It's not shared! + + if (1 < m_count) + { + // This is a temporary buffer on the primary board which is used by the compositor. The texelBuffer needs to stay intact for the accumulation. + memFree(m_systemData.tileBuffer); +#if USE_FP32_OUTPUT + m_systemData.tileBuffer = memAlloc(sizeof(float4) * m_launchWidth * m_systemData.resolution.y, sizeof(float4)); +#else + m_systemData.tileBuffer = memAlloc(sizeof(Half4) * m_launchWidth * m_systemData.resolution.y, sizeof(Half4)); +#endif + m_d_compositorData = memAlloc(sizeof(CompositorData), 16); + } + + m_ownsSharedBuffer = true; // Indicate which device owns the m_systemData.outputBuffer and m_bufferHost so that display routines can assert. + + if (m_cudaGraphicsResource != nullptr) // Need to unregister texture or PBO before resizing it. + { + CU_CHECK( cuGraphicsUnregisterResource(m_cudaGraphicsResource) ); + } + + switch (m_interop) + { + case INTEROP_MODE_OFF: + break; + + case INTEROP_MODE_TEX: + // Let the device which is called first resize the OpenGL texture. +#if USE_FP32_OUTPUT + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, (GLvoid*) m_bufferHost.data()); // RGBA32F +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_HALF_FLOAT_ARB, (GLvoid*) m_bufferHost.data()); // RGBA16F +#endif + glFinish(); // Synchronize with following CUDA operations. + + CU_CHECK( cuGraphicsGLRegisterImage(&m_cudaGraphicsResource, m_tex, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD) ); + break; + + case INTEROP_MODE_PBO: + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); +#if USE_FP32_OUTPUT + glBufferData(GL_PIXEL_UNPACK_BUFFER, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4), nullptr, GL_DYNAMIC_DRAW); +#else + glBufferData(GL_PIXEL_UNPACK_BUFFER, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(Half4), nullptr, GL_DYNAMIC_DRAW); +#endif + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + CU_CHECK( cuGraphicsGLRegisterBuffer(&m_cudaGraphicsResource, m_pbo, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD) ); + break; + } + } + + if (1 < m_count) + { + // Allocate a GPU local buffer in the per-device launch size. This is where the accumulation happens. + memFree(m_systemData.texelBuffer); +#if USE_FP32_OUTPUT + m_systemData.texelBuffer = memAlloc(sizeof(float4) * m_launchWidth * m_systemData.resolution.y, sizeof(float4)); +#else + m_systemData.texelBuffer = memAlloc(sizeof(Half4) * m_launchWidth * m_systemData.resolution.y, sizeof(Half4)); +#endif + } + + m_isDirtyOutputBuffer = false; // Buffer is allocated with new size. + m_isDirtySystemData = true; // Now the sysData on the device needs to be updated, and that needs a sync! + } + + if (m_isDirtySystemData) // Update the whole SystemData block because more than the iterationIndex changed. This normally means a GUI interaction. Just sync. + { + synchronizeStream(); + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_d_systemData), &m_systemData, sizeof(SystemData), m_cudaStream) ); + m_isDirtySystemData = false; + } + else // Just copy the new iterationIndex. + { + if (mode == 0) // Fully asynchronous launches ruin the interactivity. Synchronize in interactive mode. + { + synchronizeStream(); + } + // PERF For really asynchronous copies of the iteration indices, multiple source pointers are required. Good that I know the number of iterations upfront! + // Using the m_subFrames array as source pointers. Just contains the identity of the index. Updating the device side sysData.iterationIndex from there. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_d_systemData->iterationIndex), &m_subFrames[m_systemData.iterationIndex], sizeof(unsigned int), m_cudaStream) ); + } + + // Note the launch width per device to render in tiles. + OPTIX_CHECK( m_api.optixLaunch(m_pipeline, m_cudaStream, reinterpret_cast(m_d_systemData), sizeof(SystemData), &m_sbt, m_launchWidth, m_systemData.resolution.y, /* depth */ 1) ); +} + + +void Device::updateDisplayTexture() +{ + activateContext(); + + // Only allow this on the device which owns the shared peer-to-peer buffer which also resized the host buffer to copy this to the host. + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer && m_tex != 0); + + switch (m_interop) + { + case INTEROP_MODE_OFF: + // Copy the GPU local render buffer into host and update the HDR texture image from there. +#if USE_FP32_OUTPUT + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); +#else + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(Half4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); +#endif + synchronizeStream(); // Wait for the buffer to arrive on the host. + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_tex); +#if USE_FP32_OUTPUT + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, m_bufferHost.data()); // RGBA32F from host buffer data. +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_HALF_FLOAT_ARB, m_bufferHost.data()); // RGBA16F from host buffer data. +#endif + break; + + case INTEROP_MODE_TEX: + { + // Map the Texture object directly and copy the output buffer. + CU_CHECK( cuGraphicsMapResources(1, &m_cudaGraphicsResource, m_cudaStream )); // This is an implicit cuSynchronizeStream(). + + CUarray dstArray = nullptr; + + CU_CHECK( cuGraphicsSubResourceGetMappedArray(&dstArray, m_cudaGraphicsResource, 0, 0) ); // arrayIndex = 0, mipLevel = 0 + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_DEVICE; + params.srcDevice = m_systemData.outputBuffer; +#if USE_FP32_OUTPUT + params.srcPitch = m_systemData.resolution.x * sizeof(float4); // RGBA32F +#else + params.srcPitch = m_systemData.resolution.x * sizeof(Half4); // RGBA16F +#endif + params.srcHeight = m_systemData.resolution.y; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = dstArray; +#if USE_FP32_OUTPUT + params.WidthInBytes = m_systemData.resolution.x * sizeof(float4); +#else + params.WidthInBytes = m_systemData.resolution.x * sizeof(Half4); +#endif + params.Height = m_systemData.resolution.y; + params.Depth = 1; + + CU_CHECK( cuMemcpy3D(¶ms) ); // Copy from linear to array layout. + + CU_CHECK( cuGraphicsUnmapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). + } + break; + + case INTEROP_MODE_PBO: // This contains two device-to-device copies and is just for demonstration. Use INTEROP_MODE_TEX when possible. + { + size_t size = 0; + CUdeviceptr d_ptr; + + CU_CHECK( cuGraphicsMapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). + CU_CHECK( cuGraphicsResourceGetMappedPointer(&d_ptr, &size, m_cudaGraphicsResource) ); // The pointer can change on every map! + // PERF PBO interop is kind of moot with a direct texture access. +#if USE_FP32_OUTPUT + MY_ASSERT(m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4) <= size); + CU_CHECK( cuMemcpyDtoDAsync(d_ptr, m_systemData.outputBuffer, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4), m_cudaStream) ); +#else + MY_ASSERT(m_systemData.resolution.x * m_systemData.resolution.y * sizeof(Half4) <= size); + CU_CHECK( cuMemcpyDtoDAsync(d_ptr, m_systemData.outputBuffer, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(Half4), m_cudaStream) ); +#endif + CU_CHECK( cuGraphicsUnmapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_tex); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); +#if USE_FP32_OUTPUT + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, (GLvoid*) 0); // RGBA32F from byte offset 0 in the pixel unpack buffer. +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_HALF_FLOAT_ARB, (GLvoid*) 0); // RGBA32F from byte offset 0 in the pixel unpack buffer. +#endif + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + break; + } +} + + +const void* Device::getOutputBufferHost() +{ + activateContext(); + + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer); // Only allow this on the device which owns the shared peer-to-peer buffer and resized the host buffer to copy this to the host. + + // Note that the caller takes care to sync the other devices before calling into here or this image might not be complete! +#if USE_FP32_OUTPUT + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); +#else + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(Half4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); +#endif + + synchronizeStream(); // Wait for the buffer to arrive on the host. + + return m_bufferHost.data(); +} + +// PERF This is NOT called when there is only one active device! +// That is using a different ray generation program instead which accumulates directly into the output buffer. +void Device::compositor(Device* other) +{ + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer); + + // The compositor sources the tileBuffer, which is only allocated on the primary device. + // The texelBuffer is a GPU local buffer on all devices and contains the accumulation. + if (this == other) + { + activateContext(); +#if USE_FP32_OUTPUT + CU_CHECK( cuMemcpyDtoDAsync(m_systemData.tileBuffer, m_systemData.texelBuffer, + sizeof(float4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); +#else + CU_CHECK( cuMemcpyDtoDAsync(m_systemData.tileBuffer, m_systemData.texelBuffer, + sizeof(Half4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); +#endif + } + else + { + // Make sure the other device has finished rendering! Otherwise there can be checkerboard corruption visible. + other->activateContext(); + other->synchronizeStream(); + + activateContext(); +#if USE_FP32_OUTPUT + CU_CHECK( cuMemcpyPeerAsync(m_systemData.tileBuffer, m_cudaContext, other->m_systemData.texelBuffer, other->m_cudaContext, + sizeof(float4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); +#else + CU_CHECK( cuMemcpyPeerAsync(m_systemData.tileBuffer, m_cudaContext, other->m_systemData.texelBuffer, other->m_cudaContext, + sizeof(Half4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); +#endif + } + + CompositorData compositorData; // FIXME This would need to be persistent per Device to allow async copies! + + compositorData.outputBuffer = m_systemData.outputBuffer; + compositorData.tileBuffer = m_systemData.tileBuffer; + compositorData.resolution = m_systemData.resolution; + compositorData.tileSize = m_systemData.tileSize; + compositorData.tileShift = m_systemData.tileShift; + compositorData.launchWidth = m_launchWidth; + compositorData.deviceCount = m_systemData.deviceCount; + compositorData.deviceIndex = other->m_systemData.deviceIndex; // This is the only value which changes per device. + + // Need a synchronous copy here to not overwrite or delete the compositorData above. + CU_CHECK( cuMemcpyHtoD(m_d_compositorData, &compositorData, sizeof(CompositorData)) ); + + void* args[1] = { &m_d_compositorData }; + + // FIXME PERF Should this be 32x32 for 1024 threads? + const int blockDimX = std::min(compositorData.tileSize.x, 16); + const int blockDimY = std::min(compositorData.tileSize.y, 16); + + const int gridDimX = (m_launchWidth + blockDimX - 1) / blockDimX; + const int gridDimY = (compositorData.resolution.y + blockDimY - 1) / blockDimY; + + MY_ASSERT(gridDimX <= m_deviceAttribute.maxGridDimX && + gridDimY <= m_deviceAttribute.maxGridDimY); + + // Reduction kernel with launch dimension of height blocks with 32 threads. + CU_CHECK( cuLaunchKernel(m_functionCompositor, // CUfunction f, + gridDimX, // unsigned int gridDimX, + gridDimY, // unsigned int gridDimY, + 1, // unsigned int gridDimZ, + blockDimX, // unsigned int blockDimX, + blockDimY, // unsigned int blockDimY, + 1, // unsigned int blockDimZ, + 0, // unsigned int sharedMemBytes, + m_cudaStream, // CUstream hStream, + args, // void **kernelParams, + nullptr) ); // void **extra + + synchronizeStream(); +} + + +// Arena version of cuMemAlloc(), but asynchronous! +CUdeviceptr Device::memAlloc(const size_t size, const size_t alignment, const cuda::Usage usage) +{ + return m_allocator->alloc(size, alignment, usage); +} + +// Arena version of cuMemFree(), but asynchronous! +void Device::memFree(const CUdeviceptr ptr) +{ + m_allocator->free(ptr); +} + +// This is getting the current VRAM situation on the device. +// Means this includes everything running on the GPU and all allocations done for textures and the ArenaAllocator. +// Currently not used for picking the home device for the next shared allocation, because with the ArenaAllocator that isn't fine grained. +// Instead getMemoryAllocated() is used to return the sum of all allocated blocks inside arenas and the texture sizes in bytes. +size_t Device::getMemoryFree() const +{ + activateContext(); + + size_t sizeFree = 0; + size_t sizeTotal = 0; + + CU_CHECK( cuMemGetInfo(&sizeFree, &sizeTotal) ); + + return sizeFree; +} + +// getMemoryAllocated() returns the sum of all allocated blocks inside arenas and the texture sizes in bytes (without GPU alignment and padding). +// Using this in Raytracer::getDeviceHome() assumes the free VRAM amount is about equal on the devices in an island. +size_t Device::getMemoryAllocated() const +{ + return m_allocator->getSizeMemoryAllocated() + m_sizeMemoryTextureArrays; +} + +Texture* Device::initTexture(const std::string& name, const Picture* picture, const unsigned int flags) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + Texture* texture; + + // FIXME Only using the filename as key and not the load flags. This will not support the same image with different flags! + std::map::const_iterator it = m_mapTextures.find(name); + if (it == m_mapTextures.end()) + { + texture = new Texture(this); // This device is the owner of the CUarray or CUmipmappedArray data. + + texture->create(picture, flags); + + m_sizeMemoryTextureArrays += texture->getSizeBytes(); // Texture memory tracking. + + m_mapTextures[name] = texture; + + //std::cout << "initTexture() device ordinal = " << m_ordinal << ": name = " << name << '\n'; // DEBUG + } + else + { + texture = it->second; // Return the existing texture under this name. + + //std::cout << "initTexture() Texture " << name << " reused\n"; // DEBUG + } + + return texture; // Not used when not sharing. +} + + +void Device::shareTexture(const std::string& name, const Texture* shared) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + std::map::const_iterator it = m_mapTextures.find(name); + + if (it == m_mapTextures.end()) + { + Texture* texture = new Texture(shared->getOwner()); + + texture->create(shared); // No texture memory tracking in this case. Arrays are reused. + + m_mapTextures[name] = texture; + } +} + + +unsigned int Device::appendProgramGroupMDL(const int indexModule, const std::string& nameFunction) +{ + OptixProgramGroupDesc pgd = {}; + + pgd.kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd.flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd.callables.moduleDC = m_modulesMDL[indexModule]; + pgd.callables.entryFunctionNameDC = nameFunction.c_str(); + + OptixProgramGroup pg = {}; + + OPTIX_CHECK( m_api.optixProgramGroupCreate(m_optixContext, &pgd, 1u, &m_pgo, nullptr, nullptr, &pg) ); + + // Add the call offset skipping the lens shader and light sample callables on the host. + const unsigned int idx = static_cast(m_programGroupsMDL.size()) + CALL_OFFSET; + + m_programGroupsMDL.push_back(pg); + + return idx; +} + + +// Compile_result and MaterialMDL is per reference, ShaderConfiguration is per shader (code). +void Device::compileMaterial(mi::neuraylib::ITransaction* transaction, + MaterialMDL* material, + const Compile_result& res, + const ShaderConfiguration& config) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + // This function is called per reference because it needs to allocate and store the parameter argument block per reference. + // Though the shader code, resp. the callable program indices need to be reused. + const int indexShader = material->getShaderIndex(); + + const std::string suffix = std::to_string(indexShader); + + DeviceShaderConfiguration dsc = {}; + + // Set all callable indices to the invalid value -1. + // The MDL code generator will generate all functions by default (sample, evaluate, pdf), + // but pdf functions are disabled with backend set_option("enable_pdf", "off") + // This is only containing the direct callables which are required inside the pipeline of this unidirectional path tracer. + + dsc.idxCallInit = -1; + + dsc.idxCallThinWalled = -1; + + dsc.idxCallSurfaceScatteringSample = -1; + dsc.idxCallSurfaceScatteringEval = -1; + + dsc.idxCallBackfaceScatteringSample = -1; + dsc.idxCallBackfaceScatteringEval = -1; + + dsc.idxCallSurfaceEmissionEval = -1; + dsc.idxCallSurfaceEmissionIntensity = -1; + dsc.idxCallSurfaceEmissionIntensityMode = -1; + + dsc.idxCallBackfaceEmissionEval = -1; + dsc.idxCallBackfaceEmissionIntensity = -1; + dsc.idxCallBackfaceEmissionIntensityMode = -1; + + dsc.idxCallIor = -1; + + // No direct callables for VDFs itself. The MDL SDK is not generating code for VDFs. + + dsc.idxCallVolumeAbsorptionCoefficient = -1; + dsc.idxCallVolumeScatteringCoefficient = -1; + dsc.idxCallVolumeDirectionalBias = -1; + + dsc.idxCallGeometryCutoutOpacity = -1; + + dsc.idxCallHairSample = -1; + dsc.idxCallHairEval = -1; + + // Simplify the conditions by translating all constants unconditionally. + if (config.thin_walled) + { + dsc.flags |= IS_THIN_WALLED; + } + dsc.surface_intensity = make_float3(config.surface_intensity[0], config.surface_intensity[1], config.surface_intensity[2]); + dsc.surface_intensity_mode = config.surface_intensity_mode; + dsc.backface_intensity = make_float3(config.backface_intensity[0], config.backface_intensity[1], config.backface_intensity[2]); + dsc.backface_intensity_mode = config.backface_intensity_mode; + dsc.ior = make_float3(config.ior[0], config.ior[1], config.ior[2]); + dsc.absorption_coefficient = make_float3(config.absorption_coefficient[0], config.absorption_coefficient[1], config.absorption_coefficient[2]); + dsc.scattering_coefficient = make_float3(config.scattering_coefficient[0], config.scattering_coefficient[1], config.scattering_coefficient[2]); + dsc.cutout_opacity = config.cutout_opacity; + + MY_ASSERT(indexShader <= m_modulesMDL.size()); + + // If the shader index hasn't been seen before, we need to create a new OptixModule and a device side shader configuration. + // Otherwise the indexShader is already indexing the correct shader configuration and + // only the per reference parameter argument block and texture_handler need to be setup per reference. + if (indexShader == m_modulesMDL.size()) + { + OptixModule moduleMDL = {}; + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixModuleCreate(m_optixContext, &m_mco, &m_pco, res.target_code->get_code(), res.target_code->get_code_size(), nullptr, nullptr, &moduleMDL) ); +#else + OPTIX_CHECK( m_api.optixModuleCreateFromPTX(m_optixContext, &m_mco, &m_pco, res.target_code->get_code(), res.target_code->get_code_size(), nullptr, nullptr, &moduleMDL) ); +#endif + + m_modulesMDL.push_back(moduleMDL); + + dsc.idxCallInit = appendProgramGroupMDL(indexShader, std::string("__direct_callable__init") + suffix); // The material init function. + + if (!config.is_thin_walled_constant) + { + dsc.idxCallThinWalled = appendProgramGroupMDL(indexShader, std::string("__direct_callable__thin_walled") + suffix); + } + + if (config.is_surface_bsdf_valid) + { + const std::string name = std::string("__direct_callable__surface_scattering") + suffix; + + dsc.idxCallSurfaceScatteringSample = appendProgramGroupMDL(indexShader, name + std::string("_sample")); + dsc.idxCallSurfaceScatteringEval = appendProgramGroupMDL(indexShader, name + std::string("_evaluate")); + } + + if (config.is_backface_bsdf_valid) + { + const std::string name = std::string("__direct_callable__backface_scattering") + suffix; + + dsc.idxCallBackfaceScatteringSample = appendProgramGroupMDL(indexShader, name + std::string("_sample")); + dsc.idxCallBackfaceScatteringEval = appendProgramGroupMDL(indexShader, name + std::string("_evaluate")); + } + + if (config.is_surface_edf_valid) + { + const std::string name = std::string("__direct_callable__surface_emission_emission") + suffix; + + dsc.idxCallSurfaceEmissionEval = appendProgramGroupMDL(indexShader, name + std::string("_evaluate")); + + if (!config.is_surface_intensity_constant) + { + dsc.idxCallSurfaceEmissionIntensity = appendProgramGroupMDL(indexShader, std::string("__direct_callable__surface_emission_intensity") + suffix); + } + + if (!config.is_surface_intensity_mode_constant) + { + dsc.idxCallSurfaceEmissionIntensityMode = appendProgramGroupMDL(indexShader, std::string("__direct_callable__surface_emission_mode") + suffix); + } + } + + if (config.is_backface_edf_valid) + { + if (config.use_backface_edf) + { + const std::string name = std::string("__direct_callable__backface_emission_emission") + suffix; + + dsc.idxCallBackfaceEmissionEval = appendProgramGroupMDL(indexShader, name + std::string("_evaluate")); + } + else // Surface and backface expressions were identical. Reuse the code of the surface expression. + { + dsc.idxCallBackfaceEmissionEval = dsc.idxCallSurfaceEmissionEval; + } + + if (config.use_backface_intensity) + { + if (!config.is_backface_intensity_constant) + { + dsc.idxCallBackfaceEmissionIntensity = appendProgramGroupMDL(indexShader, std::string("__direct_callable__backface_emission_intensity") + suffix); + } + } + else // Surface and backface expressions were identical. Reuse the code of the surface expression. + { + dsc.idxCallBackfaceEmissionIntensity = dsc.idxCallSurfaceEmissionIntensity; + } + + if (config.use_backface_intensity_mode) + { + if (!config.is_backface_intensity_mode_constant) + { + dsc.idxCallBackfaceEmissionIntensityMode = appendProgramGroupMDL(indexShader, std::string("__direct_callable__backface_emission_mode") + suffix); + } + } + else // Surface and backface expressions were identical. Reuse the code of the surface expression. + { + dsc.idxCallBackfaceEmissionIntensityMode = dsc.idxCallSurfaceEmissionIntensityMode; + } + } + + if (config.isEmissive()) + { + dsc.flags |= USE_EMISSION; + } + + if (!config.is_ior_constant) + { + dsc.idxCallIor = appendProgramGroupMDL(indexShader, std::string("__direct_callable__ior") + suffix); + } + + if (!config.is_absorption_coefficient_constant) + { + dsc.idxCallVolumeAbsorptionCoefficient = appendProgramGroupMDL(indexShader, std::string("__direct_callable__volume_absorption_coefficient") + suffix); + } + + if (config.is_vdf_valid) + { + // The MDL SDK doesn't generate code for the volume.scattering expression. + // Means volume scattering must be implemented by the renderer and only the parameter expresssions can be generated. + + // The volume scattering coefficient and direction bias are only used when there is a valid VDF. + if (!config.is_scattering_coefficient_constant) + { + dsc.idxCallVolumeScatteringCoefficient = appendProgramGroupMDL(indexShader, std::string("__direct_callable__volume_scattering_coefficient") + suffix); + } + + if (!config.is_directional_bias_constant) + { + dsc.idxCallVolumeDirectionalBias = appendProgramGroupMDL(indexShader, std::string("__direct_callable__volume_directional_bias") + suffix); + } + + // volume.scattering.emission_intensity not implemented. + } + + if (config.use_cutout_opacity) + { + dsc.flags |= USE_CUTOUT_OPACITY; + + if (!config.is_cutout_opacity_constant) + { + dsc.idxCallGeometryCutoutOpacity = appendProgramGroupMDL(indexShader, std::string("__direct_callable__geometry_cutout_opacity") + suffix); + } + } + + if (config.is_hair_bsdf_valid) + { + const std::string name = std::string("__direct_callable__hair") + suffix; + + dsc.idxCallHairSample = appendProgramGroupMDL(indexShader, name + std::string("_sample")); + dsc.idxCallHairEval = appendProgramGroupMDL(indexShader, name + std::string("_evaluate")); + } + + m_deviceShaderConfigurations.push_back(dsc); + + MY_ASSERT(m_modulesMDL.size() == m_deviceShaderConfigurations.size()); + } +} + + +const TextureMDLHost* Device::prepareTextureMDL(mi::neuraylib::ITransaction* transaction, + mi::base::Handle image_api, + char const* texture_db_name, + mi::neuraylib::ITarget_code::Texture_shape texture_shape) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + // Get access to the texture data by the texture database name from the target code. + mi::base::Handle texture(transaction->access(texture_db_name)); + + // First check the texture cache. + std::string entry_name = std::string(texture_db_name) + "_" + std::to_string(unsigned(texture_shape)); + + const auto& it = m_mapTextureNameToIndex.find(entry_name); + if (it != m_mapTextureNameToIndex.end()) + { + return &m_textureMDLHosts[it->second]; // The texture already exists inside the texture cache on this device. + } + + // This is the structure which will hold the newly created texture data. + TextureMDLHost host; + memset(&host, 0, sizeof(TextureMDLHost)); + + host.m_owner = this; // Track the device which owns the CUarray data. + + // std::cout << "DEBUG: prepareTextureMDL() loading " << entry_name << '\n'; + + // Access image and canvas via the texture object + mi::base::Handle image(transaction->access(texture->get_image())); + + mi::base::Handle canvas(image->get_canvas(0, 0, 0)); + + mi::Uint32 tex_width = canvas->get_resolution_x(); + mi::Uint32 tex_height = canvas->get_resolution_y(); + mi::Uint32 tex_layers = canvas->get_layers_size(); + + if (image->is_uvtile() || image->is_animated()) + { + std::cerr << "ERROR: prepareTextureMDL() uvtile and/or animated textures not supported!\n"; + return nullptr; + } + + char const* image_type = image->get_type(0, 0); + + // Determine the image type. + + // MDL pixel types. + //"Sint8" // Signed 8-bit integer + //"Sint32" // Signed 32-bit integer + //"Float32" // 32-bit IEEE-754 single-precision floating-point number + //"Float32<2>" // 2 x Float32 + //"Float32<3>" // 3 x Float32 + //"Float32<4>" // 4 x Float32 + //"Rgb" // 3 x Uint8 representing RGB color + //"Rgba" // 4 x Uint8 representing RGBA color + //"Rgbe" // 4 x Uint8 representing RGBE color + //"Rgbea" // 5 x Uint8 representing RGBEA color + //"Rgb_16" // 3 x Uint16 representing RGB color + //"Rgba_16" // 4 x Uint16 representing RGBA color + //"Rgb_fp" // 3 x Float32 representing RGB color + //"Color" // 4 x Float32 representing RGBA color + + size_t sizeBytesPerElement = 1; + + const mi::Float32 effectiveGamma = texture->get_effective_gamma(0, 0); + + // Handle RGB8 and RGBA8 images natively. + if (strcmp(image_type, "Rgb") == 0) + { + canvas = image_api->convert(canvas.get(), "Rgba"); // Append an alpha channel with 0xFF. + + host.m_descArray3D.Format = CU_AD_FORMAT_UNSIGNED_INT8; + host.m_descArray3D.NumChannels = 4; + + sizeBytesPerElement = sizeof(mi::Uint8); + } + else if (strcmp(image_type, "Rgba") == 0) + { + host.m_descArray3D.Format = CU_AD_FORMAT_UNSIGNED_INT8; + host.m_descArray3D.NumChannels = 4; + + sizeBytesPerElement = sizeof(mi::Uint8); + } + else // FIXME PERF All other formats are currently converted to linear float4 colors. + { + if (effectiveGamma != 1.0f) // Convert image to linear color space if necessary. + { + // Copy/convert to float4 canvas and adjust gamma from "effective gamma" to 1.0. + mi::base::Handle gamma_canvas(image_api->convert(canvas.get(), "Color")); + + gamma_canvas->set_gamma(texture->get_effective_gamma(0, 0)); + image_api->adjust_gamma(gamma_canvas.get(), 1.0f); + + canvas = gamma_canvas; + } + else if (strcmp(image_type, "Color") != 0 && + strcmp(image_type, "Float32<4>") != 0) + { + // All other formats which aren't float4 already get converted to linear float4 color. Gamma is 1.0 here. + canvas = image_api->convert(canvas.get(), "Color"); + } + + host.m_descArray3D.Format = CU_AD_FORMAT_FLOAT; + host.m_descArray3D.NumChannels = 4; + + sizeBytesPerElement = sizeof(mi::Float32); + } + + host.m_resourceDescription = {}; + + // Copy image data to GPU array depending on texture shape + if (texture_shape == mi::neuraylib::ITarget_code::Texture_shape_cube || + texture_shape == mi::neuraylib::ITarget_code::Texture_shape_3d || + texture_shape == mi::neuraylib::ITarget_code::Texture_shape_bsdf_data) // DEBUG MBSDF data should not reach this code path. + { + // Cubemap and 3D texture objects require 3D CUDA arrays. + if (texture_shape == mi::neuraylib::ITarget_code::Texture_shape_cube && tex_layers != 6) + { + std::cerr << "ERROR: prepareTextureMDL() Invalid number of layers (" << tex_layers << "), cubemaps must have 6 layers!\n"; + return nullptr; + } + + // Allocate a 3D array on the GPU + host.m_descArray3D.Width = tex_width; + host.m_descArray3D.Height = tex_height; + host.m_descArray3D.Depth = tex_layers; // Not a 2D texture if this is != 0. + + // Track the current texture allocation size on this device. + host.m_sizeBytesArray = host.m_descArray3D.Width * + host.m_descArray3D.Height * + host.m_descArray3D.Depth * + host.m_descArray3D.NumChannels * + ((host.m_descArray3D.Format == CU_AD_FORMAT_UNSIGNED_INT8) ? 1 : 4); + m_sizeMemoryTextureArrays += host.m_sizeBytesArray; + + CU_CHECK( cuArray3DCreate(&host.m_d_array, &host.m_descArray3D) ); + + // Prepare the memcpy parameter structure + + // Copy the image data of all layers (the layers are not consecutive in memory) + for (mi::Uint32 layer = 0; layer < tex_layers; ++layer) + { + mi::base::Handle tile(canvas->get_tile(layer)); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = tile->get_data(); + params.srcPitch = tex_width * sizeBytesPerElement * host.m_descArray3D.NumChannels; + params.srcHeight = tex_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = host.m_d_array; + + params.dstXInBytes = 0; + params.dstY = 0; + params.dstZ = layer; + + params.WidthInBytes = params.srcPitch; + params.Height = tex_height; + params.Depth = 1; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + host.m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + host.m_resourceDescription.res.array.hArray = host.m_d_array; + } + else + { + // 2D texture objects use CUDA arrays + host.m_descArray3D.Width = tex_width; + host.m_descArray3D.Height = tex_height; + host.m_descArray3D.Depth = 0; // A 2D array is allocated if only Depth extent is zero. + + // Track the current texture allocation size on this device. + host.m_sizeBytesArray = host.m_descArray3D.Width * + host.m_descArray3D.Height * + host.m_descArray3D.NumChannels * + ((host.m_descArray3D.Format == CU_AD_FORMAT_UNSIGNED_INT8) ? 1 : 4); + m_sizeMemoryTextureArrays += host.m_sizeBytesArray; + + CU_CHECK( cuArray3DCreate(&host.m_d_array, &host.m_descArray3D) ); + + mi::base::Handle tile(canvas->get_tile()); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = tile->get_data(); + params.srcPitch = tex_width * sizeBytesPerElement * host.m_descArray3D.NumChannels; + params.srcHeight = tex_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = host.m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = tex_height; + params.Depth = 1; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + host.m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + host.m_resourceDescription.res.array.hArray = host.m_d_array; + } + + // For cube maps we need clamped address mode to avoid artifacts in the corners. + CUaddress_mode addr_mode = (texture_shape == mi::neuraylib::ITarget_code::Texture_shape_cube) ? CU_TR_ADDRESS_MODE_CLAMP : CU_TR_ADDRESS_MODE_WRAP; + + // Create filtered texture object + host.m_textureDescription = {}; + + // If the flag CU_TRSF_NORMALIZED_COORDINATES is not set, the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP. + host.m_textureDescription.addressMode[0] = addr_mode; + host.m_textureDescription.addressMode[1] = addr_mode; + host.m_textureDescription.addressMode[2] = addr_mode; + + host.m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + + // Possible flags: CU_TRSF_READ_AS_INTEGER, CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_SRGB + host.m_textureDescription.flags = CU_TRSF_NORMALIZED_COORDINATES; + if (effectiveGamma != 1.0f && sizeBytesPerElement == 1) + { + host.m_textureDescription.flags |= CU_TRSF_SRGB; + } + + host.m_textureDescription.maxAnisotropy = 1; + + // LOD 0 only by default. + // This means when using mipmaps it's the developer's responsibility to set at least + // maxMipmapLevelClamp > 0.0f before calling Texture::create() to make sure mipmaps can be sampled! + host.m_textureDescription.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + host.m_textureDescription.mipmapLevelBias = 0.0f; + host.m_textureDescription.minMipmapLevelClamp = 0.0f; + host.m_textureDescription.maxMipmapLevelClamp = 0.0f; // This should be set to Picture::getNumberOfLevels() when using mipmaps. + + host.m_textureDescription.borderColor[0] = 0.0f; + host.m_textureDescription.borderColor[1] = 0.0f; + host.m_textureDescription.borderColor[2] = 0.0f; + host.m_textureDescription.borderColor[3] = 0.0f; + + CUtexObject tex_obj = 0; // This type is interchangeable with cudaTextureObject_t. + + CU_CHECK( cuTexObjectCreate(&tex_obj, &host.m_resourceDescription, &host.m_textureDescription, nullptr) ); + + // Create unfiltered texture object if necessary (cube textures have no texel functions) + CUtexObject tex_obj_unfilt = 0; + + if (texture_shape != mi::neuraylib::ITarget_code::Texture_shape_cube) + { + // Use a black border for access outside of the texture + host.m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_BORDER; + host.m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_BORDER; + host.m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_BORDER; + + host.m_textureDescription.filterMode = CU_TR_FILTER_MODE_POINT; + + CU_CHECK( cuTexObjectCreate(&tex_obj_unfilt, &host.m_resourceDescription, &host.m_textureDescription, nullptr) ); + } + + // Add the device-side information for the Texture_handler. + host.m_texture = TextureMDL(tex_obj, tex_obj_unfilt, make_uint3(tex_width, tex_height, tex_layers)); + + // Get the new texture entry index for the cache. + const int indexCache = static_cast(m_textureMDLHosts.size()); + // Track the cache index of this texture. This is needed to build the Texture_handler. + host.m_index = indexCache; + // Track the index inside the texture cache. + m_mapTextureNameToIndex[entry_name] = indexCache; + // Store the texture array and object handles inside the vector of all textures on this device. + m_textureMDLHosts.push_back(host); + + // Return the pointer this newly created TextureMDLHost. + return &m_textureMDLHosts[indexCache]; +} + + +void Device::shareTextureMDL(const TextureMDLHost* shared, + char const* texture_db_name, + mi::neuraylib::ITarget_code::Texture_shape texture_shape) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + // First check the texture cache. + std::string entry_name = std::string(texture_db_name) + "_" + std::to_string(unsigned(texture_shape)); + + const auto& it = m_mapTextureNameToIndex.find(entry_name); + if (it != m_mapTextureNameToIndex.end()) + { + // The texture already exists inside the per device cache, + // which also means the MaterialMDL indices point to that already. + return; + } + + TextureMDLHost host = *shared; // Copy everything from the shared texture. + + host.m_texture.filtered_object = 0; // Except for the texture objects. + host.m_texture.unfiltered_object = 0; + + // Create filtered texture object + CUaddress_mode addr_mode = (texture_shape == mi::neuraylib::ITarget_code::Texture_shape_cube) ? CU_TR_ADDRESS_MODE_CLAMP : CU_TR_ADDRESS_MODE_WRAP; + + // If the flag CU_TRSF_NORMALIZED_COORDINATES is not set, the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP. + host.m_textureDescription.addressMode[0] = addr_mode; + host.m_textureDescription.addressMode[1] = addr_mode; + host.m_textureDescription.addressMode[2] = addr_mode; + + host.m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering. + + CU_CHECK( cuTexObjectCreate(&host.m_texture.filtered_object, &host.m_resourceDescription, &host.m_textureDescription, nullptr) ); + + if (texture_shape != mi::neuraylib::ITarget_code::Texture_shape_cube) + { + // Use a black border for access outside of the texture + host.m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_BORDER; + host.m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_BORDER; + host.m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_BORDER; + + host.m_textureDescription.filterMode = CU_TR_FILTER_MODE_POINT; + + CU_CHECK( cuTexObjectCreate(&host.m_texture.unfiltered_object, &host.m_resourceDescription, &host.m_textureDescription, nullptr) ); + } + + // Get the new texture entry index for the cache. + const int indexTexture = static_cast(m_textureMDLHosts.size()); + MY_ASSERT(host.m_index == indexTexture); // Make sure the index of the shared texture is the same as the newly appended reused texture. + // Store the texture array and object handles inside the vector of all textures on this device. + m_textureMDLHosts.push_back(host); // This array is the same size on all devices! + // Track the index inside the texture cache of this device. + m_mapTextureNameToIndex[entry_name] = indexTexture; +} + + +bool Device::prepare_mbsdfs_part(mi::neuraylib::Mbsdf_part part, + MbsdfHost& host, + const mi::neuraylib::IBsdf_measurement* bsdf_measurement) +{ + mi::base::Handle dataset; + + switch (part) + { + case mi::neuraylib::MBSDF_DATA_REFLECTION: + dataset = bsdf_measurement->get_reflection(); + break; + + case mi::neuraylib::MBSDF_DATA_TRANSMISSION: + dataset = bsdf_measurement->get_transmission(); + break; + } + + // No data, fine. + if (!dataset) + { + return true; + } + + // get dimensions + uint2 res; + + res.x = dataset->get_resolution_theta(); + res.y = dataset->get_resolution_phi(); + + unsigned int num_channels = (dataset->get_type() == mi::neuraylib::BSDF_SCALAR) ? 1 : 3; + + Mbsdf& mbsdf = host.m_mbsdf; + + mbsdf.Add(part, res, num_channels); + + // Get data. + mi::base::Handle buffer(dataset->get_bsdf_buffer()); + + // {1, 3} * (index_theta_in * (res_phi * res_theta) + index_theta_out * res_phi + index_phi) + const mi::Float32* src_data = buffer->get_data(); + + // Prepare importance sampling data: + // - For theta_in we will be able to perform a two stage CDF, + // first to select theta_out, and second to select phi_out. + // - Maximum component is used to "probability" in case of colored measurements. + + // CDF of the probability to select a certain theta_out for a given theta_in. + const unsigned int cdf_theta_size = res.x * res.x; + + // For each of theta_in x theta_out combination, a CDF of the probabilities to select a certain theta_out is stored. + const unsigned sample_data_size = cdf_theta_size + cdf_theta_size * res.y; + + float* sample_data = new float[sample_data_size]; + + float* albedo_data = new float[res.x]; // albedo for sampling reflection and transmission + + float* sample_data_theta = sample_data; // begin of the first (theta) CDF + float* sample_data_phi = sample_data + cdf_theta_size; // begin of the second (phi) CDFs + + const float s_theta = (float) (M_PI * 0.5) / float(res.x); // step size + const float s_phi = (float) (M_PI) / float(res.y); // step size + + float max_albedo = 0.0f; + + for (unsigned int t_in = 0; t_in < res.x; ++t_in) + { + float sum_theta = 0.0f; + float sintheta0_sqd = 0.0f; + + for (unsigned int t_out = 0; t_out < res.x; ++t_out) + { + const float sintheta1 = sinf(float(t_out + 1) * s_theta); + const float sintheta1_sqd = sintheta1 * sintheta1; + + // BSDFs are symmetric: f(w_in, w_out) = f(w_out, w_in) + // Take the average of both measurements. + + // Area of two the surface elements (the ones we are averaging). + const float mu = (sintheta1_sqd - sintheta0_sqd) * s_phi * 0.5f; + + sintheta0_sqd = sintheta1_sqd; + + // Offset for both the thetas into the measurement data (select row in the volume). + const unsigned int offset_phi = (t_in * res.x + t_out) * res.y; + const unsigned int offset_phi2 = (t_out * res.x + t_in ) * res.y; + + // Build CDF for phi + float sum_phi = 0.0f; + + for (unsigned int p_out = 0; p_out < res.y; ++p_out) + { + const unsigned int idx = offset_phi + p_out; + const unsigned int idx2 = offset_phi2 + p_out; + + float value = 0.0f; + + if (num_channels == 3) + { + value = fmax(fmaxf(src_data[3 * idx + 0], src_data[3 * idx + 1]), fmaxf(src_data[3 * idx + 2], 0.0f)) + + fmax(fmaxf(src_data[3 * idx2 + 0], src_data[3 * idx2 + 1]), fmaxf(src_data[3 * idx2 + 2], 0.0f)); + } + else /* num_channels == 1 */ + { + value = fmaxf(src_data[idx], 0.0f) + fmaxf(src_data[idx2], 0.0f); + } + + sum_phi += value * mu; + + sample_data_phi[idx] = sum_phi; + } + + // Normalize CDF for phi. + for (unsigned int p_out = 0; p_out < res.y; ++p_out) + { + const unsigned int idx = offset_phi + p_out; + + sample_data_phi[idx] = sample_data_phi[idx] / sum_phi; + } + + // Build CDF for theta. + sum_theta += sum_phi; + sample_data_theta[t_in * res.x + t_out] = sum_theta; + } + + if (sum_theta > max_albedo) + { + max_albedo = sum_theta; + } + + albedo_data[t_in] = sum_theta; + + // normalize CDF for theta + for (unsigned int t_out = 0; t_out < res.x; ++t_out) + { + const unsigned int idx = t_in * res.x + t_out; + + sample_data_theta[idx] = sample_data_theta[idx] / sum_theta; + } + } + + // Copy entire CDF data buffer to GPU + CUdeviceptr d_sample_obj = memAlloc(sample_data_size * sizeof(float), 4); + CU_CHECK( cuMemcpyHtoD(d_sample_obj, sample_data, sample_data_size * sizeof(float)) ); + delete[] sample_data; // Copy is synchronous so this can be deleted now. + + CUdeviceptr d_albedo_obj = memAlloc(res.x * sizeof(float), 4); + CU_CHECK( cuMemcpyHtoD(d_albedo_obj, albedo_data, res.x * sizeof(float)) ); + delete[] albedo_data; // Copy is synchronous so this can be deleted now. + + mbsdf.sample_data[part] = reinterpret_cast(d_sample_obj); + mbsdf.albedo_data[part] = reinterpret_cast(d_albedo_obj); + mbsdf.max_albedo[part] = max_albedo; + + // Prepare evaluation data: + // - Simply store the measured data in a volume texture. + // - In case of color data, we store each sample in a vector4 to get texture support. + unsigned int lookup_channels = (num_channels == 3) ? 4 : 1; + + // Make lookup data symmetric + float* lookup_data = new float[lookup_channels * res.y * res.x * res.x]; + + for (unsigned int t_in = 0; t_in < res.x; ++t_in) + { + for (unsigned int t_out = 0; t_out < res.x; ++t_out) + { + const unsigned int offset_phi = (t_in * res.x + t_out) * res.y; + const unsigned int offset_phi2 = (t_out * res.x + t_in ) * res.y; + + for (unsigned int p_out = 0; p_out < res.y; ++p_out) + { + const unsigned int idx = offset_phi + p_out; + const unsigned int idx2 = offset_phi2 + p_out; + + if (num_channels == 3) + { + lookup_data[4 * idx + 0] = (src_data[3 * idx + 0] + src_data[3 * idx2 + 0]) * 0.5f; + lookup_data[4 * idx + 1] = (src_data[3 * idx + 1] + src_data[3 * idx2 + 1]) * 0.5f; + lookup_data[4 * idx + 2] = (src_data[3 * idx + 2] + src_data[3 * idx2 + 2]) * 0.5f; + lookup_data[4 * idx + 3] = 1.0f; + } + else + { + lookup_data[idx] = (src_data[idx] + src_data[idx2]) * 0.5f; + } + } + } + } + + // Allocate a 3D array on the GPU (phi_delta x theta_out x theta_in) + CUDA_ARRAY3D_DESCRIPTOR& descArray3D = host.m_descArray3D[part]; + + descArray3D = {}; + + descArray3D.Width = res.y; + descArray3D.Height = res.x; + descArray3D.Depth = res.x; + descArray3D.Format = CU_AD_FORMAT_FLOAT; + descArray3D.NumChannels = (num_channels == 3) ? 4 : 1; + descArray3D.Flags = 0; + + // Track the current texture allocation size on this device. + host.m_sizeBytesArray[part] += descArray3D.Width * + descArray3D.Height * + descArray3D.Depth * + descArray3D.NumChannels * sizeof(float); + m_sizeMemoryTextureArrays += host.m_sizeBytesArray[part]; + + CU_CHECK( cuArray3DCreate(&host.m_d_array[part], &descArray3D) ); + + // Prepare and copy + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = lookup_data; + params.srcPitch = res.y * sizeof(float) * descArray3D.NumChannels; + params.srcHeight = res.x; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = host.m_d_array[part]; + + params.WidthInBytes = params.srcPitch; + params.Height = res.x; + params.Depth = res.x; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + delete[] lookup_data; + + CUDA_RESOURCE_DESC& resourceDesc = host.m_resourceDescription[part]; + + resourceDesc = {}; + + resourceDesc.resType = CU_RESOURCE_TYPE_ARRAY; + resourceDesc.res.array.hArray = host.m_d_array[part]; + + CUDA_TEXTURE_DESC& textureDesc = host.m_textureDescription; // Same settings for both parts. + + textureDesc = {}; + + // Possible flags: CU_TRSF_READ_AS_INTEGER, CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_SRGB + textureDesc.flags = CU_TRSF_NORMALIZED_COORDINATES; + textureDesc.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + textureDesc.addressMode[0] = CU_TR_ADDRESS_MODE_CLAMP; + textureDesc.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + textureDesc.addressMode[2] = CU_TR_ADDRESS_MODE_CLAMP; + textureDesc.maxAnisotropy = 1; + // DAR The default initialization handled all these. Just for code clarity. + textureDesc.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + textureDesc.mipmapLevelBias = 0.0f; + textureDesc.minMipmapLevelClamp = 0.0f; + textureDesc.maxMipmapLevelClamp = 0.0f; + textureDesc.borderColor[0] = 0.0f; + textureDesc.borderColor[1] = 0.0f; + textureDesc.borderColor[2] = 0.0f; + textureDesc.borderColor[3] = 0.0f; + + CUtexObject eval_tex_obj = 0; // This type is interchangeable with cudaTextureObject_t. + + CU_CHECK( cuTexObjectCreate(&eval_tex_obj, &resourceDesc, &textureDesc, nullptr) ); + + mbsdf.eval_data[part] = eval_tex_obj; + + return true; +} + + +MbsdfHost* Device::prepareMBSDF(mi::neuraylib::ITransaction* transaction, + const mi::neuraylib::ITarget_code* code, + const int index) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + // Get access to the MBSDF data by the texture database name from the target code. + mi::base::Handle bsdf_measurement(transaction->access(code->get_bsdf_measurement(index))); + + MbsdfHost host; + memset(&host, 0, sizeof(MbsdfHost)); + + host.m_owner = this; // Track the device which owns the CUarray data. + + // Handle reflection part. + if (!prepare_mbsdfs_part(mi::neuraylib::MBSDF_DATA_REFLECTION, host, bsdf_measurement.get())) + { + return nullptr; + } + + // Handle transmission part. + if (!prepare_mbsdfs_part(mi::neuraylib::MBSDF_DATA_TRANSMISSION, host, bsdf_measurement.get())) + { + return nullptr; + } + + // Get the index of this Mbsdf inside the per Device's m_mbsdf vector. + const int indexCache = static_cast(m_mbsdfHosts.size()); + + host.m_index = indexCache; + // FIXME Implement a cache. (It's unlikely that multiple different materials use the same measured BSDF though.) + + // These are all MBSDFs inside the scene. + m_mbsdfHosts.push_back(host); + + return &m_mbsdfHosts[indexCache]; +} + + +void Device::shareMBSDF(const MbsdfHost* shared) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + MbsdfHost host = *shared; // Copy everything from the shared MbsdfHost. + + for (int part = 0; part < 2; ++part) + { + host.m_mbsdf.eval_data[part] = 0; // Texture objects will be generated per-device. + + if (host.m_d_array[part]) // If there is CUarray data for the part, create a texture object from that. + { + CU_CHECK( cuTexObjectCreate(&host.m_mbsdf.eval_data[part], &host.m_resourceDescription[part], &host.m_textureDescription, nullptr) ); + } + } + + // Get the index of this MbsdfHost inside the per Device's m_mbsdfHosts vector. + const int indexCache = static_cast(m_mbsdfHosts.size()); + MY_ASSERT(indexCache == host.m_index); // Make sure the index is the same on all devices. + + // These are all MBSDFs inside the scene. + m_mbsdfHosts.push_back(host); +} + + +LightprofileHost* Device::prepareLightprofile(mi::neuraylib::ITransaction* transaction, + const mi::neuraylib::ITarget_code* code, + int index) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + // Get access to the light_profile data. + mi::base::Handle lprof_nr(transaction->access(code->get_light_profile(index))); + + uint2 res = make_uint2(lprof_nr->get_resolution_theta(), lprof_nr->get_resolution_phi()); + float2 start = make_float2(lprof_nr->get_theta(0), lprof_nr->get_phi(0)); + float2 delta = make_float2(lprof_nr->get_theta(1) - start.x, lprof_nr->get_phi(1) - start.y); + + // phi-mayor: [res.x x res.y] + const float* data = lprof_nr->get_data(); + + // Compute total power. + // Compute inverse CDF data for sampling. + // Sampling will work on cells rather than grid nodes (used for evaluation). + + // First (res.x-1) for the cdf for sampling theta. + // Rest (rex.x-1) * (res.y-1) for the individual cdfs for sampling phi (after theta). + size_t cdf_data_size = (res.x - 1) + (res.x - 1) * (res.y - 1); + + float* cdf_data = new float[cdf_data_size]; + + float debug_total_area = 0.0f; + float sum_theta = 0.0f; + float total_power = 0.0f; + + float cos_theta0 = cosf(start.x); + + for (unsigned int t = 0; t < res.x - 1; ++t) + { + const float cos_theta1 = cosf(start.x + float(t + 1) * delta.x); + + // Area of the patch (grid cell) + // \mu = int_{theta0}^{theta1} sin{theta} \delta theta + const float mu = cos_theta0 - cos_theta1; + cos_theta0 = cos_theta1; + + // Build CDF for phi. + float* cdf_data_phi = cdf_data + (res.x - 1) + t * (res.y - 1); + + float sum_phi = 0.0f; + for (unsigned int p = 0; p < res.y - 1; ++p) + { + // The probability to select a patch corresponds to the value times area. + // The value of a cell is the average of the corners. + // Omit the *1/4 as we normalize in the end. + float value = data[ p * res.x + t ] + + data[ p * res.x + t + 1] + + data[(p + 1) * res.x + t ] + + data[(p + 1) * res.x + t + 1]; + + sum_phi += value * mu; + cdf_data_phi[p] = sum_phi; + + debug_total_area += mu; + } + + // Normalize CDF for phi. + for (unsigned int p = 0; p < res.y - 2; ++p) + { + cdf_data_phi[p] = (0.0f < sum_phi) ? (cdf_data_phi[p] / sum_phi) : 0.0f; + } + + cdf_data_phi[res.y - 2] = 1.0f; + + // Build CDF for theta + sum_theta += sum_phi; + cdf_data[t] = sum_theta; + } + + total_power = sum_theta * 0.25f * delta.y; + + // Normalize CDF for theta. + for (unsigned int t = 0; t < res.x - 2; ++t) + { + cdf_data[t] = (0.0f < sum_theta) ? (cdf_data[t] / sum_theta) : cdf_data[t]; + } + + cdf_data[res.x - 2] = 1.0f; + + // Copy entire CDF data buffer to GPU + CUdeviceptr d_cdf_data_obj = memAlloc(cdf_data_size * sizeof(float), 4); + CU_CHECK( cuMemcpyHtoD(d_cdf_data_obj, cdf_data, cdf_data_size * sizeof(float)) ); // Synchronous. + delete[] cdf_data; + + // -------------------------------------------------------------------------------------------- + // Prepare evaluation data. + // - Use a 2d texture that allows bilinear interpolation. + + LightprofileHost host; + memset(&host, 0, sizeof(LightprofileHost)); + + // Allocate a 3D array on the GPU (phi_delta x theta_out x theta_in) + CUDA_ARRAY3D_DESCRIPTOR& descArray3D = host.m_descArray3D; + + descArray3D = {}; + + descArray3D.Width = res.x; + descArray3D.Height = res.y; + descArray3D.Depth = 0; // A 2D array is allocated if only Depth extent is zero. + descArray3D.Format = CU_AD_FORMAT_FLOAT; + descArray3D.NumChannels = 1; + descArray3D.Flags = 0; + + CU_CHECK( cuArray3DCreate(&host.m_d_array, &descArray3D) ); + + // Copy data to GPU array + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = res.x * sizeof(float) * descArray3D.NumChannels; + params.srcHeight = res.y; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = host.m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = res.y; + params.Depth = 1; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + // Create filtered texture object + CUDA_RESOURCE_DESC& resourceDesc = host.m_resourceDescription; + + resourceDesc = {}; + + resourceDesc.resType = CU_RESOURCE_TYPE_ARRAY; + resourceDesc.res.array.hArray = host.m_d_array; + + CUDA_TEXTURE_DESC& textureDesc = host.m_textureDescription; + + textureDesc = {}; + + // Possible flags: CU_TRSF_READ_AS_INTEGER, CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_SRGB + textureDesc.flags = CU_TRSF_NORMALIZED_COORDINATES; + textureDesc.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + textureDesc.addressMode[0] = CU_TR_ADDRESS_MODE_CLAMP; // FIXME Shouldn't phi use wrap? + textureDesc.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + textureDesc.addressMode[2] = CU_TR_ADDRESS_MODE_CLAMP; + textureDesc.maxAnisotropy = 1; + // DAR The default initialization handled all these. Just for code clarity. + textureDesc.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + textureDesc.mipmapLevelBias = 0.0f; + textureDesc.minMipmapLevelClamp = 0.0f; + textureDesc.maxMipmapLevelClamp = 0.0f; + textureDesc.borderColor[0] = 1.0f; // DAR DEBUG Why 1.0f? Shouldn't matter with clamp. + textureDesc.borderColor[1] = 1.0f; + textureDesc.borderColor[2] = 1.0f; + textureDesc.borderColor[3] = 1.0f; + + CUtexObject tex_obj = 0; // This type is interchangeable with cudaTextureObject_t. + + CU_CHECK( cuTexObjectCreate(&tex_obj, &resourceDesc, &textureDesc, nullptr) ); + + double multiplier = lprof_nr->get_candela_multiplier(); + + host.m_profile = Lightprofile(tex_obj, + reinterpret_cast(d_cdf_data_obj), + res, + start, + delta, + float(multiplier), + float(total_power * multiplier)); + + const int indexCache = static_cast(m_lightprofileHosts.size()); + + host.m_index = indexCache; + // FIXME Implement a cache. (It's unlikely that multiple different materials use the same light profile though.) + + m_lightprofileHosts.push_back(host); // These are all light profiles in the scene. + + return &m_lightprofileHosts[indexCache]; +} + + +void Device::shareLightprofile(const LightprofileHost* shared) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + LightprofileHost host = *shared; // Copy everything from the shared MbsdfHost. + + host.m_profile.eval_data = 0; // Texture objects will be generated per-device. + + if (host.m_d_array) + { + CU_CHECK( cuTexObjectCreate(&host.m_profile.eval_data, &host.m_resourceDescription, &host.m_textureDescription, nullptr) ); + } + + const int indexCache = static_cast(m_lightprofileHosts.size()); + MY_ASSERT(indexCache == host.m_index); // Make sure the index is the same on all devices. + + m_lightprofileHosts.push_back(host); // These are all Lightprofiles inside the scene. +} + + +static int numberOfBits(unsigned int num) +{ + int bit = 0; + + while (num) + { + num &= ~(1u << bit); + ++bit; + } + + return bit; +} + + +void Device::initTextureHandler(std::vector& materialsMDL) +{ + activateContext(); + synchronizeStream(); // PERF Required here? + + const size_t numMaterialReferences = materialsMDL.size(); + + for (MaterialMDL* material : materialsMDL) + { + MaterialDefinitionMDL materialDefinition = {}; // Set everything to zero. + + const size_t sizeArgumentBlock = material->getArgumentBlockSize(); + // If the material has an argument block, allocate and upload it. + if (0 < sizeArgumentBlock) + { + materialDefinition.arg_block = memAlloc(sizeArgumentBlock, 16); + + CU_CHECK( cuMemcpyHtoD(materialDefinition.arg_block, material->getArgumentBlockData(), sizeArgumentBlock) ); + } + + // The MaterialMDL (per reference) only stores indices into the texture, MBSDF, and light profile caches (per device). + // The current MDL runtime functions expect expanded arrays with the actual TextureMDL, Mbsdf and Lightprofile structures. + // Expand them here. + // Note that all three Texture_handler arrays are indexed zero-based and do not contain the invalid resource at index 0! + + Texture_handler handler = {}; + + if (!material->m_indicesToTextures.empty()) + { + std::vector textures; + + for (int i : material->m_indicesToTextures) + { + textures.push_back(m_textureMDLHosts[i].m_texture); + } + + handler.num_textures = static_cast(textures.size()); + handler.textures = reinterpret_cast(memAlloc(sizeof(TextureMDL) * textures.size(), 16)); + + CU_CHECK( cuMemcpyHtoD(reinterpret_cast(handler.textures), textures.data(), sizeof(TextureMDL) * textures.size()) ); // Synchronous to not overwrite the host data in the loop. + } + + if (!material->m_indicesToMBSDFs.empty()) + { + std::vector mbsdfs; + + for (int i : material->m_indicesToMBSDFs) + { + mbsdfs.push_back(m_mbsdfHosts[i].m_mbsdf); + } + + handler.num_mbsdfs = static_cast(mbsdfs.size()); + handler.mbsdfs = reinterpret_cast(memAlloc(sizeof(Mbsdf) * mbsdfs.size(), 16)); + + CU_CHECK( cuMemcpyHtoD(reinterpret_cast(handler.mbsdfs), mbsdfs.data(), sizeof(Mbsdf) * mbsdfs.size()) ); // Synchronous to not overwrite the host data in the loop. + } + + if (!material->m_indicesToLightprofiles.empty()) + { + std::vector profiles; + + for (int i : material->m_indicesToLightprofiles) + { + profiles.push_back(m_lightprofileHosts[i].m_profile); + } + + handler.num_lightprofiles = static_cast(profiles.size()); + handler.lightprofiles = reinterpret_cast(memAlloc(sizeof(Lightprofile) * profiles.size(), 16)); + + CU_CHECK( cuMemcpyHtoD(reinterpret_cast(handler.lightprofiles), profiles.data(), sizeof(Lightprofile) * profiles.size()) ); // Synchronous to not overwrite the host data in the loop. + } + + materialDefinition.texture_handler = reinterpret_cast(memAlloc(sizeof(Texture_handler), 16)); + + CU_CHECK( cuMemcpyHtoD(reinterpret_cast(materialDefinition.texture_handler), &handler, sizeof(Texture_handler)) ); + + // Set the index to the shader cache. This defines which shader configuration is used. + // If this indexShader is invalid, the instance with that material will not be put into the rendergraph! + materialDefinition.indexShader = material->getShaderIndex(); + + m_materialDefinitions.push_back(materialDefinition); + } + + // Allocate the material definition device array, one entry per MDL material reference. + m_systemData.materialDefinitionsMDL = 0; // The MDL material parameter argument block, texture handler and index into the shader. + + if (!m_materialDefinitions.empty()) + { + m_systemData.materialDefinitionsMDL = reinterpret_cast(memAlloc(sizeof(MaterialDefinitionMDL) * m_materialDefinitions.size(), 16)); + + CU_CHECK( cuMemcpyHtoD(reinterpret_cast(m_systemData.materialDefinitionsMDL), m_materialDefinitions.data(), sizeof(MaterialDefinitionMDL) * m_materialDefinitions.size()) ); + + m_systemData.numMaterials = static_cast(m_materialDefinitions.size()); + } + + m_systemData.shaderConfigurations = 0; + + if (!m_deviceShaderConfigurations.empty()) + { + m_systemData.shaderConfigurations = reinterpret_cast(memAlloc(sizeof(DeviceShaderConfiguration) * m_deviceShaderConfigurations.size(), 16)); + + CU_CHECK( cuMemcpyHtoD(reinterpret_cast(m_systemData.shaderConfigurations), m_deviceShaderConfigurations.data(), sizeof(DeviceShaderConfiguration) * m_deviceShaderConfigurations.size()) ); + + m_systemData.numBitsShaders = numberOfBits(static_cast(m_deviceShaderConfigurations.size())); + } + + m_isDirtySystemData = true; + + // Only when all MDL materials have been created, all information about the direct callable programs are available and the pipeline can be built. + initPipeline(); +} + diff --git a/apps/MDL_sdf/src/Hair.cpp b/apps/MDL_sdf/src/Hair.cpp new file mode 100644 index 00000000..e23bbb80 --- /dev/null +++ b/apps/MDL_sdf/src/Hair.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Hair.h" + +#include +#include +#include +#include + +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +// Hair file format and models courtesy of Cem Yuksel: http://www.cemyuksel.com/research/hairmodels/ + +Hair::Hair() +{ + memset(&m_header, 0, sizeof(m_header)); + + m_header.signature[0] = 'H'; + m_header.signature[1] = 'A'; + m_header.signature[2] = 'I'; + m_header.signature[3] = 'R'; +} + +//Hair::~Hair() +//{ +//} + +void Hair::setNumStrands(const unsigned int num) +{ + m_header.numStrands = num; +} + +unsigned int Hair::getNumStrands() const +{ + return m_header.numStrands; +} + +// Used when all strands have the same number of segments. +void Hair::setNumSegments(const unsigned int num) +{ + m_header.numSegments = num; +} + +// Used when each hair strand can have a different number of segments. +void Hair::setSegmentsArray(const std::vector& segments) +{ + // segments arrays can either be empty or must match the number of strands. + // That means numStrands needs to be set first! + MY_ASSERT(segments.empty() || m_header.numStrands == static_cast(segments.size())); + m_segmentsArray = segments; +} + +unsigned int Hair::getNumSegments(const unsigned int idxStrand) const +{ + MY_ASSERT(idxStrand < m_header.numStrands); + if (!m_segmentsArray.empty()) + { + return m_segmentsArray[idxStrand]; + } + return m_header.numSegments; +} + +void Hair::setPointsArray(const std::vector& points) +{ + MY_ASSERT(!points.empty()); // Points array cannot be empty. + m_pointsArray = points; + + // Changing numPoints potentially breaks the thickness, transparency, or color arrays temporarily. + // These should always be set after the points array when present. + m_header.numPoints = static_cast(m_pointsArray.size()); +} + +float3 Hair::getPoint(const unsigned int idx) const +{ + MY_ASSERT(idx < m_header.numPoints); + return m_pointsArray[idx]; +} + +void Hair::setThickness(const float thickness) +{ + m_header.thickness = thickness; +} + +void Hair::setThicknessArray(const std::vector& thickness) +{ + MY_ASSERT(thickness.empty() || m_header.numPoints == static_cast(thickness.size())); + m_thicknessArray = thickness; +} + +float Hair::getThickness(const unsigned int idx) const +{ + MY_ASSERT(idx < m_header.numPoints); + if (!m_thicknessArray.empty()) + { + return m_thicknessArray[idx]; + } + return m_header.thickness; +} + +void Hair::setTransparency(const float transparency) +{ + m_header.transparency = transparency; +} + +void Hair::setTransparencyArray(const std::vector& transparency) +{ + MY_ASSERT(transparency.empty() || m_header.numPoints == static_cast(transparency.size())); + m_transparencyArray = transparency; +} + +float Hair::getTransparency(const unsigned int idx) const +{ + MY_ASSERT(idx < m_header.numPoints); + if (!m_transparencyArray.empty()) + { + return m_transparencyArray[idx]; + } + return m_header.transparency; +} + +void Hair::setColor(const float3 color) +{ + m_header.color = color; +} + +void Hair::setColorArray(const std::vector& color) +{ + MY_ASSERT(color.empty() || m_header.numPoints == static_cast(color.size())); + m_colorArray = color; +} + +float3 Hair::getColor(const unsigned int idx) const +{ + MY_ASSERT(idx < m_header.numPoints); + if (!m_colorArray.empty()) + { + return m_colorArray[idx]; + } + return m_header.color; +} + +bool Hair::load(const std::string& filename) +{ + // This is a binary file format for 3D hair models. + FILE *file = fopen(filename.c_str(), "rb"); + if (!file) + { + std::cerr << "ERROR: Hair::load(): fopen(" << filename << ") failed.\n"; + return false; + } + + // A HAIR file begins with a 128-Byte long header. + size_t count = fread(&m_header, sizeof(Header), 1, file); + if (count < 1) + { + std::cerr << "ERROR: Hair::load(): Reading the header of " << filename << " failed.\n"; + fclose(file); + return false; + } + + if (strncmp(m_header.signature, "HAIR", 4) != 0) + { + std::cerr << "ERROR: Hair::load(): Header signature in " << filename << " not matching \"HAIR\"\n"; + fclose(file); + return false; + } + + // A HAIR file must have a points array, but all the other arrays are optional. + // When an array does not exist, corresponding default value from the file header is used instead of the missing array. + if ((m_header.bits & HAS_POINTS_ARRAY) == 0) + { + std::cerr << "ERROR: Hair::load(): Header bits in " << filename << " has no points array\n"; + fclose(file); + return false; + } + +#if 0 // DEBUG + std::cout << "Header of " << filename << " :\n"; + std::cout << "Number of hair strands = " << m_header.numStrands << "\n"; + std::cout << "Total number of points = " << m_header.numPoints; + if (m_header.bits & HAS_POINTS_ARRAY) // Put this first because this must always exist. + { + std::cout << "POINTS_ARRAY"; + } + if (m_header.bits & HAS_SEGMENTS_ARRAY) + { + std::cout << " | SEGMENTS_ARRAY"; + } + if (m_header.bits & HAS_THICKNESS_ARRAY) + { + std::cout << " | THICKNESS_ARRAY"; + } + if (m_header.bits & HAS_TRANSPARENCY_ARRAY) + { + std::cout << " | TRANSPARENCY_ARRAY"; + } + if (m_header.bits & HAS_COLOR_ARRAY) + { + std::cout << " | COLOR_ARRAY"; + } + std::cout << std::endl; + + if ((m_header.bits & HAS_SEGMENTS_ARRAY) == 0) + { + std::cout << "Default number of segments = " << m_header.numSegments << '\n'; + } + if ((m_header.bits & HAS_THICKNESS_ARRAY) == 0) + { + std::cout << "Default thickness = " << m_header.thickness << '\n'; + } + if ((m_header.bits & HAS_TRANSPARENCY_ARRAY) == 0) + { + std::cout << "Default transparency = " << m_header.transparency << '\n'; + } + if ((m_header.bits & HAS_COLOR_ARRAY) == 0) + { + std::cout << "Default color = " << m_header.color[0] << ", " << m_header.color[1] << ", " << m_header.color[2] << '\n'; + } +#endif + + // The 3D hair model consists of strands, each one of which is represented by a number of line segments. + + // Segments array (unsigned short) + // This array keeps the number of segments of each hair strand. + // Each entry is a 16-bit unsigned integer, therefore each hair strand can have up to 65536 segments. + if (m_header.bits & HAS_SEGMENTS_ARRAY) + { + m_segmentsArray.resize(m_header.numStrands); + + size_t count = fread(m_segmentsArray.data(), sizeof(unsigned short), m_header.numStrands, file); + if (count < m_header.numStrands) + { + std::cerr << "ERROR: Hair::load(): Failed to read segments array of " << filename << '\n'; + fclose(file); + return false; + } + } + + // Points array (float) + // This array keeps the 3D positions each of hair strand point. + // These points are not shared by different hair strands; each point belongs to a particular hair strand only. + // Line segments of a hair strand connects consecutive points. + // The points in this array are ordered by strand and from root to tip; + // such that it begins with the root point of the first hair strand, + // continues with the next point of the first hair strand until the tip of the first hair strand, + // and then comes the points of the next hair strands. + // Each entry is a 32-bit floating point number, and each point is defined by 3 consecutive numbers + // that correspond to x, y, and z coordinates. + if (m_header.bits & HAS_POINTS_ARRAY) + { + m_pointsArray.resize(m_header.numPoints); + + size_t count = fread(m_pointsArray.data(), sizeof(float3), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::load(): Failed to read points array of " << filename << '\n'; + fclose(file); + return false; + } + } + + // Thickness array (float) + // This array keeps the thickness of hair strands at point locations, + // therefore the size of this array is equal to the number of points. + // Each entry is a 32-bit floating point number. + if (m_header.bits & HAS_THICKNESS_ARRAY) + { + m_thicknessArray.resize(m_header.numPoints); + + size_t count = fread(m_thicknessArray.data(), sizeof(float), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::load(): Failed to read thickness array of " << filename << '\n'; + fclose(file); + return false; + } + } + + // Transparency array (float) + // This array keeps the transparency of hair strands at point locations, + // therefore the size of this array is equal to the number of points. + // Each entry is a 32-bit floating point number. + if (m_header.bits & HAS_TRANSPARENCY_ARRAY) + { + m_transparencyArray.resize(m_header.numPoints); + + size_t count = fread(m_transparencyArray.data(), sizeof(float), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::load(): Failed to read transparency array of " << filename << '\n'; + fclose(file); + return false; + } + } + + // Color array (float) + // This array keeps the color of hair strands at point locations, + // therefore the size of this array is three times the number of points. + // Each entry is a 32-bit floating point number, and each color is defined by 3 consecutive numbers + // that correspond to red, green, and blue components. + if (m_header.bits & HAS_COLOR_ARRAY) + { + m_colorArray.resize(m_header.numPoints); + + size_t count = fread(m_colorArray.data(), sizeof(float3), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::load(): Failed to read color array of " << filename << '\n'; + fclose(file); + return false; + } + } + + fclose(file); + + return true; +} + +bool Hair::save(const std::string& filename) +{ + // The points array must always be present and defines the sizes for thickness, transparency and color arrays when those are present. + if (m_pointsArray.empty()) + { + std::cerr << "ERROR: Hair::save(): Points array for " << filename << " is empty.\n"; + return false; + } + m_header.bits = HAS_POINTS_ARRAY; + + if (!m_segmentsArray.empty()) + { + m_header.bits |= HAS_SEGMENTS_ARRAY; + + if (m_header.numStrands != static_cast(m_segmentsArray.size())) + { + std::cerr << "ERROR: Hair::save(): Segments arrays for " << filename << " has incorrect size " << m_segmentsArray.size() << ", numStrands = "<< m_header.numStrands << '\n'; + return false; + } + else if (m_header.numPoints != m_header.numStrands * (m_header.numSegments + 1)) + { + std::cerr << "ERROR: Hair::save(): Number of points " << m_header.numPoints << " for " << filename << " not matching expected count " << m_header.numStrands * (m_header.numSegments + 1) << '\n'; + return false; + } + } + if (!m_thicknessArray.empty()) + { + m_header.bits |= HAS_THICKNESS_ARRAY; + + if (m_header.numPoints != static_cast(m_thicknessArray.size())) + { + std::cerr << "ERROR: Hair::save(): Thickness array for " << filename << " has incorrect size " << m_thicknessArray.size() << ", numPoints = "<< m_header.numPoints << '\n'; + return false; + } + } + if (!m_transparencyArray.empty()) + { + m_header.bits |= HAS_TRANSPARENCY_ARRAY; + + if (m_header.numPoints != static_cast(m_transparencyArray.size())) + { + std::cerr << "ERROR: Hair::save(): Transparency array for " << filename << " has incorrect size " << m_transparencyArray.size() << ", numPoints = "<< m_header.numPoints << '\n'; + return false; + } + } + if (!m_colorArray.empty()) + { + m_header.bits |= HAS_COLOR_ARRAY; + + if (m_header.numPoints != static_cast(m_colorArray.size())) + { + std::cerr << "ERROR: Hair::save(): Color array for " << filename << " has incorrect size " << m_colorArray.size() << ", numPoints = "<< m_header.numPoints << '\n'; + return false; + } + } + + // All other fields inside the Hair header are supposed to be filled by the caller. + + FILE *file = fopen(filename.c_str(), "wb"); + if (!file) + { + std::cerr << "ERROR: Hair::save(): fopen(" << filename << ") failed.\n"; + return false; + } + + // A HAIR file begins with a 128-Byte long header. + size_t count = fwrite(&m_header, sizeof(Header), 1, file); + if (count < 1) + { + std::cerr << "ERROR: Hair::save(): Writing the header of " << filename << " failed.\n"; + fclose(file); + return false; + } + + // The 3D hair model consists of strands, each one of which is represented by a number of line segments. + + if (m_header.bits & HAS_SEGMENTS_ARRAY) + { + size_t count = fwrite(m_segmentsArray.data(), sizeof(unsigned short), m_header.numStrands, file); + if (count < m_header.numStrands) + { + std::cerr << "ERROR: Hair::save(): Failed to write segments array of " << filename << '\n'; + fclose(file); + return false; + } + } + + if (m_header.bits & HAS_POINTS_ARRAY) + { + size_t count = fwrite(m_pointsArray.data(), sizeof(float3), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::save(): Failed to write points array of " << filename << '\n'; + fclose(file); + return false; + } + } + + if (m_header.bits & HAS_THICKNESS_ARRAY) + { + size_t count = fread(m_thicknessArray.data(), sizeof(float), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::save(): Failed to write thickness array of " << filename << '\n'; + fclose(file); + return false; + } + } + + if (m_header.bits & HAS_TRANSPARENCY_ARRAY) + { + size_t count = fwrite(m_transparencyArray.data(), sizeof(float), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::save(): Failed to write transparency array of " << filename << '\n'; + fclose(file); + return false; + } + } + + if (m_header.bits & HAS_COLOR_ARRAY) + { + size_t count = fwrite(m_colorArray.data(), sizeof(float3), m_header.numPoints, file); + if (count < m_header.numPoints) + { + std::cerr << "ERROR: Hair::save(): Failed to write color array of " << filename << '\n'; + fclose(file); + return false; + } + } + + fclose(file); + + return true; +} diff --git a/apps/MDL_sdf/src/LoaderIES.cpp b/apps/MDL_sdf/src/LoaderIES.cpp new file mode 100644 index 00000000..b2cf32bb --- /dev/null +++ b/apps/MDL_sdf/src/LoaderIES.cpp @@ -0,0 +1,644 @@ +/* + * Copyright (c) 2013-2022, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/LoaderIES.h" +#include "inc/MyAssert.h" + +#include +#include +#include + +LoaderIES::LoaderIES() +{ + m_iesData.file.format = IESNA_86; + + m_iesData.lamp.numLamps = 0; + m_iesData.lamp.lumenPerLamp = 0.0f; + m_iesData.lamp.multiplier = 1.0f; + m_iesData.lamp.hasTilt = false; // Default to TILT=NONE. + m_iesData.lamp.tilt.orientation = LO_VERTICAL; + m_iesData.lamp.tilt.numPairs = 0; + + m_iesData.units = U_METERS; + + m_iesData.dimension.width = 0.0f; + m_iesData.dimension.length = 0.0f; + m_iesData.dimension.height = 0.0f; + + m_iesData.electrical.ballastFactor = 1.0f; + m_iesData.electrical.ballastLampPhotometricFactor = 1.0f; + m_iesData.electrical.inputWatts = 0.0f; + + m_iesData.photometric.goniometerType = TYPE_C; + m_iesData.photometric.numVerticalAngles = 0; + m_iesData.photometric.numHorizontalAngles = 0; +} + +//LoaderIES::~LoaderIES() +//{ +//} + + +bool LoaderIES::load(const std::string& filename) +{ + std::ifstream inputStream(filename); + + if (!inputStream) + { + std::cerr << "ERROR: LoaderIES::load() Failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: LoaderIES::load() failed to read file " << filename << '\n'; + return false; + } + + m_source = data.str(); + m_index = 0; + m_lookbackIndex = m_index; + + m_iesData.file.name = filename; // Store the found file to use the path to search for possible tilt filename. + + return true; +} + + +bool LoaderIES::loadTilt(const std::string& filename) +{ + // This is a "tilt" filename stored inside the IES file. + std::filesystem::path tilt(filename); + + // First check if this exists in the current working directory, which is the module directory in my apps. + // That's normally not the case if the IES is not loaded from the that directory. + if (!std::filesystem::exists(tilt)) + { + // Search for it next to the IES file. + // Concatenate the path of IES files with the filename and extension of the TILT file + tilt = std::filesystem::path(m_iesData.file.name).remove_filename(); + tilt += std::filesystem::path(filename).filename(); + + if (!std::filesystem::exists(tilt)) + { + std::cerr << "ERROR: LoaderIES::load() did not find " << filename << " or " << tilt << '\n'; + return false; + } + } + + std::ifstream inputStream(tilt.string()); + + if (!inputStream) + { + std::cerr << "ERROR: LoaderIES::load() Failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: LoaderIES::load() failed to read file " << tilt << '\n'; + return false; + } + + // Merge the tilt data string into the source string and handle tilt data as if it was specified with INCLUDE. + std::string prefix = m_source.substr(0, m_index); + std::string suffix = m_source.substr(m_index, std::string::npos); + + m_source = prefix + data.str() + suffix; + + return true; +} + + +LoaderIES::IESTokenType LoaderIES::getNextLine(std::string& token /* , const bool lookback */) +{ + // Line concatenation escape character is handled as whitespace, which is probably not quite right. + const static std::string whitespace = " \t"; // space, tab + const static std::string newline = "\n"; + + //token.clear(); // Make sure the returned token starts empty. + + IESTokenType type = ITT_UNKNOWN; // This return value indicates an error. + + std::string::size_type first; + std::string::size_type last; + + m_lookbackIndex = m_index; // Remember the scan index of this line in case this line needs to be re-read. + + // Prune whitespace at the start of the token + first = m_source.find_first_not_of(whitespace, m_index); + if (first == std::string::npos) + { + token.clear(); + return ITT_EOF; + } + m_index = first; // Skip the leading whitespaces. + + last = m_source.find_first_of(newline, m_index); + if (first == std::string::npos) + { + token.clear(); + return ITT_EOF; + } + + m_index = last + 1; // Skip the token and the newline for the next scan. + m_line++; + + // Prune whitespace at the end of the token. + while ((first < last) && (m_source[last - 1] == ' ' || + m_source[last - 1] == '\t' || + m_source[last - 1] == '\r' || + m_source[last - 1] == '\n')) + { + --last; + } + + // Empty line! This is actually an error condition in the IES file. + if (first == last) + { + token.clear(); + return ITT_EOL; + } + + token = m_source.substr(first, last - first); // Get the line. + return ITT_LINE; +} + +LoaderIES::IESTokenType LoaderIES::getNextToken(std::string& token) +{ + const static std::string whitespace = " \t"; // space, tab + const static std::string newline = "\n"; + const static std::string value = "+-0123456789.eE"; + const static std::string delimiter = " \t\r\n"; // space, tab, carriage return, linefeed + + //token.clear(); // Make sure the returned token starts empty. + + IESTokenType type = ITT_UNKNOWN; // This return value indicates an error. + + std::string::size_type first; + std::string::size_type last; + + bool done = false; + while (!done) + { + // Find first character which is not a whitespace. + first = m_source.find_first_not_of(whitespace, m_index); + if (first == std::string::npos) + { + token.clear(); + return ITT_EOF; + } + + m_lookbackIndex = first; // To be able to handle filenames with spaces it's necessary to look back at the last token's start index. + + // The found character indicates how parsing continues. + char c = m_source[first]; + + // No comments in IES files. + //if (c == '#') // comment until the next newline + //{ + // // m_index = first + 1; // skip '#' // Redundant. + // first = m_source.find_first_of(newline, m_index); // Skip everything until the next newline. + // if (first == std::string::npos) + // { + // token.clear(); + // type = ITT_EOF; + // done = true; + // } + // m_index = first + 1; // skip newline + // m_line++; + //} + //else + if (c == '\r') // carriage return 13 + { + m_index = first + 1; + } + else if (c == '\n') // newline (linefeed 10) // When parsing tokens, also skip this like carriage return. + { + //token.clear(); + //type = ITT_EOL; + //done = true; + m_index = first + 1; + m_line++; + } + else // anything else + { + last = m_source.find_first_of(delimiter, first); + if (last == std::string::npos) + { + last = m_source.size(); + } + m_index = last; // Token has been processed from the m_source. + + token = m_source.substr(first, last - first); // Get the current token. + // Check if token is only built of characters used for numbers. + // (Not perfectly parsing a floating point number but good enough for most filenames.) + if (isdigit(c) || c == '-' || c == '+' || c == '.') // Legal start characters for a floating point number. + { + last = token.find_first_not_of(value, 0); + if (last == std::string::npos) + { + type = ITT_VALUE; + } + } + if (type == ITT_UNKNOWN) // Not a valid number, could still be an option keyword. + { + //std::map::const_iterator it = m_mapMtlKeywords.find(token); + //if (it != m_mapMtlKeywords.end()) + //{ + // type = it->second; // Known keyword. + //} + //else + //{ + type = ITT_IDENTIFIER; + //} + } + done = true; + } + } + + return type; +} + +bool LoaderIES::parse() +{ + std::cout << "LoaderIES::parse() begin.\n"; + + std::string token; + + IESTokenType type = getNextLine(token); // First line can be the format, a label or TILT= + if (type == ITT_LINE) + { + if (token == std::string("IESNA:LM-63-2002")) + { + m_iesData.file.format = IESNA_02; + } + else if (token == std::string("IESNA:LM-63-1995")) + { + m_iesData.file.format = IESNA_95; + } + else if (token == std::string("IESNA91")) + { + m_iesData.file.format = IESNA_91; + } + else // First line is a label or "TILT=". Need to reread. + { + m_iesData.file.format = IESNA_86; + m_index = m_lookbackIndex; // Rewind. + } + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading format.\n"; + return false; + } + + // Label or "TILT=" + while (type == ITT_LINE) + { + type = getNextLine(token); + if (type == ITT_LINE) + { + if (token.substr(0, 5) == "TILT=") + { + break; // Labels done. No rewind, just keep the line in token. + } + else + { + m_iesData.label.push_back(token); // Everything is a label until the TILT token. + } + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading labels.\n"; + return false; + } + } + + // token == "TILT=" + if (type == ITT_LINE) + { + std::string tilt = token.substr(5, std::string::npos); // Get the token behind "TILT=". + + m_iesData.lamp.tiltFilename = tilt; // This is the tilt filename in case TILT is neither NONE nor INCLUDE. + + if (tilt != std::string("NONE")) + { + m_iesData.lamp.hasTilt = true; // Tilt data already included or specified in a separate file. + + if (tilt != std::string("INCLUDE")) + { + // Simplest method is to insert the file contents at the current m_index into the source, + // that is directly behind the TILT line, and keep on parsing as if it was "INCLUDE". + if (!loadTilt(tilt)) + { + return false; + } + } + } + } + + if (m_iesData.lamp.hasTilt) // Read tilt data. + { + type = getNextToken(token); + if (ITT_VALUE) + { + const int ori = atoi(token.c_str()); + MY_ASSERT(1 <= ori && ori <= 3); + m_iesData.lamp.tilt.orientation = (IESLampOrientation) ori; + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading tilt.orientation.\n"; + return false; + } + + type = getNextToken(token); + if (ITT_VALUE) + { + m_iesData.lamp.tilt.numPairs = atoi(token.c_str()); + + m_iesData.lamp.tilt.angles.reserve(m_iesData.lamp.tilt.numPairs); + + for (int i = 0; i < m_iesData.lamp.tilt.numPairs; ++i) + { + type = getNextToken(token); + if (ITT_VALUE) + { + m_iesData.lamp.tilt.angles.push_back((float) atof(token.c_str())); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading tilt.angles.\n"; + return false; + } + } + + m_iesData.lamp.tilt.factors.reserve(m_iesData.lamp.tilt.numPairs); + + for (int i = 0; i < m_iesData.lamp.tilt.numPairs; ++i) + { + type = getNextToken(token); + if (ITT_VALUE) + { + m_iesData.lamp.tilt.factors.push_back((float) atof(token.c_str())); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading tilt.factors.\n"; + return false; + } + } + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading tilt.numPairs.\n"; + return false; + } + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.lamp.numLamps = atoi(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading lamp.numLamps.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.lamp.lumenPerLamp = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading lamp.lumenPerLamp.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.lamp.multiplier = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading lamp.multiplier.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.photometric.numVerticalAngles = atoi(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading photometric.numVerticalAngles.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.photometric.numHorizontalAngles = atoi(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading photometric.numHorizontalAngles.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + int gonio = atoi(token.c_str()); + MY_ASSERT(1 <= gonio && gonio <= 3); + m_iesData.photometric.goniometerType = (IESGoniometerType) gonio; + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading photometric.goniometerType.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + int units = atoi(token.c_str()); + MY_ASSERT(1 <= units && units <= 2); + m_iesData.units = (IESUnits) units; + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading units.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.dimension.width = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading dimension.width.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.dimension.length = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading dimension.length.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.dimension.height = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading dimension.height.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.electrical.ballastFactor = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading electrical.ballastFactor.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.electrical.ballastLampPhotometricFactor = (float) atof(token.c_str()); // Exception: In IESNA:LM-63-1995 this float value is meant for "future use". + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading electrical.ballastLampPhotometricFactor.\n"; + return false; + } + + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.electrical.inputWatts = (float) atof(token.c_str()); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading electrical.inputWatts.\n"; + return false; + } + + m_iesData.photometric.verticalAngles.reserve(m_iesData.photometric.numVerticalAngles); + + for (int i = 0; i < m_iesData.photometric.numVerticalAngles; ++i) + { + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.photometric.verticalAngles.push_back((float) atof(token.c_str())); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading photometric.verticalAngles.\n"; + return false; + } + } + MY_ASSERT((size_t) m_iesData.photometric.numVerticalAngles == m_iesData.photometric.verticalAngles.size()); + + m_iesData.photometric.horizontalAngles.reserve(m_iesData.photometric.numHorizontalAngles); + + for (int i = 0; i < m_iesData.photometric.numHorizontalAngles; ++i) + { + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.photometric.horizontalAngles.push_back((float) atof(token.c_str())); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading photometric.horizontalAngles.\n"; + return false; + } + } + MY_ASSERT((size_t) m_iesData.photometric.numHorizontalAngles == m_iesData.photometric.horizontalAngles.size()); + + // numHorizontalAngles * numVerticalAngles candela values. + // 2D index [h][v] <==> linear index [h * numVerticalAngles + v] + m_iesData.photometric.candela.reserve(m_iesData.photometric.numHorizontalAngles * m_iesData.photometric.numVerticalAngles); + + for (int i = 0; i < m_iesData.photometric.numHorizontalAngles * m_iesData.photometric.numVerticalAngles; ++i) + { + type = getNextToken(token); + if (type == ITT_VALUE) + { + m_iesData.photometric.candela.push_back((float) atof(token.c_str())); + } + else + { + std::cerr << "ERROR: LoaderIES::parse(): Unexpected token \'" << token << "\' when reading photometric.candela.\n"; + return false; + } + } + MY_ASSERT((size_t) (m_iesData.photometric.numVerticalAngles * m_iesData.photometric.numHorizontalAngles) == m_iesData.photometric.candela.size()); + + m_source.clear(); // The input data isn't needed anymore. + + std::cout << "LoaderIES::parse() done.\n"; + + return true; +} + + +const IESData& LoaderIES::getData() +{ + return m_iesData; +} \ No newline at end of file diff --git a/apps/MDL_sdf/src/MaterialMDL.cpp b/apps/MDL_sdf/src/MaterialMDL.cpp new file mode 100644 index 00000000..655e4763 --- /dev/null +++ b/apps/MDL_sdf/src/MaterialMDL.cpp @@ -0,0 +1,392 @@ +/* +* Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. +*/ + +#include "inc/MaterialMDL.h" + +#include +#include +//#include + +#include "inc/MyAssert.h" + + +MaterialMDL::MaterialMDL(const MaterialDeclaration& declaration) + : m_isValid(false) // Set to true once all MDL and OptiX initializations have been done. + , m_declaration(declaration) + , m_indexShader(-1) // Invalid +{ +} + +bool MaterialMDL::getIsValid() const +{ + return m_isValid; +} + +void MaterialMDL::setIsValid(bool value) +{ + m_isValid = value; +} + + +std::string MaterialMDL::getReference() const +{ + return m_declaration.nameReference; +} + +std::string MaterialMDL::getName() const +{ + return m_declaration.nameMaterial; +} + +std::string MaterialMDL::getPath() const +{ + return m_declaration.pathMaterial; +} + +void MaterialMDL::storeMaterialInfo(const int indexShader, + mi::neuraylib::IFunction_definition const* mat_def, + mi::neuraylib::ICompiled_material const* comp_mat, + mi::neuraylib::ITarget_value_layout const* arg_block_layout, + mi::neuraylib::ITarget_argument_block const* arg_block) +{ + m_indexShader = indexShader; + m_name = mat_def->get_mdl_name(); + + char* arg_block_data = nullptr; + + if (arg_block != nullptr) + { + m_arg_block = mi::base::Handle(arg_block->clone()); + arg_block_data = m_arg_block->get_data(); + } + + mi::base::Handle anno_list(mat_def->get_parameter_annotations()); + + for (mi::Size j = 0, num_params = comp_mat->get_parameter_count(); j < num_params; ++j) + { + const char* name = comp_mat->get_parameter_name(j); + if (name == nullptr) + { + continue; + } + + // Determine the type of the argument + mi::base::Handle arg(comp_mat->get_argument(j)); + mi::neuraylib::IValue::Kind kind = arg->get_kind(); + + Param_info::Param_kind param_kind = Param_info::PK_UNKNOWN; + Param_info::Param_kind param_array_elem_kind = Param_info::PK_UNKNOWN; + mi::Size param_array_size = 0; + mi::Size param_array_pitch = 0; + + const Enum_type_info* enum_type = nullptr; + + switch (kind) + { + case mi::neuraylib::IValue::VK_FLOAT: + param_kind = Param_info::PK_FLOAT; + break; + case mi::neuraylib::IValue::VK_COLOR: + param_kind = Param_info::PK_COLOR; + break; + case mi::neuraylib::IValue::VK_BOOL: + param_kind = Param_info::PK_BOOL; + break; + case mi::neuraylib::IValue::VK_INT: + param_kind = Param_info::PK_INT; + break; + case mi::neuraylib::IValue::VK_VECTOR: + { + mi::base::Handle val(arg.get_interface()); + mi::base::Handle val_type(val->get_type()); + mi::base::Handle elem_type(val_type->get_element_type()); + + if (elem_type->get_kind() == mi::neuraylib::IType::TK_FLOAT) + { + switch (val_type->get_size()) + { + case 2: + param_kind = Param_info::PK_FLOAT2; + break; + case 3: + param_kind = Param_info::PK_FLOAT3; + break; + } + } + } + break; + case mi::neuraylib::IValue::VK_ARRAY: + { + mi::base::Handle val(arg.get_interface()); + mi::base::Handle val_type(val->get_type()); + mi::base::Handle elem_type(val_type->get_element_type()); + + // we currently only support arrays of some values + switch (elem_type->get_kind()) + { + case mi::neuraylib::IType::TK_FLOAT: + param_array_elem_kind = Param_info::PK_FLOAT; + break; + case mi::neuraylib::IType::TK_COLOR: + param_array_elem_kind = Param_info::PK_COLOR; + break; + case mi::neuraylib::IType::TK_BOOL: + param_array_elem_kind = Param_info::PK_BOOL; + break; + case mi::neuraylib::IType::TK_INT: + param_array_elem_kind = Param_info::PK_INT; + break; + case mi::neuraylib::IType::TK_VECTOR: + { + mi::base::Handle val_type(elem_type.get_interface()); + mi::base::Handle velem_type(val_type->get_element_type()); + + if (velem_type->get_kind() == mi::neuraylib::IType::TK_FLOAT) + { + switch (val_type->get_size()) + { + case 2: + param_array_elem_kind = Param_info::PK_FLOAT2; + break; + case 3: + param_array_elem_kind = Param_info::PK_FLOAT3; + break; + } + } + } + break; + default: + break; + } + if (param_array_elem_kind != Param_info::PK_UNKNOWN) + { + param_kind = Param_info::PK_ARRAY; + param_array_size = val_type->get_size(); + + // determine pitch of array if there are at least two elements + if (param_array_size > 1) + { + mi::neuraylib::Target_value_layout_state array_state(arg_block_layout->get_nested_state(j)); + mi::neuraylib::Target_value_layout_state next_elem_state(arg_block_layout->get_nested_state(1, array_state)); + + mi::neuraylib::IValue::Kind kind; + mi::Size param_size; + + mi::Size start_offset = arg_block_layout->get_layout(kind, param_size, array_state); + mi::Size next_offset = arg_block_layout->get_layout(kind, param_size, next_elem_state); + + param_array_pitch = next_offset - start_offset; + } + } + } + break; + case mi::neuraylib::IValue::VK_ENUM: + { + mi::base::Handle val(arg.get_interface()); + mi::base::Handle val_type(val->get_type()); + + // prepare info for this enum type if not seen so far + const Enum_type_info* info = get_enum_type(val_type->get_symbol()); + if (info == nullptr) + { + std::shared_ptr p(new Enum_type_info()); + + for (mi::Size i = 0, n = val_type->get_size(); i < n; ++i) + { + p->add(val_type->get_value_name(i), val_type->get_value_code(i)); + } + add_enum_type(val_type->get_symbol(), p); + info = p.get(); + } + enum_type = info; + + param_kind = Param_info::PK_ENUM; + } + break; + case mi::neuraylib::IValue::VK_STRING: + param_kind = Param_info::PK_STRING; + break; + case mi::neuraylib::IValue::VK_TEXTURE: + param_kind = Param_info::PK_TEXTURE; + break; + case mi::neuraylib::IValue::VK_LIGHT_PROFILE: + param_kind = Param_info::PK_LIGHT_PROFILE; + break; + case mi::neuraylib::IValue::VK_BSDF_MEASUREMENT: + param_kind = Param_info::PK_BSDF_MEASUREMENT; + break; + default: + // Unsupported? -> skip + continue; + } + + // Get the offset of the argument within the target argument block + mi::neuraylib::Target_value_layout_state state(arg_block_layout->get_nested_state(j)); + mi::neuraylib::IValue::Kind kind2; + mi::Size param_size; + mi::Size offset = arg_block_layout->get_layout(kind2, param_size, state); + if (kind != kind2) + { + continue; // layout is invalid -> skip + } + + Param_info param_info(j, + name, + name, + /*group_name=*/ "", + param_kind, + param_array_elem_kind, + param_array_size, + param_array_pitch, + arg_block_data + offset, + enum_type); + + // Check for annotation info + mi::base::Handle anno_block(anno_list->get_annotation_block(name)); + if (anno_block) + { + mi::neuraylib::Annotation_wrapper annos(anno_block.get()); + mi::Size anno_index = annos.get_annotation_index("::anno::hard_range(float,float)"); + if (anno_index != mi::Size(-1)) + { + annos.get_annotation_param_value(anno_index, 0, param_info.range_min()); + annos.get_annotation_param_value(anno_index, 1, param_info.range_max()); + } + else + { + anno_index = annos.get_annotation_index("::anno::soft_range(float,float)"); + if (anno_index != mi::Size(-1)) + { + annos.get_annotation_param_value(anno_index, 0, param_info.range_min()); + annos.get_annotation_param_value(anno_index, 1, param_info.range_max()); + } + } + anno_index = annos.get_annotation_index("::anno::display_name(string)"); + if (anno_index != mi::Size(-1)) + { + char const* display_name = nullptr; + annos.get_annotation_param_value(anno_index, 0, display_name); + param_info.set_display_name(display_name); + } + anno_index = annos.get_annotation_index("::anno::in_group(string)"); + if (anno_index != mi::Size(-1)) + { + char const* group_name = nullptr; + annos.get_annotation_param_value(anno_index, 0, group_name); + param_info.set_group_name(group_name); + } + } + + add_sorted_by_group(param_info); + } +} + +// Add the parameter information as last entry of the corresponding group, or to the +// end of the list, if no group name is available. +void MaterialMDL::add_sorted_by_group(const Param_info& info) +{ + bool group_found = false; + if (info.group_name() != nullptr) + { + for (std::list::iterator it = params().begin(); it != params().end(); ++it) + { + const bool same_group = (it->group_name() != nullptr && strcmp(it->group_name(), info.group_name()) == 0); + if (group_found && !same_group) + { + m_params.insert(it, info); + return; + } + if (same_group) + { + group_found = true; + } + } + } + m_params.push_back(info); +} + +// Add a new enum type to the map of used enum types. +void MaterialMDL::add_enum_type(const std::string name, std::shared_ptr enum_info) +{ + m_mapEnumTypes[name] = enum_info; +} + +// Lookup enum type info for a given enum type absolute MDL name. +const Enum_type_info* MaterialMDL::get_enum_type(const std::string name) +{ + std::map >::const_iterator it = m_mapEnumTypes.find(name); + if (it != m_mapEnumTypes.end()) + { + return it->second.get(); + } + return nullptr; +} + +int MaterialMDL::getShaderIndex() const +{ + return m_indexShader; +} + +// Get the name of the material. +char const* MaterialMDL::name() const +{ + return m_name.c_str(); +} + +// Get the parameters of this material. +std::list& MaterialMDL::params() +{ + return m_params; +} + +// Get the modifiable argument block data. +char* MaterialMDL::getArgumentBlockData() const +{ + if (!m_arg_block) + { + return nullptr; + } + return m_arg_block->get_data(); +} + +// Get the modifiable argument block size. +size_t MaterialMDL::getArgumentBlockSize() const +{ + if (!m_arg_block) + { + return 0; + } + return m_arg_block->get_size(); +} + +// Get the scene data names referenced by the compiled material. +std::vector const& MaterialMDL::getReferencedSceneDataNames() const +{ + return m_referencedSceneDataNames; +} + diff --git a/apps/MDL_sdf/src/NVMLImpl.cpp b/apps/MDL_sdf/src/NVMLImpl.cpp new file mode 100644 index 00000000..0137e38a --- /dev/null +++ b/apps/MDL_sdf/src/NVMLImpl.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +// The cfgmgr32 header is necessary for interrogating driver information in the registry. +#include +// For convenience the library is also linked in automatically using the #pragma command. +#pragma comment(lib, "Cfgmgr32.lib") +#else +#include +#endif + +#include "inc/NVMLImpl.h" + +#include +#include + +#ifdef _WIN32 + +static void *nvmlLoadFromDriverStore(const char* nvmlDllName) +{ + void* handle = NULL; + + // We are going to look for the OpenGL driver which lives next to nvoptix.dll and nvml.dll. + // 0 (null) will be returned if any errors occured. + + static const char* deviceInstanceIdentifiersGUID = "{4d36e968-e325-11ce-bfc1-08002be10318}"; + const ULONG flags = CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT; + ULONG deviceListSize = 0; + + if (CM_Get_Device_ID_List_SizeA(&deviceListSize, deviceInstanceIdentifiersGUID, flags) != CR_SUCCESS) + { + return NULL; + } + + char* deviceNames = (char*) malloc(deviceListSize); + + if (CM_Get_Device_ID_ListA(deviceInstanceIdentifiersGUID, deviceNames, deviceListSize, flags)) + { + free(deviceNames); + return NULL; + } + + DEVINST devID = 0; + + // Continue to the next device if errors are encountered. + for (char* deviceName = deviceNames; *deviceName; deviceName += strlen(deviceName) + 1) + { + if (CM_Locate_DevNodeA(&devID, deviceName, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) + { + continue; + } + + HKEY regKey = 0; + if (CM_Open_DevNode_Key(devID, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, ®Key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) + { + continue; + } + + const char* valueName = "OpenGLDriverName"; + DWORD valueSize = 0; + + LSTATUS ret = RegQueryValueExA(regKey, valueName, NULL, NULL, NULL, &valueSize); + if (ret != ERROR_SUCCESS) + { + RegCloseKey(regKey); + continue; + } + + char* regValue = (char*) malloc(valueSize); + ret = RegQueryValueExA(regKey, valueName, NULL, NULL, (LPBYTE) regValue, &valueSize); + if (ret != ERROR_SUCCESS) + { + free(regValue); + RegCloseKey(regKey); + continue; + } + + // Strip the OpenGL driver dll name from the string then create a new string with + // the path and the nvoptix.dll name + for (int i = valueSize - 1; i >= 0 && regValue[i] != '\\'; --i) + { + regValue[i] = '\0'; + } + + size_t newPathSize = strlen(regValue) + strlen(nvmlDllName) + 1; + char* dllPath = (char*) malloc(newPathSize); + strcpy(dllPath, regValue); + strcat(dllPath, nvmlDllName); + + free(regValue); + RegCloseKey(regKey); + + handle = LoadLibraryA((LPCSTR) dllPath); + free(dllPath); + + if (handle) + { + break; + } + } + + free(deviceNames); + + return handle; +} + +static void *nvmlLoadFromSystemDirectory(const char* nvmlDllName) +{ + // Get the size of the path first, then allocate. + const unsigned int size = GetSystemDirectoryA(NULL, 0); + if (size == 0) + { + // Couldn't get the system path size, so bail. + return NULL; + } + + // Alloc enough memory to concatenate with "\\nvml.dll". + const size_t pathSize = size + 1 + strlen(nvmlDllName); + + char* systemPath = (char*) malloc(pathSize); + + if (GetSystemDirectoryA(systemPath, size) != size - 1) + { + // Something went wrong. + free(systemPath); + return NULL; + } + + strcat(systemPath, "\\"); + strcat(systemPath, nvmlDllName); + + void* handle = LoadLibraryA(systemPath); + + free(systemPath); + + return handle; +} + +static void* nvmlLoadWindowsDll(void) +{ + const char* nvmlDllName = "nvml.dll"; + + void* handle = nvmlLoadFromDriverStore(nvmlDllName); + + if (!handle) + { + handle = nvmlLoadFromSystemDirectory(nvmlDllName); + // If the nvml.dll is still not found here, something is wrong with the display driver installation. + } + + return handle; +} +#endif + + +NVMLImpl::NVMLImpl() + : m_handle(0) +{ + // Fill all existing NVML function pointers with nullptr. + memset(&m_api, 0, sizeof(NVMLFunctionTable)); +} + +//NVMLImpl::~NVMLImpl() +//{ +//} + + +// Helper function to get the entry point address in a loaded library just to abstract the platform in GET_FUNC macro. +static void* getFunc(void* handle, const char* name) +{ +#ifdef _WIN32 + return GetProcAddress((HMODULE) handle, name); +#else + return dlsym(handle, name); +#endif +} + +bool NVMLImpl::initFunctionTable() +{ +#ifdef _WIN32 + void* handle = nvmlLoadWindowsDll(); + if (!handle) + { + std::cerr << "nvml.dll not found\n"; + return false; + } +#else + void* handle = dlopen("libnvidia-ml.so.1", RTLD_NOW); + if (!handle) + { + std::cerr << "libnvidia-ml.so.1 not found\n"; + return false; + } +#endif + +// Local macro to get the NVML entry point addresses and assign them to the NVMLFunctionTable members with the right type. +// Some of the NVML functions are versioned by a #define adding a version suffix (_v2, _v3) to the name, +// which requires a set of two macros to resolve the unversioned function name to the versioned one. + +#define GET_FUNC_V(name) \ +{ \ + const void* func = getFunc(handle, #name); \ + if (func) { \ + m_api.name = reinterpret_cast(func); \ + } else { \ + std::cerr << "ERROR: " << #name << " is nullptr\n"; \ + success = false; \ + } \ +} + +#define GET_FUNC(name) GET_FUNC_V(name) + + + bool success = true; + + GET_FUNC(nvmlInit); + //GET_FUNC(nvmlInitWithFlags); + GET_FUNC(nvmlShutdown); + //GET_FUNC(nvmlErrorString); + //GET_FUNC(nvmlSystemGetDriverVersion); + //GET_FUNC(nvmlSystemGetNVMLVersion); + //GET_FUNC(nvmlSystemGetCudaDriverVersion); + //GET_FUNC(nvmlSystemGetCudaDriverVersion_v2); + //GET_FUNC(nvmlSystemGetProcessName); + //GET_FUNC(nvmlUnitGetCount); + //GET_FUNC(nvmlUnitGetHandleByIndex); + //GET_FUNC(nvmlUnitGetUnitInfo); + //GET_FUNC(nvmlUnitGetLedState); + //GET_FUNC(nvmlUnitGetPsuInfo); + //GET_FUNC(nvmlUnitGetTemperature); + //GET_FUNC(nvmlUnitGetFanSpeedInfo); + //GET_FUNC(nvmlUnitGetDevices); + //GET_FUNC(nvmlSystemGetHicVersion); + //GET_FUNC(nvmlDeviceGetCount); + //GET_FUNC(nvmlDeviceGetAttributes); + //GET_FUNC(nvmlDeviceGetHandleByIndex); + //GET_FUNC(nvmlDeviceGetHandleBySerial); + //GET_FUNC(nvmlDeviceGetHandleByUUID); + GET_FUNC(nvmlDeviceGetHandleByPciBusId); + //GET_FUNC(nvmlDeviceGetName); + //GET_FUNC(nvmlDeviceGetBrand); + //GET_FUNC(nvmlDeviceGetIndex); + //GET_FUNC(nvmlDeviceGetSerial); + //GET_FUNC(nvmlDeviceGetMemoryAffinity); + //GET_FUNC(nvmlDeviceGetCpuAffinityWithinScope); + //GET_FUNC(nvmlDeviceGetCpuAffinity); + //GET_FUNC(nvmlDeviceSetCpuAffinity); + //GET_FUNC(nvmlDeviceClearCpuAffinity); + //GET_FUNC(nvmlDeviceGetTopologyCommonAncestor); + //GET_FUNC(nvmlDeviceGetTopologyNearestGpus); + //GET_FUNC(nvmlSystemGetTopologyGpuSet); + //GET_FUNC(nvmlDeviceGetP2PStatus); + //GET_FUNC(nvmlDeviceGetUUID); + //GET_FUNC(nvmlVgpuInstanceGetMdevUUID); + //GET_FUNC(nvmlDeviceGetMinorNumber); + //GET_FUNC(nvmlDeviceGetBoardPartNumber); + //GET_FUNC(nvmlDeviceGetInforomVersion); + //GET_FUNC(nvmlDeviceGetInforomImageVersion); + //GET_FUNC(nvmlDeviceGetInforomConfigurationChecksum); + //GET_FUNC(nvmlDeviceValidateInforom); + //GET_FUNC(nvmlDeviceGetDisplayMode); + //GET_FUNC(nvmlDeviceGetDisplayActive); + //GET_FUNC(nvmlDeviceGetPersistenceMode); + //GET_FUNC(nvmlDeviceGetPciInfo); + //GET_FUNC(nvmlDeviceGetMaxPcieLinkGeneration); + //GET_FUNC(nvmlDeviceGetMaxPcieLinkWidth); + //GET_FUNC(nvmlDeviceGetCurrPcieLinkGeneration); + //GET_FUNC(nvmlDeviceGetCurrPcieLinkWidth); + //GET_FUNC(nvmlDeviceGetPcieThroughput); + //GET_FUNC(nvmlDeviceGetPcieReplayCounter); + //GET_FUNC(nvmlDeviceGetClockInfo); + //GET_FUNC(nvmlDeviceGetMaxClockInfo); + //GET_FUNC(nvmlDeviceGetApplicationsClock); + //GET_FUNC(nvmlDeviceGetDefaultApplicationsClock); + //GET_FUNC(nvmlDeviceResetApplicationsClocks); + //GET_FUNC(nvmlDeviceGetClock); + //GET_FUNC(nvmlDeviceGetMaxCustomerBoostClock); + //GET_FUNC(nvmlDeviceGetSupportedMemoryClocks); + //GET_FUNC(nvmlDeviceGetSupportedGraphicsClocks); + //GET_FUNC(nvmlDeviceGetAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceSetAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceSetDefaultAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceGetFanSpeed); + //GET_FUNC(nvmlDeviceGetFanSpeed_v2); + //GET_FUNC(nvmlDeviceGetTemperature); + //GET_FUNC(nvmlDeviceGetTemperatureThreshold); + //GET_FUNC(nvmlDeviceGetPerformanceState); + //GET_FUNC(nvmlDeviceGetCurrentClocksThrottleReasons); + //GET_FUNC(nvmlDeviceGetSupportedClocksThrottleReasons); + //GET_FUNC(nvmlDeviceGetPowerState); + //GET_FUNC(nvmlDeviceGetPowerManagementMode); + //GET_FUNC(nvmlDeviceGetPowerManagementLimit); + //GET_FUNC(nvmlDeviceGetPowerManagementLimitConstraints); + //GET_FUNC(nvmlDeviceGetPowerManagementDefaultLimit); + //GET_FUNC(nvmlDeviceGetPowerUsage); + //GET_FUNC(nvmlDeviceGetTotalEnergyConsumption); + //GET_FUNC(nvmlDeviceGetEnforcedPowerLimit); + //GET_FUNC(nvmlDeviceGetGpuOperationMode); + //GET_FUNC(nvmlDeviceGetMemoryInfo); + //GET_FUNC(nvmlDeviceGetComputeMode); + //GET_FUNC(nvmlDeviceGetCudaComputeCapability); + //GET_FUNC(nvmlDeviceGetEccMode); + //GET_FUNC(nvmlDeviceGetBoardId); + //GET_FUNC(nvmlDeviceGetMultiGpuBoard); + //GET_FUNC(nvmlDeviceGetTotalEccErrors); + //GET_FUNC(nvmlDeviceGetDetailedEccErrors); + //GET_FUNC(nvmlDeviceGetMemoryErrorCounter); + //GET_FUNC(nvmlDeviceGetUtilizationRates); + //GET_FUNC(nvmlDeviceGetEncoderUtilization); + //GET_FUNC(nvmlDeviceGetEncoderCapacity); + //GET_FUNC(nvmlDeviceGetEncoderStats); + //GET_FUNC(nvmlDeviceGetEncoderSessions); + //GET_FUNC(nvmlDeviceGetDecoderUtilization); + //GET_FUNC(nvmlDeviceGetFBCStats); + //GET_FUNC(nvmlDeviceGetFBCSessions); + //GET_FUNC(nvmlDeviceGetDriverModel); + //GET_FUNC(nvmlDeviceGetVbiosVersion); + //GET_FUNC(nvmlDeviceGetBridgeChipInfo); + //GET_FUNC(nvmlDeviceGetComputeRunningProcesses); + //GET_FUNC(nvmlDeviceGetGraphicsRunningProcesses); + //GET_FUNC(nvmlDeviceOnSameBoard); + //GET_FUNC(nvmlDeviceGetAPIRestriction); + //GET_FUNC(nvmlDeviceGetSamples); + //GET_FUNC(nvmlDeviceGetBAR1MemoryInfo); + //GET_FUNC(nvmlDeviceGetViolationStatus); + //GET_FUNC(nvmlDeviceGetAccountingMode); + //GET_FUNC(nvmlDeviceGetAccountingStats); + //GET_FUNC(nvmlDeviceGetAccountingPids); + //GET_FUNC(nvmlDeviceGetAccountingBufferSize); + //GET_FUNC(nvmlDeviceGetRetiredPages); + //GET_FUNC(nvmlDeviceGetRetiredPages_v2); + //GET_FUNC(nvmlDeviceGetRetiredPagesPendingStatus); + //GET_FUNC(nvmlDeviceGetRemappedRows); + //GET_FUNC(nvmlDeviceGetArchitecture); + //GET_FUNC(nvmlUnitSetLedState); + //GET_FUNC(nvmlDeviceSetPersistenceMode); + //GET_FUNC(nvmlDeviceSetComputeMode); + //GET_FUNC(nvmlDeviceSetEccMode); + //GET_FUNC(nvmlDeviceClearEccErrorCounts); + //GET_FUNC(nvmlDeviceSetDriverModel); + //GET_FUNC(nvmlDeviceSetGpuLockedClocks); + //GET_FUNC(nvmlDeviceResetGpuLockedClocks); + //GET_FUNC(nvmlDeviceSetApplicationsClocks); + //GET_FUNC(nvmlDeviceSetPowerManagementLimit); + //GET_FUNC(nvmlDeviceSetGpuOperationMode); + //GET_FUNC(nvmlDeviceSetAPIRestriction); + //GET_FUNC(nvmlDeviceSetAccountingMode); + //GET_FUNC(nvmlDeviceClearAccountingPids); + GET_FUNC(nvmlDeviceGetNvLinkState); + //GET_FUNC(nvmlDeviceGetNvLinkVersion); + GET_FUNC(nvmlDeviceGetNvLinkCapability); + GET_FUNC(nvmlDeviceGetNvLinkRemotePciInfo); + //GET_FUNC(nvmlDeviceGetNvLinkErrorCounter); + //GET_FUNC(nvmlDeviceResetNvLinkErrorCounters); + //GET_FUNC(nvmlDeviceSetNvLinkUtilizationControl); + //GET_FUNC(nvmlDeviceGetNvLinkUtilizationControl); + //GET_FUNC(nvmlDeviceGetNvLinkUtilizationCounter); + //GET_FUNC(nvmlDeviceFreezeNvLinkUtilizationCounter); + //GET_FUNC(nvmlDeviceResetNvLinkUtilizationCounter); + //GET_FUNC(nvmlEventSetCreate); + //GET_FUNC(nvmlDeviceRegisterEvents); + //GET_FUNC(nvmlDeviceGetSupportedEventTypes); + //GET_FUNC(nvmlEventSetWait); + //GET_FUNC(nvmlEventSetFree); + //GET_FUNC(nvmlDeviceModifyDrainState); + //GET_FUNC(nvmlDeviceQueryDrainState); + //GET_FUNC(nvmlDeviceRemoveGpu); + //GET_FUNC(nvmlDeviceDiscoverGpus); + //GET_FUNC(nvmlDeviceGetFieldValues); + //GET_FUNC(nvmlDeviceGetVirtualizationMode); + //GET_FUNC(nvmlDeviceGetHostVgpuMode); + //GET_FUNC(nvmlDeviceSetVirtualizationMode); + //GET_FUNC(nvmlDeviceGetGridLicensableFeatures); + //GET_FUNC(nvmlDeviceGetProcessUtilization); + //GET_FUNC(nvmlDeviceGetSupportedVgpus); + //GET_FUNC(nvmlDeviceGetCreatableVgpus); + //GET_FUNC(nvmlVgpuTypeGetClass); + //GET_FUNC(nvmlVgpuTypeGetName); + //GET_FUNC(nvmlVgpuTypeGetDeviceID); + //GET_FUNC(nvmlVgpuTypeGetFramebufferSize); + //GET_FUNC(nvmlVgpuTypeGetNumDisplayHeads); + //GET_FUNC(nvmlVgpuTypeGetResolution); + //GET_FUNC(nvmlVgpuTypeGetLicense); + //GET_FUNC(nvmlVgpuTypeGetFrameRateLimit); + //GET_FUNC(nvmlVgpuTypeGetMaxInstances); + //GET_FUNC(nvmlVgpuTypeGetMaxInstancesPerVm); + //GET_FUNC(nvmlDeviceGetActiveVgpus); + //GET_FUNC(nvmlVgpuInstanceGetVmID); + //GET_FUNC(nvmlVgpuInstanceGetUUID); + //GET_FUNC(nvmlVgpuInstanceGetVmDriverVersion); + //GET_FUNC(nvmlVgpuInstanceGetFbUsage); + //GET_FUNC(nvmlVgpuInstanceGetLicenseStatus); + //GET_FUNC(nvmlVgpuInstanceGetType); + //GET_FUNC(nvmlVgpuInstanceGetFrameRateLimit); + //GET_FUNC(nvmlVgpuInstanceGetEccMode); + //GET_FUNC(nvmlVgpuInstanceGetEncoderCapacity); + //GET_FUNC(nvmlVgpuInstanceSetEncoderCapacity); + //GET_FUNC(nvmlVgpuInstanceGetEncoderStats); + //GET_FUNC(nvmlVgpuInstanceGetEncoderSessions); + //GET_FUNC(nvmlVgpuInstanceGetFBCStats); + //GET_FUNC(nvmlVgpuInstanceGetFBCSessions); + //GET_FUNC(nvmlVgpuInstanceGetMetadata); + //GET_FUNC(nvmlDeviceGetVgpuMetadata); + //GET_FUNC(nvmlGetVgpuCompatibility); + //GET_FUNC(nvmlDeviceGetPgpuMetadataString); + //GET_FUNC(nvmlGetVgpuVersion); + //GET_FUNC(nvmlSetVgpuVersion); + //GET_FUNC(nvmlDeviceGetVgpuUtilization); + //GET_FUNC(nvmlDeviceGetVgpuProcessUtilization); + //GET_FUNC(nvmlVgpuInstanceGetAccountingMode); + //GET_FUNC(nvmlVgpuInstanceGetAccountingPids); + //GET_FUNC(nvmlVgpuInstanceGetAccountingStats); + //GET_FUNC(nvmlVgpuInstanceClearAccountingPids); + //GET_FUNC(nvmlGetBlacklistDeviceCount); + //GET_FUNC(nvmlGetBlacklistDeviceInfoByIndex); + //GET_FUNC(nvmlDeviceSetMigMode); + //GET_FUNC(nvmlDeviceGetMigMode); + //GET_FUNC(nvmlDeviceGetGpuInstanceProfileInfo); + //GET_FUNC(nvmlDeviceGetGpuInstancePossiblePlacements); + //GET_FUNC(nvmlDeviceGetGpuInstanceRemainingCapacity); + //GET_FUNC(nvmlDeviceCreateGpuInstance); + //GET_FUNC(nvmlGpuInstanceDestroy); + //GET_FUNC(nvmlDeviceGetGpuInstances); + //GET_FUNC(nvmlDeviceGetGpuInstanceById); + //GET_FUNC(nvmlGpuInstanceGetInfo); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceProfileInfo); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceRemainingCapacity); + //GET_FUNC(nvmlGpuInstanceCreateComputeInstance); + //GET_FUNC(nvmlComputeInstanceDestroy); + //GET_FUNC(nvmlGpuInstanceGetComputeInstances); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceById); + //GET_FUNC(nvmlComputeInstanceGetInfo); + //GET_FUNC(nvmlDeviceIsMigDeviceHandle); + //GET_FUNC(nvmlDeviceGetGpuInstanceId); + //GET_FUNC(nvmlDeviceGetComputeInstanceId); + //GET_FUNC(nvmlDeviceGetMaxMigDeviceCount); + //GET_FUNC(nvmlDeviceGetMigDeviceHandleByIndex); + //GET_FUNC(nvmlDeviceGetDeviceHandleFromMigDeviceHandle); + + return success; +} diff --git a/apps/MDL_sdf/src/Options.cpp b/apps/MDL_sdf/src/Options.cpp new file mode 100644 index 00000000..b1c363e1 --- /dev/null +++ b/apps/MDL_sdf/src/Options.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Options.h" + +#include + +Options::Options() +: m_width(512) +, m_height(512) +, m_mode(0) +, m_optimize(false) +{ +} + +//Options::~Options() +//{ +//} + +bool Options::parseCommandLine(int argc, char *argv[]) +{ + for (int i = 1; i < argc; ++i) + { + const std::string arg(argv[i]); + + if (arg == "?" || arg == "help" || arg == "--help") + { + printUsage(std::string(argv[0])); // Application name. + return false; + } + else if (arg == "-w" || arg == "--width") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_width = atoi(argv[++i]); + } + else if (arg == "-h" || arg == "--height") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_height = atoi(argv[++i]); + } + else if (arg == "-m" || arg == "--mode") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_mode = atoi(argv[++i]); + } + else if (arg == "-o" || arg == "--optimize") + { + m_optimize = true; + } + else if (arg == "-s" || arg == "--system") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_filenameSystem = std::string(argv[++i]); + } + else if (arg == "-d" || arg == "--desc") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_filenameScene = std::string(argv[++i]); + } + else + { + std::cerr << "Unknown option '" << arg << "'\n"; + printUsage(argv[0]); + return false; + } + } + + if (m_filenameSystem.empty()) + { + std::cerr << "ERROR: Options::parseCommandLine() System description filename is empty.\n"; + printUsage(argv[0]); + return false; + } + + if (m_filenameScene.empty()) + { + std::cerr << "ERROR: Options::parseCommandLine() Scene description filename is empty.\n"; + printUsage(argv[0]); + return false; + } + + return true; +} + +int Options::getWidth() const +{ + return m_width; +} + +int Options::getHeight() const +{ + return m_height; +} + +int Options::getMode() const +{ + return m_mode; +} + +bool Options::getOptimize() const +{ + return m_optimize; +} + +std::string Options::getSystem() const +{ + return m_filenameSystem; +} + +std::string Options::getScene() const +{ + return m_filenameScene; +} + + +void Options::printUsage(const std::string& argv0) +{ + std::cerr << "\nUsage: " << argv0 << " [options]\n"; + std::cerr << + "App Options:\n" + " ? | help | --help Print this usage message and exit.\n" + " -w | --width Width of the client window (512) \n" + " -h | --height Height of the client window (512)\n" + " -m | --mode 0 = interactive, 1 == benchmark (0)\n" + " -o | --optimize Optimize the assimp scene graph (false)\n" + " -s | --system Filename for system options (empty).\n" + " -d | --desc Filename for scene description (empty).\n" + "App Keystrokes:\n" + " SPACE Toggles GUI display.\n"; +} diff --git a/apps/MDL_sdf/src/Parser.cpp b/apps/MDL_sdf/src/Parser.cpp new file mode 100644 index 00000000..21e9d0b2 --- /dev/null +++ b/apps/MDL_sdf/src/Parser.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "inc/Parser.h" + +#include +#include +#include + + +Parser::Parser() +: m_index(0) +, m_line(1) +{ +} + +//Parser::~Parser() +//{ +//} + +bool Parser::load(const std::string& filename) +{ + m_source.clear(); + + std::ifstream inputStream(filename); + if (!inputStream) + { + std::cerr << "ERROR: Parser::load() failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: loadString() Failed to read file " << filename << '\n'; + return false; + } + + m_source = data.str(); + return true; +} + +ParserTokenType Parser::getNextToken(std::string& token) +{ + const static std::string whitespace = " \t"; // space, tab + const static std::string value = "+-0123456789.eE"; + const static std::string delimiter = " \t\r\n"; // space, tab, carriage return, linefeed + const static std::string newline = "\n"; + const static std::string quotation = "\""; + + token.clear(); // Make sure the returned token starts empty. + + ParserTokenType type = PTT_UNKNOWN; // This return value indicates an error. + + std::string::size_type first; + std::string::size_type last; + + bool done = false; + while (!done) + { + // Find first character which is not a whitespace. + first = m_source.find_first_not_of(whitespace, m_index); + if (first == std::string::npos) + { + token = std::string(); + type = PTT_EOF; + done = true; + continue; + } + + // The found character indicates how parsing continues. + char c = m_source[first]; + + if (c == '#') // comment until the next newline + { + // m_index = first + 1; // skip '#' // Redundant. + first = m_source.find_first_of(newline, m_index); // Skip everything until the next newline. + if (first == std::string::npos) + { + type = PTT_EOF; + done = true; + } + m_index = first + 1; // skip newline + m_line++; + } + else if (c == '\r') // carriage return 13 + { + m_index = first + 1; + } + else if (c == '\n') // newline (linefeed 10) + { + m_index = first + 1; + m_line++; + } + else if (c == '\"') // Quotation mark delimits strings (filenames or material names with spaces.) + { + ++first; // Skip beginning quotation mark. + last = m_source.find_first_of(quotation, first); // Find the ending quotation mark. Should be in the same line! + if (last == std::string::npos) // Error, no matching end quotation mark found. + { + m_index = first; // Keep scanning behind the quotation mark. + } + else + { + m_index = last + 1; // Skip the ending quotation mark. + token = m_source.substr(first, last - first); + type = PTT_STRING; + done = true; + } + } + else // anything else + { + last = m_source.find_first_of(delimiter, first); + if (last == std::string::npos) + { + last = m_source.size(); + } + m_index = last; + token = m_source.substr(first, last - first); + type = PTT_ID; // Default to general identifier. + // Check if token is only built of characters used for numbers. + // (Not perfectly parsing a floating point number but good enough for most filenames.) + if (isdigit(c) || c == '-' || c == '+' || c == '.') // Legal start characters for a floating point number. + { + last = token.find_first_not_of(value, 0); + if (last == std::string::npos) + { + type = PTT_VAL; + } + } + done = true; + } + } + + return type; +} + +std::string::size_type Parser::getSize() const +{ + return m_source.size(); +} + +std::string::size_type Parser::getIndex() const +{ + return m_index; +} + +unsigned int Parser::getLine() const +{ + return m_line; +} + + + diff --git a/apps/MDL_sdf/src/Picture.cpp b/apps/MDL_sdf/src/Picture.cpp new file mode 100644 index 00000000..7f692133 --- /dev/null +++ b/apps/MDL_sdf/src/Picture.cpp @@ -0,0 +1,1577 @@ +/* + * Copyright (c) 2013-2022, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// Code in these classes is based on the ILTexLoader.h/.cpp routines inside the NVIDIA nvpro-pipeline ILTexLoader plugin: +// https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp + + +#include "inc/Picture.h" + +#include "dp/math/math.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "inc/MyAssert.h" + + +static unsigned int numberOfComponents(int format) +{ + switch (format) + { + case IL_RGB: + case IL_BGR: + return 3; + + case IL_RGBA: + case IL_BGRA: + return 4; + + case IL_LUMINANCE: + case IL_ALPHA: + return 1; + + case IL_LUMINANCE_ALPHA: + return 2; + + default: + MY_ASSERT(!"Unsupported image data format."); + return 0; + } +} + +static unsigned int sizeOfComponents(int type) +{ + switch (type) + { + case IL_BYTE: + case IL_UNSIGNED_BYTE: + return 1; + + case IL_SHORT: + case IL_UNSIGNED_SHORT: + case IL_HALF: + return 2; + + case IL_INT: + case IL_UNSIGNED_INT: + case IL_FLOAT: + return 4; + + default: + MY_ASSERT(!"Unsupported image data type."); + return 0; + } +} + +Image::Image(unsigned int width, + unsigned int height, + unsigned int depth, + int format, + int type) +: m_width(width) +, m_height(height) +, m_depth(depth) +, m_format(format) +, m_type(type) +, m_pixels(nullptr) +{ + m_bpp = numberOfComponents(m_format) * sizeOfComponents(m_type); + m_bpl = m_width * m_bpp; + m_bps = m_height * m_bpl; + m_nob = m_depth * m_bps; +} + +Image::~Image() +{ + if (m_pixels != nullptr) + { + delete[] m_pixels; + m_pixels = nullptr; + } +} + +static int determineFace(int i, bool isCubemapDDS) +{ + int face = i; + + // If this is a cubemap in a DDS file, exchange the z-negative and z-positive images to match OpenGL and what's used here for OptiX. + if (isCubemapDDS) + { + if (i == 4) + { + face = 5; + } + else if (i == 5) + { + face = 4; + } + } + + return face; +} + + +Picture::Picture() +: m_flags(0) +, m_isCube(false) +{ +} + +Picture::~Picture() +{ + clearImages(); +} + +unsigned int Picture::getFlags() const +{ + return m_flags; +} + +void Picture::setFlags(const unsigned int flags) +{ + m_flags = flags; +} + +void Picture::addFlags(const unsigned int flags) +{ + m_flags |= flags; +} + +unsigned int Picture::getNumberOfImages() const +{ + return static_cast(m_images.size()); +} + +unsigned int Picture::getNumberOfLevels(unsigned int index) const +{ + MY_ASSERT(index < m_images.size()); + return static_cast(m_images[index].size()); +} + +const Image* Picture::getImageLevel(unsigned int index, unsigned int level) const +{ + if (index < m_images.size() && level < m_images[index].size()) + { + return m_images[index][level]; + } + return nullptr; +} + +bool Picture::isCubemap() const +{ + return m_isCube; +} + +void Picture::setIsCubemap(const bool isCube) +{ + m_isCube = isCube; +} + + +// Returns true if the input extents were already the smallest possible mipmap level. +static bool calculateNextExtents(unsigned int &w, unsigned int &h, unsigned int& d, const unsigned int flags) +{ + bool done = false; + + // Calculate the expected LOD image extents. + if (flags & IMAGE_FLAG_LAYER) + { + if (flags & IMAGE_FLAG_1D) + { + // 1D layered mipmapped. + done = (w == 1); + w = (1 < w) ? w >> 1 : 1; + // height is 1 + // depth is the number of layers and must not change. + } + else if (flags & (IMAGE_FLAG_2D | IMAGE_FLAG_CUBE)) + { + // 2D or cubemap layered mipmapped + done = (w == 1 && h == 1); + w = (1 < w) ? w >> 1 : 1; + h = (1 < h) ? h >> 1 : 1; + // depth is the number of layers (* 6 for cubemaps) and must not change. + } + } + else + { + // Standard mipmap chain. + done = (w == 1 && h == 1 && d == 1); + w = (1 < w) ? w >> 1 : 1; + h = (1 < h) ? h >> 1 : 1; + d = (1 < d) ? d >> 1 : 1; + } + return done; +} + + +void Picture::clearImages() +{ + for (size_t i = 0; i < m_images.size(); ++i) + { + for (size_t lod = 0; lod < m_images[i].size(); ++lod) + { + delete m_images[i][lod]; + m_images[i][lod] = nullptr; + } + m_images[i].clear(); + } + m_images.clear(); +} + + +bool Picture::load(const std::string& filename, const unsigned int flags) +{ + bool success = false; + + clearImages(); // Each load() wipes previously loaded image data. + + if (filename.empty()) + { + std::cerr << "ERROR: Picture::load() " << filename << " empty\n"; + MY_ASSERT(!"Picture::load() filename empty"); + return success; + } + + if (!std::filesystem::exists(filename)) + { + std::cerr << "ERROR: Picture::load() " << filename << " not found\n"; + MY_ASSERT(!"Picture::load() File not found"); + return success; + } + + m_flags = flags; // Track the flags with which this picture was loaded. + + std::string ext; + std::string::size_type last = filename.find_last_of('.'); + if (last != std::string::npos) + { + ext = filename.substr(last, std::string::npos); + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + } + + bool isDDS = (ext == std::string(".dds")); // .dds images need special handling + m_isCube = false; + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + ilBindImage(imageID); + + // Let DevIL handle the proper orientation during loading. + if (isDDS) + { + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_UPPER_LEFT); // DEBUG What happens when I set IL_ORIGIN_LOWER_LEFT all the time? + } + else + { + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_LOWER_LEFT); + } + + // Load the image from file. This loads all data. + if (ilLoadImage((const ILstring) filename.c_str())) + { + std::vector mipmaps; // All mipmaps excluding the LOD 0. + + ilBindImage(imageID); + ilActiveImage(0); // Get the frst image, potential LOD 0. + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // Get the size of the LOD 0 image. + unsigned int w = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int h = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int d = ilGetInteger(IL_IMAGE_DEPTH); + + // Querying for IL_NUM_IMAGES returns the number of images following the current one. Add 1 for the correct image count! + int numImages = ilGetInteger(IL_NUM_IMAGES) + 1; + + int numMipmaps = 0; // Default to no mipmap handling. + + if (flags & IMAGE_FLAG_MIPMAP) // Only handle mipmaps if we actually want to load them. + { + numMipmaps = ilGetInteger(IL_NUM_MIPMAPS); // Excluding the current image which becomes LOD 0. + + // Special check to see if the number of top-level images build a 1D, 2D, or 3D mipmap chain, if there are no mipmaps in the LOD 0 image. + if (1 < numImages && !numMipmaps) + { + bool isMipmapChain = true; // Indicates if the images in this file build a standard mimpmap chain. + + for (int i = 1; i < numImages; ++i) // Start check at LOD 1. + { + ilBindImage(imageID); + ilActiveImage(i); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // Next image extents. + const unsigned int ww = ilGetInteger(IL_IMAGE_WIDTH); + const unsigned int hh = ilGetInteger(IL_IMAGE_HEIGHT); + const unsigned int dd = ilGetInteger(IL_IMAGE_DEPTH); + + calculateNextExtents(w, h, d, flags); // Calculates the extents of the next mipmap level, taking layered textures into account! + + if (ww == w && hh == h && dd == d) // Criteria for next mipmap level match. + { + // Top-level image actually is the i-th mipmap level. Remember the data. + // This doesn't get overwritten below, because the standard mipmap handling is based on numMipmaps != 0. + mipmaps.push_back(ilGetData()); + } + else + { + // Could not identify top-level image as a mipmap level, no further testing required. + // Test failed, means the number of images do not build a mipmap chain. + isMipmapChain = false; + mipmaps.clear(); + break; + } + } + + if (isMipmapChain) + { + // Consider only the very first image in the file in the following code. + numImages = 1; + } + } + } + + m_isCube = (ilGetInteger(IL_IMAGE_CUBEFLAGS) != 0); + + // If the file isn't identified as cubemap already, + // check if there are six square images of the same extents in the file and handle them as cubemap. + if (!m_isCube && numImages == 6) + { + bool isCube = true; + + unsigned int w0 = 0; + unsigned int h0 = 0; + unsigned int d0 = 0; + + for (int image = 0; image < numImages && isCube; ++image) + { + ilBindImage(imageID); + ilActiveImage(image); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + if (image == 0) + { + w0 = ilGetInteger(IL_IMAGE_WIDTH); + h0 = ilGetInteger(IL_IMAGE_HEIGHT); + d0 = ilGetInteger(IL_IMAGE_DEPTH); + + MY_ASSERT(0 < d0); // This case of no image data is handled later. + + if (w0 != h0) + { + isCube = false; // Not square. + } + } + else + { + unsigned int w1 = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int h1 = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int d1 = ilGetInteger(IL_IMAGE_DEPTH); + + // All LOD 0 faces must be the same size. + if (w0 != w1 || h0 != h1) + { + isCube = false; + } + // If this should be interpreted as layered cubemap, all images must have the same number of layers. + if ((flags & IMAGE_FLAG_LAYER) && d0 != d1) + { + isCube = false; + } + } + } + m_isCube = isCube; + } + + for (int image = 0; image < numImages; ++image) + { + // Cubemap faces within DevIL philosophy are organized like this: + // image -> 1st face -> face index 0 + // face1 -> 2nd face -> face index 1 + // ... + // face5 -> 6th face -> face index 5 + + const int numFaces = ilGetInteger(IL_NUM_FACES) + 1; + + for (int f = 0; f < numFaces; ++f) + { + // Need to juggle with the faces to get them aligned with how OpenGL expects cube faces. Using the same layout in OptiX. + const int face = determineFace(f, m_isCube && isDDS); + + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // pixel format + int format = ilGetInteger(IL_IMAGE_FORMAT); + + if (IL_COLOR_INDEX == format) + { + // Convert color index to whatever the base type of the palette is. + if (!ilConvertImage(ilGetInteger(IL_PALETTE_BASE_TYPE), IL_UNSIGNED_BYTE)) + { + // Free all resources associated with the DevIL image. + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + return false; + } + // Now query format of the converted image. + format = ilGetInteger(IL_IMAGE_FORMAT); + } + + const int type = ilGetInteger(IL_IMAGE_TYPE); + + // Image dimension of the LOD 0 in pixels. + unsigned int width = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int height = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int depth = ilGetInteger(IL_IMAGE_DEPTH); + + if (width == 0 || height == 0 || depth == 0) // There must be at least a single pixel. + { + std::cerr << "ERROR Picture::load() " << filename << ": image " << image << " face " << f << " extents (" << width << ", " << height << ", " << depth << ")\n"; + MY_ASSERT(!"Picture::load() Image with zero extents."); + + // Free all resources associated with the DevIL image. + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + return false; + } + + // Get the remaining mipmaps for this image. + // Note that the special case handled above where multiple images built a mipmap chain + // will not enter this because that was only checked when numMipmaps == 0. + if (0 < numMipmaps) + { + mipmaps.clear(); // Clear this for currently processed image. + + for (int j = 1; j <= numMipmaps; ++j) // Mipmaps are starting at LOD 1. + { + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + ilActiveMipmap(j); + + // Not checking consistency of the individual LODs here. + mipmaps.push_back(ilGetData()); + } + + // Look at LOD 0 of this image again for the next ilGetData(). + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + ilActiveMipmap(0); + } + + // Add a a new vector of images with the whole mipmap chain. + // Mind that there will be six of these for a cubemap image! + unsigned int index = addImages(ilGetData(), width, height, depth, format, type, mipmaps, flags); + + if (m_isCube && isDDS) + { + // WARNING: + // This piece of code MUST NOT be visited twice for the same image, + // because this would falsify the desired effect! + // The images at this position are flipped at the x-axis (due to DevIL) + // flipping at x-axis will result in original image + // mirroring at y-axis will result in rotating the image 180 degree + if (face == 0 || face == 1 || face == 4 || face == 5) // px, nx, pz, nz + { + mirrorY(index); // mirror over y-axis + } + else // py, ny + { + mirrorX(index); // flip over x-axis + } + } + + ILint origin = ilGetInteger(IL_IMAGE_ORIGIN); + if (!m_isCube && origin == IL_ORIGIN_UPPER_LEFT) + { + // OpenGL expects origin at lower left, so the image has to be flipped at the x-axis + // for DDS cubemaps we handle the separate face rotations above + // DEBUG This should only happen for DDS images. + // All others are flipped by DevIL because I set the origin to lower left. Handle DDS images the same? + mirrorX(index); // reverse rows + } + } + } + success = true; + } + else + { + std::cerr << "ERROR: Picture::load() ilLoadImage(" << filename << " failed with error " << ilGetError() << '\n'; + MY_ASSERT(!"Picture::load() ilLoadImage failed"); + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + return success; +} + +void Picture::clear() +{ + m_images.clear(); +} + +// Append a new empty vector of images. Returns the new image index. +unsigned int Picture::addImages() +{ + const unsigned int index = static_cast(m_images.size()); + + m_images.push_back(std::vector()); // Append a new empty vector of image pointers. Each vector can hold one mipmap chain. + + return index; +} + +// Add a vector of images and fill it with the LOD 0 pixels and the optional mipmap chain. +unsigned int Picture::addImages(const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type, + const std::vector& mipmaps, const unsigned int flags) +{ + const unsigned int index = static_cast(m_images.size()); + + m_images.push_back(std::vector()); // Append a new empty vector of image pointers. + + Image* image = new Image(width, height, depth, format, type); // LOD 0 + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, pixels, image->m_nob); + + m_images[index].push_back(image); // LOD 0 + + unsigned int w = width; + unsigned int h = height; + unsigned int d = depth; + + for (size_t i = 0; i < mipmaps.size(); ++i) + { + MY_ASSERT(mipmaps[i]); // No nullptr expected. + + calculateNextExtents(w, h, d, flags); // Mind that the flags let this work for layered mipmap chains! + + image = new Image(w, h, d, format, type); // LOD 1 to N. + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, mipmaps[i], image->m_nob); + + m_images[index].push_back(image); // LOD 1 - N + } + + return index; +} + +// Append a new image LOD to the images in index. +unsigned int Picture::addLevel(const unsigned int index, const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type) +{ + MY_ASSERT(index < m_images.size()); + MY_ASSERT(pixels != nullptr); + MY_ASSERT((0 < width) && (0 < height) && (0 < depth)); + + Image* image = new Image(width, height, depth, format, type); + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, pixels, image->m_nob); + + const unsigned int level = static_cast(m_images[index].size()); + + m_images[index].push_back(image); + + return level; +} + + +void Picture::mirrorX(unsigned int index) +{ + MY_ASSERT(index < m_images.size()); + + // Flip all images upside down. + for (size_t i = 0; i < m_images[index].size(); ++i) + { + Image* image = m_images[index][i]; + + const unsigned char* srcPixels = image->m_pixels; + unsigned char* dstPixels = new unsigned char[image->m_nob]; + + for (unsigned int z = 0; z < image->m_depth; ++z) + { + for (unsigned int y = 0; y < image->m_height; ++y) + { + const unsigned char* srcLine = srcPixels + z * image->m_bps + y * image->m_bpl; + unsigned char* dstLine = dstPixels + z * image->m_bps + (image->m_height - 1 - y) * image->m_bpl; + + memcpy(dstLine, srcLine, image->m_bpl); + } + } + delete[] image->m_pixels; + image->m_pixels = dstPixels; + } +} + +void Picture::mirrorY(unsigned int index) +{ + MY_ASSERT(index < m_images.size()); + + // Mirror all images left to right. + for (size_t i = 0; i < m_images[index].size(); ++i) + { + Image* image = m_images[index][i]; + + const unsigned char* srcPixels = image->m_pixels; + unsigned char* dstPixels = new unsigned char[image->m_nob]; + + for (unsigned int z = 0; z < image->m_depth; ++z) + { + for (unsigned int y = 0; y < image->m_height; ++y) + { + const unsigned char* srcLine = srcPixels + z * image->m_bps + y * image->m_bpl; + unsigned char* dstLine = dstPixels + z * image->m_bps + y * image->m_bpl; + + for (unsigned int x = 0; x < image->m_width; ++x) + { + const unsigned char* srcPixel = srcLine + x * image->m_bpp; + unsigned char* dstPixel = dstLine + (image->m_width - 1 - x) * image->m_bpp; + + memcpy(dstPixel, srcPixel, image->m_bpp); + } + } + } + + delete[] image->m_pixels; + image->m_pixels = dstPixels; + } +} + + +void Picture::generateRGBA8(unsigned int width, unsigned int height, unsigned int depth, const unsigned int flags) +{ + clearImages(); + + const unsigned char colors[14][4] = + { + { 0xFF, 0x00, 0x00, 0xFF }, // red + { 0x00, 0xFF, 0x00, 0xFF }, // green + { 0x00, 0x00, 0xFF, 0xFF }, // blue + { 0xFF, 0xFF, 0x00, 0xFF }, // yellow + { 0x00, 0xFF, 0xFF, 0xFF }, // cyan + { 0xFF, 0x00, 0xFF, 0xFF }, // magenta + { 0xFF, 0xFF, 0xFF, 0xFF }, // white + + { 0x7F, 0x00, 0x00, 0xFF }, // dark red + { 0x00, 0x7F, 0x00, 0xFF }, // dark green + { 0x00, 0x00, 0x7F, 0xFF }, // dark blue + { 0x7F, 0x7F, 0x00, 0xFF }, // dark yellow + { 0x00, 0x7F, 0x7F, 0xFF }, // dark cyan + { 0x7F, 0x00, 0x7F, 0xFF }, // dark magenta + { 0x7F, 0x7F, 0x7F, 0xFF } // grey + }; + + m_flags = flags; + + m_isCube = ((flags & IMAGE_FLAG_CUBE) != 0); + + unsigned char* rgba = new unsigned char[width * height * depth * 4]; // Enough to hold the LOD 0. + + const unsigned int numFaces = (m_isCube) ? 6 : 1; // Cubemaps put each face in a new images vector. + + for (unsigned int face = 0; face < numFaces; ++face) + { + const unsigned int index = addImages(); // New mipmap chain. + MY_ASSERT(index == face); + + // calculateNextExtents() changes the w, h, d values. Restore them for each face. + unsigned int w = width; + unsigned int h = height; + unsigned int d = depth; + + bool done = false; // Indicates if one mipmap chain is done. + + unsigned int idx = face; // Color index. Each face gets a different color. + + while (!done) + { + unsigned char* p = rgba; + + for (unsigned int z = 0; z < d; ++z) + { + for (unsigned int y = 0; y < h; ++y) + { + for (unsigned int x = 0; x < w; ++x) + { + p[0] = colors[idx][0]; + p[1] = colors[idx][1]; + p[2] = colors[idx][2]; + p[3] = colors[idx][3]; + p += 4; + } + } + } + + idx = (idx + 1) % 14; // Next color index. Each mipmap level gets a different color. + + const unsigned int level = addLevel(index, rgba, w, h, d, IL_RGBA, IL_UNSIGNED_BYTE); + + if ((flags & IMAGE_FLAG_MIPMAP) == 0) + { + done = true; + } + else + { + done = calculateNextExtents(w, h, d, flags); + } + } + } + + delete[] rgba; +} + + + +bool Picture::loadSDF(const unsigned int width, + const unsigned int height, + const unsigned int depth, + const std::string& filename, + const unsigned int flags) +{ + clearImages(); + + // Make sure this is called only with 3D SDF data, no mipmaps, no layers. + MY_ASSERT((flags & (IMAGE_FLAG_3D | IMAGE_FLAG_SDF)) == (IMAGE_FLAG_3D | IMAGE_FLAG_SDF)); + + m_flags = flags; + + m_isCube = false; + + std::ifstream fileStream(filename, std::ios::binary); + + if (fileStream.fail()) + { + std::cerr << "ERROR: loadSDF() Failed to open file " << filename << '\n'; + return false; + } + + // Get the size of the file in bytes. + fileStream.seekg(0, fileStream.end); + std::streamsize size = fileStream.tellg(); + fileStream.seekg (0, fileStream.beg); + + if (size <= 0) + { + std::cerr << "ERROR: loadSDF() File size of " << filename << " is <= 0.\n"; + return false; + } + + // Read the SDF data into memory. + char *sdf = new char[size]; + + fileStream.read(sdf, size); + + if (fileStream.fail()) + { + std::cerr << "ERROR: loadSDF() Failed to read file " << filename << '\n'; + delete [] sdf; + return false; + } + + // Check the file extension and distinguish between float and half texture data. + int type = IL_FLOAT; // Assume everything is float data, except for *.bin files which are assumed to be half. + + std::filesystem::path path(filename); + if (path.extension() == std::string(".bin")) + { + type = IL_HALF; + } + + // SDF 3D textures are 1-component images. + // Interpret them as single-channel alpha image which will become a 1-component texture due to the host and destination encoding setup for this case. + // This works without support of half data inside the converter remapper functions because 1-component textures are hardware supported. + const unsigned int index = addImages(); // New mipmap chain. + const unsigned int level = addLevel(index, sdf, width, height, depth, IL_ALPHA, type); // LOD 0 image only. + + delete [] sdf; + + return true; +} + + +// Functions related to IES light profiles. + +// This is the "Type C point symmetry" case. +// Horizontal max angle == 0. +bool generateTypeC_PointSymmetry(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for 0 and 360 degrees. + if (ies.photometric.numHorizontalAngles != 1 || + ies.photometric.horizontalAngles[0] != 0.0f) + { + MY_ASSERT(!"generateTypeC_PointSymmetry failed.") + return false; + } + + // Generate data for horizontal angles 0 and 360 degrees. + horizontalAngles.resize(2); + candelas.resize(2 * ies.photometric.numVerticalAngles); + + horizontalAngles[0] = 0.0f; + horizontalAngles[1] = 360.0f; + + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + candelas[j] = ies.photometric.candela[j]; // copy 0 + candelas[ies.photometric.numVerticalAngles + j] = ies.photometric.candela[j]; // symmetry 360 + } + + return true; +} + +// This is the Type C bilateral symmetry along the 0-180 degree photometric plane. +// This algorithm generates a new array of horizontal angle to cover the full circle from 0-360 phi. +// Everything after this routine can be handled as no symmetry case. +bool generateTypeC_BilateralSymmetryX(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for 0 and 180 degrees. + if (ies.photometric.numHorizontalAngles < 2 || + ies.photometric.horizontalAngles[0] != 0.0f || + ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1] != 180.0f) + { + MY_ASSERT(!"generateTypeC_BilateralSymmetryX failed.") + return false; + } + + // Calculate the new number of horizontal angle entries after the bilateral symmetry operation. + // 360 degrees copies from 0 degrees. + // 180 degrees is not mirrored! + // Means there are at least three values for 0, 180, and 360. + // All values between 0 and 180 dregrees appear two times. + const int n = 3 + (ies.photometric.numHorizontalAngles - 2) * 2; + + horizontalAngles.resize(n); + candelas.resize(n * ies.photometric.numVerticalAngles); + + // Now calculate the new horizontal angles which build the full circle. + int i = 0; + int iSym = n - 1; + + while (i < ies.photometric.numHorizontalAngles) + { + const float phi = ies.photometric.horizontalAngles[i]; + + horizontalAngles[i ] = phi; + horizontalAngles[iSym] = 360.0f - phi; // 180 degrees entry is written twice, but that's ok. + + // Copy and mirror the candela value arrays per horizontal angle. + const int ii = i * ies.photometric.numVerticalAngles; + const int is = iSym * ies.photometric.numVerticalAngles; + + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + const float value = ies.photometric.candela[ii + j]; + + candelas[ii + j] = value; // copy + candelas[is + j] = value; // symmetry + } + + i++; + --iSym; + } + + return true; +} + + +// This is the "Type C bilateral symmetry" along the 90-270 degree photometric plane. +bool generateTypeC_BilateralSymmetryY(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for 0 and 180 degrees. + if (ies.photometric.numHorizontalAngles < 2 || + ies.photometric.horizontalAngles[0] != 90.0f || + ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1] != 270.0f) + { + MY_ASSERT(!"generateTypeC_BilateralSymmetryY failed.") + return false; + } + + // Use a deque to generate the proper symmetry. + // No need for complicated size calculations with special cases for 180 degrees or arbitrary horizontal angle distributions. + std::deque dequeAngles; + std::deque< std::vector > dequeCandelas; + + std::vector tempCandela; // Temporary copy of the vertical candela data array per horizontal angle. + tempCandela.resize(ies.photometric.numVerticalAngles); + + // First populate the deques with the original data + bool has180 = false; // Symmetry from 180 to 0 and 360 degrees requires interpolation if this stays false. + for (int i = 0; i < ies.photometric.numHorizontalAngles; ++i) + { + const float angle = ies.photometric.horizontalAngles[i]; + if (angle == 180.0f) + { + has180 = true; // Simple case: Indicates that there doesn't need to be an interpolation for the symmetry from 180 to 0 and 30 degrees. + } + dequeAngles.push_back(angle); + + const int idxSrc = i * ies.photometric.numVerticalAngles; + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + tempCandela[j] = ies.photometric.candela[idxSrc + j]; + } + dequeCandelas.push_back(tempCandela); + } + + // Now handle the symmetry along the 90-270 axis. + // Fill the first quadrant from 90 - 0 degrees. + // Going forward in the original data goes backward at the front of the deque, from 90 - 0 degrees. + for (int i = 1; i < ies.photometric.numHorizontalAngles - 1; ++i) // Exclude the 90 and 270 degrees entries. + { + const float angle = ies.photometric.horizontalAngles[i]; + if (angle <= 180.0f) // angle in range (90, 180] + { + dequeAngles.push_front(180.0f - angle); + + const int idxSrc = i * ies.photometric.numVerticalAngles; + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + tempCandela[j] = ies.photometric.candela[idxSrc + j]; + } + dequeCandelas.push_front(tempCandela); + } + else + { + break; // 180 < angle, no symmetry for that quadrant in this loop. + } + } + // Fill the fourth quadrant from 270 to 360 degrees. + // Going backwards in the original data goes forward at the back of the deque from 270 - 360. + for (int i = ies.photometric.numHorizontalAngles - 2; 0 < i; --i) // Exclude the 90 and 270 degrees entries. + { + const float angle = ies.photometric.horizontalAngles[i]; + if (180.0f <= angle) // angle in range [180, 270) + { + dequeAngles.push_back(270.0f + (270.0f - angle)); + + const int idxSrc = i * ies.photometric.numVerticalAngles; + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + tempCandela[j] = ies.photometric.candela[idxSrc + j]; + } + dequeCandelas.push_back(tempCandela); + } + else // angle < 180, handled already in the first loop. + { + break; + } + } + + // If there is no direct entry for 180 degrees, the 0 and 360 degrees do not exist, yet. + // The candela values for them need to be interpolated! + if (!has180) + { + // Find the indices around them and the interpolant to fill the 0 and 360 degrees candela arrays later. + int indexLo; + int indexHi; + float t_180 = 0.0f; + // Always entered because ies.photometric.numHorizontalAngles is at least 2. + for (int i = 0; i < ies.photometric.numHorizontalAngles - 1; ++i) + { + indexLo = i; + indexHi = i + 1; + + const float phiLo = ies.photometric.horizontalAngles[indexLo]; + const float phiHi = ies.photometric.horizontalAngles[indexHi]; + + if (phiLo <= 180.0f && 180.0f < phiHi) + { + t_180 = (180.0f - phiLo) / (phiHi - phiLo); + break; + } + } + + MY_ASSERT(indexLo != -1 && indexHi != -1); + + // There can't be entries for 0 and 360 degrees if no value for 180 degrees was in the list of horizontal angles. + MY_ASSERT(0.0f < dequeAngles.front()); + MY_ASSERT(dequeAngles.back() < 360.0f); + + // Calculate the interpolated vertical angles. + indexLo *= ies.photometric.numVerticalAngles; + indexHi *= ies.photometric.numVerticalAngles; + + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + const float candelaLo = ies.photometric.candela[indexLo + j]; + const float candelaHi = ies.photometric.candela[indexHi + j]; + + // The interpolated array of vertical candela values for the 180 degrees horizontal angle. + // This array is mirrored from 180 to both the 0 and 360 degrees entries. + tempCandela[j] = dp::math::lerp(t_180, candelaLo, candelaHi); + } + + dequeAngles.push_front(0.0f); + dequeCandelas.push_front(tempCandela); + + dequeAngles.push_back(360.0f); + dequeCandelas.push_back(tempCandela); + } + + // Copy the results back to the output data. + const size_t sizeAngles = dequeAngles.size(); + horizontalAngles.resize(sizeAngles); + candelas.resize(sizeAngles * ies.photometric.numVerticalAngles); + + size_t idx = 0; + for (std::deque::const_iterator it = dequeAngles.begin(); it != dequeAngles.end(); ++it) + { + horizontalAngles[idx++] = *it; + } + + idx = 0; + for (std::deque< std::vector >::const_iterator it = dequeCandelas.begin(); it != dequeCandelas.end(); ++it) + { + const size_t idxDst = idx++ * ies.photometric.numVerticalAngles; + for (size_t j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + candelas[idxDst + j] = (*it)[j]; + } + } + + return true; +} + +// This is the "Type C quadrant symmetry" case. +bool generateTypeC_QuadrantSymmetry(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for 0 and 90 degrees. + if (ies.photometric.numHorizontalAngles < 2 || + ies.photometric.horizontalAngles[0] != 0.0f || + ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1] != 90.0f) + { + MY_ASSERT(!"generateTypeC_QuadrantSymmetry failed.") + return false; + } + + // Calculate the new number of horizontal angle entries after the quadrant symmetry operation. + // 180 degrees copies from 0 degrees. + // 270 degrees copies from 90 degrees. + // 360 degrees copies from 0 degrees. + // Means there are at least five values for 0, 90, 180, 270 and 360. + // All inner values between 0 and 90 degrees are present four times. + const int n = 5 + (ies.photometric.numHorizontalAngles - 2) * 4; + + horizontalAngles.resize(n); + candelas.resize(n * ies.photometric.numVerticalAngles); + + // Now calculate the new horizontal angles which build the full circle. + // Indices are quadrants 1 to 4. + int i1 = 0; // Quadrant 1: 0 to 90, the original data. + int i2 = 2 * (ies.photometric.numHorizontalAngles - 1); // Quadrant 2: 180 to 90 + int i3 = i2; // Quadrant 3: 180 to 270 + int i4 = n - 1; // Quadrant 4: 360 to 270 + + while (i1 < ies.photometric.numHorizontalAngles) + { + const float phi = ies.photometric.horizontalAngles[i1]; + + horizontalAngles[i1] = phi; + horizontalAngles[i2] = 180.0f - phi; + horizontalAngles[i3] = 180.0f + phi; + horizontalAngles[i4] = 360.0f - phi; + + // Copy and mirror the candela value arrays per horizontal angle. + const int idx1 = i1 * ies.photometric.numVerticalAngles; + const int idx2 = i2 * ies.photometric.numVerticalAngles; + const int idx3 = i3 * ies.photometric.numVerticalAngles; + const int idx4 = i4 * ies.photometric.numVerticalAngles; + + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + const float value = ies.photometric.candela[idx1 + j]; + + candelas[idx1 + j] = value; + candelas[idx2 + j] = value; + candelas[idx3 + j] = value; + candelas[idx4 + j] = value; + } + + ++i1; + --i2; + ++i3; + --i4; + } + + return true; +} + +// This is the "Type C no symmetry" case. +// Horizontal min angle < 90 and horizontal max angle > 180, normally 0 to 360. +// FIXME Only supporting the 0 to 360 case here. +bool generateTypeC_NoSymmetry(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for 0 and 360 degrees. + if (ies.photometric.numHorizontalAngles < 2 || + ies.photometric.horizontalAngles[0] != 0.0f || + ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1] != 360.0f) + { + MY_ASSERT(!"generateTypeC_NoSymmetry failed.") + return false; + } + + // Just copying the data over to the output arrays. + horizontalAngles.resize(ies.photometric.numHorizontalAngles); + candelas.resize(ies.photometric.numHorizontalAngles * ies.photometric.numVerticalAngles); + + for (int i = 0; i < ies.photometric.numHorizontalAngles; ++i) + { + horizontalAngles[i] = ies.photometric.horizontalAngles[i]; + + const int idx = i * ies.photometric.numVerticalAngles; + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + candelas[idx + j] = ies.photometric.candela[idx + j]; // copy + } + } + return true; +} + +// This is the "Type A or B lateral symmetry" case. +// Horizontal min angle == 0 and horizontal max angle == 90. +bool generateTypeAB_BilateralSymmetry(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for 0 and 90 degrees. + if (ies.photometric.numHorizontalAngles < 2 || + ies.photometric.horizontalAngles[0] != 0.0f || + ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1] != 90.0f) + { + MY_ASSERT(!"generateTypeAB_BilateralSymmetry failed.") + return false; + } + + // Calculate the new number of horizontal angle entries after the bilateral symmetry operation. + // -90 degrees copies from 90 degrees. + // 0 degrees is not mirrored! + // Means there are at least three values for -90, 0, and 90. + // All values between 0 and 90 dregrees appear two times. + const int n = 3 + (ies.photometric.numHorizontalAngles - 2) * 2; + + horizontalAngles.resize(n); + candelas.resize(n * ies.photometric.numVerticalAngles); + + // Now calculate the new horizontal angles which build the hemisphere + int iCopy = ies.photometric.numHorizontalAngles - 1; // Start at the center with the 0 degrees entry. + int iSym = iCopy; // iSym runs backwards and gets the negative angles + + for (int i = 0; i < ies.photometric.numHorizontalAngles; ++i) + { + const float phi = ies.photometric.horizontalAngles[i]; + + horizontalAngles[iSym] = -phi; // Symmetry. + horizontalAngles[iCopy] = phi; // Copy. 0 degrees entry is written twice, but that's ok. (Keep the positive 0.) + + // Copy and mirror the candela value arrays per horizontal angle. + const int ii = i * ies.photometric.numVerticalAngles; + const int ic = iCopy * ies.photometric.numVerticalAngles; + const int is = iSym * ies.photometric.numVerticalAngles; + + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + const float value = ies.photometric.candela[ii + j]; + + candelas[is + j] = value; // Symmetry. + candelas[ic + j] = value; // Copy. + } + + ++iCopy; + --iSym; + } + + return true; +} + + +// This is the "Type A or B no symmetry" case. +// Horizontal min angle == -90 and horizontal max angle == 90. +bool generateTypeAB_NoSymmetry(IESData const& ies, + std::vector& horizontalAngles, + std::vector& candelas) +{ + // There need to be at least two values for -90 and 90 degrees. + if (ies.photometric.numHorizontalAngles < 2 || + ies.photometric.horizontalAngles[0] != -90.0f || + ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1] != 90.0f) + { + MY_ASSERT(!"generateTypeAB_NoSymmetry failed.") + return false; + } + + // Just copying the data over to the output arrays. + horizontalAngles.resize(ies.photometric.numHorizontalAngles); + candelas.resize(ies.photometric.numHorizontalAngles * ies.photometric.numVerticalAngles); + + for (int i = 0; i < ies.photometric.numHorizontalAngles; ++i) + { + horizontalAngles[i] = ies.photometric.horizontalAngles[i]; + + const int idx = i * ies.photometric.numVerticalAngles; + for (int j = 0; j < ies.photometric.numVerticalAngles; ++j) + { + candelas[idx + j] = ies.photometric.candela[idx + j]; // copy + } + } + return true; +} + +bool Picture::generateIES(const IESData& ies, + const std::vector& horizontalAngles, + const std::vector& candelas) +{ + // This is the replacement for ies.photometric.numHorizontalAngles after the symmetry operations have been applied. + const size_t sizeHorizontalAngles = horizontalAngles.size(); + + if (sizeHorizontalAngles < 2 || + ies.photometric.numVerticalAngles < 2 || + candelas.size() != sizeHorizontalAngles * ies.photometric.numVerticalAngles) + { + MY_ASSERT(!"generateIES() failed.") + return false; + } + + // Multiply the lamp multiplier, ballast factor and ballast-lamp photometric factor + float multiplier = (0.0f < ies.lamp.multiplier) ? ies.lamp.multiplier : 1.0f; + multiplier *= ies.electrical.ballastFactor * ies.electrical.ballastLampPhotometricFactor; + + std::vector pixels; // This receives the full spherical polar grid IES data. + +#if 0 + // FIXME Using the IES data as-is with linear texturing doesn't get the angles correct + // because texels are sampled in the center but the data is for the lower left corner. + // E.g. with horizontalAngularResolution == 45 the light would be rotated by 22.5 degrees by the texture sampling. + // This would also not place the spherical coordinate (0, 0) at the desired location. + // Manual interpolation inside the device code would need nearest filtering and knowledge about the goniometer type. + // Possible but that would require multiple IES light sampling functions. + // + // Be pragmatic and always expand the data to a fixed size spherical float texture, which also simplifies support + // for goniometer type A and B which both only define a hemispherical distribution around polar coordinate (0, 0). + // With the resolution 720x360 the horizontal deviation from the original input is only 0.5 degress at maximum. + // That's the #else clause. + + // First check if both horizontal and vertical angles have equidistant distributions of angles. + bool horizontalRegular = true; + const float horizontalAngularResolution = horizontalAngles[1] - horizontalAngles[0]; + for (int i = 1; horizontalRegular && i < sizeHorizontalAngles - 1; ++i) + { + if (0.01f < fabsf(horizontalAngularResolution - (horizontalAngles[i + 1] - horizontalAngles[i]))) + { + horizontalRegular = false; + } + } + + bool verticalRegular = true; + const float verticalAngularResolution = ies.photometric.verticalAngles[1] - ies.photometric.verticalAngles[0]; + for (int i = 1; verticalRegular && i < ies.photometric.numVerticalAngles - 1; ++i) + { + if (0.01f < fabsf(verticalAngularResolution - (ies.photometric.verticalAngles[i + 1] - ies.photometric.verticalAngles[i]))) + { + verticalRegular = false; + } + } + + // This is the case most often encountered with real measured data where the angular grid is in regular intervals. + if (horizontalRegular && verticalRegular) + { + if (ies.photometric.verticalAngles[0] == 0.0f && + ies.photometric.verticalAngles[ies.photometric.numVerticalAngles - 1] == 180.0f) + { + // Full definition with regular grid. Just copy the data. + width = (unsigned int) (sizeHorizontalAngles - 1); // phi == 360 is not uploaded! Texture repeats in phi direction. + height = ies.photometric.numVerticalAngles; // Poles are uploaded! + + pixels.resize(m_width * m_height); + + for (int y = 0; y < (int) m_height; ++y) + { + for (int x = 0; x < (int) m_width; ++x) + { + const float candela = candelas[x * ies.photometric.numVerticalAngles + y]; + m_texels[y * m_width + x] = candela * multiplier; + } + } + } + else if (ies.photometric.verticalAngles[0] == 0.0f && + ies.photometric.verticalAngles[ies.photometric.numVerticalAngles - 1] == 90.0f) + { + // Lower hemispher with regular regular grid. Increase the size and fill upper hemisphere with zero. + m_width = (unsigned int) (sizeHorizontalAngles - 1); // phi == 360 is not uploaded! Texture repeats in phi direction. + m_height = ies.photometric.numVerticalAngles * 2 - 1; // Poles are uploaded! -1 for the 90 degrees entry which is not duplicated. + m_depth = 1; + + m_texels.resize(m_width * m_height); + + for (int y = 0; y < (int) m_height; ++y) + { + for (int x = 0; x < (int) m_width; ++x) + { + float candela = 0.0f; + if (y < ies.photometric.numVerticalAngles) + { + candela = candelas[x * ies.photometric.numVerticalAngles + y]; + } + m_texels[y * m_width + x] = candela * multiplier; + } + } + } + else if (ies.photometric.verticalAngles[0] == 90.0f && + ies.photometric.verticalAngles[ies.photometric.numVerticalAngles - 1] == 180.0f) + { + // Upper hemisphere with regular regular grid. Increase the size and fill lower hemisphere with zero. + m_width = int(sizeHorizontalAngles - 1); // phi == 360 is not uploaded! Texture repeats in phi direction. + m_height = int(ies.photometric.numVerticalAngles * 2 - 1); // Poles are uploaded! -1 for the 90 degrees entry which is not duplicated. + m_depth = 1; + + m_texels.resize(m_width * m_height); + + for (int y = 0; y < (int) m_height; ++y) + { + for (int x = 0; x < (int) m_width; ++x) + { + float candela = 0.0f; + if (ies.photometric.numVerticalAngles - 1 <= y) + { + candela = candelas[x * ies.photometric.numVerticalAngles + y - (ies.photometric.numVerticalAngles - 1)]; + } + // I want the vertical angle 0 to be at lookup position v == 1.0f. Invert the y-index! + m_texels[y * m_width + x] = candela * multiplier; + } + } + } + } + else // The polar grid is not regular. + { +#endif + + // Use a hardcoded 0.5 degrees resolution (720x360 image) to generate the full spherical polar grid texture. + // This resolution covers most of the angular grids in IES files I've seen. + // This texture is about a megabyte in size. + // Note that since the textures are interpolated in the texel center and the polar grid is defined on the corners, + // the horizontal angle is 0.25 degrees off during rendering. + const unsigned int width = 720; + const unsigned int height = 361; // 360 + 1 to upload the poles as well. + + pixels.resize(width * height); + + // Scale value to get the actual phi and theta values per texel coordinate. + const float scaleX = 360.0f / float(width); + const float scaleY = 180.0f / float(height - 1); + + for (unsigned int y = 0; y < height; ++y) + { + float theta = float(y) * scaleY; // [0, 180] + + if (ies.photometric.goniometerType != TYPE_C) // Type A and B have vertical angles in the range [-90, 90]. + { + theta -= 90.0f; // [-90, 90] + } + + // Find the vertical angle interval which contains this theta. // PERF Make this incremental. + int iTheta = -1; + float tTheta = 0.0f; + for (int i = 0; i < ies.photometric.numVerticalAngles - 1; ++i) + { + const float thetaLo = ies.photometric.verticalAngles[i]; + const float thetaHi = ies.photometric.verticalAngles[i + 1]; + if (thetaLo <= theta && theta <= thetaHi && thetaLo != thetaHi) + { + iTheta = i; + tTheta = (theta - thetaLo) / (thetaHi - thetaLo); + break; + } + } + + for (unsigned int x = 0; x < width; ++x) + { + float candela = 0.0f; // Black when no interval in the source data contains this (phi, theta) coordinate. + + if (0 <= iTheta) // Only walk through the horizontal angles when there actually was data for this theta. + { + float phi = float(x) * scaleX; // [0, 359.5] + + if (ies.photometric.goniometerType == TYPE_C) + { + // According to documentation I found, the goniometer type C has the angle oriented counter-clockwise + // compared to the goniometer A and B types where the horizontal range [-90, 90] is clockwise. + // Well, at least that is mathematically positive, but that inconsistency is rather disturbing. + // Invert the angle and adjust the Type C goniometer to have the (0, 90) coordinate in the center of the image. + phi = 540.0f - phi; // == 360.0f - phi + 180.0f; + if (360.0f <= phi) + { + phi -= 360.0f; + } + } + else // Goniometer types A and B have horizontal angles only in the range [-90, 90]. The rest is black. + { + // Keep goniometer type A and B coordinate (0, 0) at the correct spherical location (center of the texture). + phi -= 180.0f; // [-180.0, 179.5] + } + + // Find the horizontal angle interval which contains this phi. + // PERF Make this incremental. There are generally very few horizontal angles though. + int iPhi = -1; + float tPhi = 0.0f; + for (int i = 0; i < int(sizeHorizontalAngles - 1); ++i) + { + const float phiLo = horizontalAngles[i]; + const float phiHi = horizontalAngles[i + 1]; + if (phiLo <= phi && phi <= phiHi && phiLo != phiHi) + { + iPhi = i; + tPhi = (phi - phiLo) / (phiHi - phiLo); + break; + } + } + + if (0 <= iPhi && 0 <= iTheta) + { + const int idxLL = iPhi * ies.photometric.numVerticalAngles + iTheta; + const int idxLR = (iPhi + 1) * ies.photometric.numVerticalAngles + iTheta; + const int idxUL = idxLL + 1; + const int idxUR = idxLR + 1; + + const float candelaLower = dp::math::lerp(tPhi, candelas[idxLL], candelas[idxLR]); + const float candelaUpper = dp::math::lerp(tPhi, candelas[idxUL], candelas[idxUR]); + + candela = dp::math::lerp(tTheta, candelaLower, candelaUpper) * multiplier; + } + } + + pixels[y * width + x] = candela; + } + } + + // Set the data in pixels as 1-component luminance float image. + clear(); + const unsigned int index = addImages(); + (void) addLevel(index, pixels.data(), width, height, 1, IL_LUMINANCE, IL_FLOAT); + + // Indicate that this Picture contains IES data. + // The Texture needs to be kept as 1-component float and use wrap clamp in vertical direction. + m_flags = IMAGE_FLAG_2D | IMAGE_FLAG_IES; + + return true; +} + +bool Picture::createIES(const IESData& ies) +{ + bool success = false; + + // Resulting vectors with new horizontal angles and candela entries after symmetry operations. + std::vector horizontalAngles; + std::vector candelas; + + MY_ASSERT(0 < ies.photometric.numHorizontalAngles); + + const float minHorizontalAngle = ies.photometric.horizontalAngles[0]; + const float maxHorizontalAngle = ies.photometric.horizontalAngles[ies.photometric.numHorizontalAngles - 1]; + + // Figure out symmetry. + switch (ies.photometric.goniometerType) + { + case TYPE_A: + case TYPE_B: + if (minHorizontalAngle == 0.0f && maxHorizontalAngle == 90.0f) + { + success = generateTypeAB_BilateralSymmetry(ies, horizontalAngles, candelas); + } + else + { + success = generateTypeAB_NoSymmetry(ies, horizontalAngles, candelas); + } + break; + + case TYPE_C: + if (maxHorizontalAngle <= 0.0f) + { + success = generateTypeC_PointSymmetry(ies, horizontalAngles, candelas); + } + else if (maxHorizontalAngle == 90.0f) + { + success = generateTypeC_QuadrantSymmetry(ies, horizontalAngles, candelas); + } + else if (maxHorizontalAngle == 180.0f) + { + success = generateTypeC_BilateralSymmetryX(ies, horizontalAngles, candelas); + } + else if (minHorizontalAngle == 90.0f && maxHorizontalAngle == 270.0f) + { + success = generateTypeC_BilateralSymmetryY(ies, horizontalAngles, candelas); + } + else + { + success = generateTypeC_NoSymmetry(ies, horizontalAngles, candelas); + } + break; + } + + // After any of the above symmetry functions return, the horizontalAngles define the full range from 0 to 360 degrees. + // Means a single function to convert this to a lookup texture. + if (success) + { + success = generateIES(ies, horizontalAngles, candelas); + } + + //m_goniometerType = ies.photometric.goniometerType; + + return success; +} diff --git a/apps/MDL_sdf/src/Plane.cpp b/apps/MDL_sdf/src/Plane.cpp new file mode 100644 index 00000000..0bfcb1f9 --- /dev/null +++ b/apps/MDL_sdf/src/Plane.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + void Triangles::createPlane(const unsigned int tessU, const unsigned int tessV, const unsigned int upAxis) + { + MY_ASSERT(1 <= tessU && 1 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + const float uTile = 2.0f / float(tessU); + const float vTile = 2.0f / float(tessV); + + float3 corner; + + TriangleAttributes attrib; + + switch (upAxis) + { + case 0: // Positive x-axis is the geometry normal, create geometry on the yz-plane. + corner = make_float3(0.0f, -1.0f, 1.0f); // Lower front corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(0.0f, 0.0f, -1.0f); + attrib.normal = make_float3(1.0f, 0.0f, 0.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(0.0f, v, -u); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + + case 1: // Positive y-axis is the geometry normal, create geometry on the xz-plane. + corner = make_float3(-1.0f, 0.0f, 1.0f); // left front corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 1.0f, 0.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(u, 0.0f, -v); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + + case 2: // Positive z-axis is the geometry normal, create geometry on the xy-plane. + corner = make_float3(-1.0f, -1.0f, 0.0f); // Lower left corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(u, v, 0.0f); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + } + + const unsigned int stride = tessU + 1; + for (unsigned int j = 0; j < tessV; ++j) + { + for (unsigned int i = 0; i < tessU; ++i) + { + m_indices.push_back( j * stride + i); + m_indices.push_back( j * stride + i + 1); + m_indices.push_back((j + 1) * stride + i + 1); + + m_indices.push_back((j + 1) * stride + i + 1); + m_indices.push_back((j + 1) * stride + i); + m_indices.push_back( j * stride + i); + } + } + } + +} // namespace sg \ No newline at end of file diff --git a/apps/MDL_sdf/src/Rasterizer.cpp b/apps/MDL_sdf/src/Rasterizer.cpp new file mode 100644 index 00000000..69c47a81 --- /dev/null +++ b/apps/MDL_sdf/src/Rasterizer.cpp @@ -0,0 +1,835 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "shaders/half_common.h" + +#include "inc/Rasterizer.h" +#include "inc/MyAssert.h" + +#include +#include +#include + + +#if USE_TIME_VIEW +static bool generateColorRamp(const std::vector& definition, int size, float *ramp) +{ + if (definition.size() < 1 || !size || !ramp) + { + return false; + } + + float r; + float g; + float b; + float a = 1.0f; // CUDA doesn't support float3 textures. + + float *p = ramp; + + if (definition.size() == 1) + { + // Special case, only one color in the input, means the whole color ramp is that color. + r = definition[0].c[0]; + g = definition[0].c[1]; + b = definition[0].c[2]; + + for (int i = 0; i < size; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } + return true; + } + + // Here definition.size() is at least 2. + ColorRampElement left; + ColorRampElement right; + size_t entry = 0; + + left = definition[entry]; + if (0.0f < left.u) + { + left.u = 0.0f; + } + else // left.u == 0.0f; + { + entry++; + } + right = definition[entry++]; + + for (int i = 0; i < size; ++i) + { + // The 1D coordinate at which we need to calculate the color. + float u = (float) i / (float) (size - 1); + + // Check if it's in the range [left.u, right.u) + while (!(left.u <= u && u < right.u)) + { + left = right; + if (entry < definition.size()) + { + right = definition[entry++]; + } + else + { + // left is already the last entry, move right.u to the end of the range. + right.u = 1.0001f; // Make sure we pass 1.0 < right.u in the last iteration. + break; + } + } + + float t = (u - left.u) / (right.u - left.u); + r = left.c[0] + t * (right.c[0] - left.c[0]); + g = left.c[1] + t * (right.c[1] - left.c[1]); + b = left.c[2] + t * (right.c[2] - left.c[2]); + + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } + return true; +} +#endif + + +Rasterizer::Rasterizer(const int w, const int h, const int interop) +: m_width(w) +, m_height(h) +, m_interop(interop) +, m_widthResolution(w) +, m_heightResolution(h) +, m_numDevices(0) +, m_nodeMask(0) +, m_hdrTexture(0) +, m_pbo(0) +, m_glslProgram(0) +, m_vboAttributes(0) +, m_vboIndices(0) +, m_locAttrPosition(-1) +, m_locAttrTexCoord(-1) +, m_locProjection(-1) +, m_locSamplerHDR(-1) +, m_colorRampTexture(0) +, m_locSamplerColorRamp(-1) +, m_locInvGamma(-1) +, m_locColorBalance(-1) +, m_locInvWhitePoint(-1) +, m_locBurnHighlights(-1) +, m_locCrushBlacks(-1) +, m_locSaturation(-1) +{ + for (int i = 0; i < 24; ++i) + { + memset(m_deviceUUID[i], 0, sizeof(m_deviceUUID[0])); + } + //memset(m_driverUUID, 0, sizeof(m_driverUUID)); // Unused. + memset(m_deviceLUID, 0, sizeof(m_deviceLUID)); + + // Find out which device is running the OpenGL implementation to be able to allocate the PBO peer-to-peer staging buffer on the same device. + // Needs these OpenGL extensions: + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects.txt + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects_win32.txt + // and on CUDA side the CUDA 10.0 Driver API function cuDeviceGetLuid(). + // While the extensions are named EXT_external_objects, the enums and functions are found under name string EXT_memory_object! + if (GLEW_EXT_memory_object) + { + // UUID + // To determine which devices are used by the current context, first call GetIntegerv with set to NUM_DEVICE_UUIDS_EXT, + // then call GetUnsignedBytei_vEXT with set to DEVICE_UUID_EXT, set to a value in the range [0, ), + // and set to point to an array of UUID_SIZE_EXT unsigned bytes. + glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &m_numDevices); // This is normally 1, but not when multicast is enabled! + MY_ASSERT(m_numDevices <= 24); // DEBUG m_deviceUUID is only prepared for 24 devices. + m_numDevices = std::min(m_numDevices, 24); + + for (GLint i = 0; i < m_numDevices; ++i) + { + glGetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, i, m_deviceUUID[i]); + } + //glGetUnsignedBytevEXT(GL_DRIVER_UUID_EXT, m_driverUUID); // Not used here. + + // LUID + // "The devices in use by the current context may also be identified by an (LUID, node) pair. + // To determine the LUID of the current context, call GetUnsignedBytev with set to DEVICE_LUID_EXT and set to point to an array of LUID_SIZE_EXT unsigned bytes. + // Following the call, can be cast to a pointer to an LUID object that will be equal to the locally unique identifier + // of an IDXGIAdapter1 object corresponding to the adapter used by the current context. + // To identify which individual devices within an adapter are used by the current context, call GetIntegerv with set to DEVICE_NODE_MASK_EXT. + // A bitfield is returned with one bit set for each device node used by the current context. + // The bits set will be subset of those available on a Direct3D 12 device created on an adapter with the same LUID as the current context." + if (GLEW_EXT_memory_object_win32) + { + // It is not expected that a single context will be associated with multiple DXGI adapters, so only one LUID is returned. + glGetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, m_deviceLUID); + glGetIntegerv(GL_DEVICE_NODE_MASK_EXT, &m_nodeMask); + } + } + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + glViewport(0, 0, m_width, m_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // default, works for BGRA8, RGBA16F, and RGBA32F. + + glDisable(GL_CULL_FACE); // default + glDisable(GL_DEPTH_TEST); // default + + glGenTextures(1, &m_hdrTexture); + MY_ASSERT(m_hdrTexture != 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + // For batch rendering initialize the texture contents to some default. +#if USE_FP32_OUTPUT + const float texel[4] = { 1.0f, 0.0f, 1.0f, 1.0f }; // Magenta to indicate that the texture has not been initialized. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, &texel); // RGBA32F +#else + const Half4 texel= make_Half4(1.0f, 0.0f, 1.0f, 1.0f); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, GL_HALF_FLOAT_ARB, &texel); // RGBA16F +#endif + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (GLEW_NV_gpu_multicast) + { + const char* envMulticast = getenv("GL_NV_GPU_MULTICAST"); + if (envMulticast != nullptr && envMulticast[0] != '0') + { + std::cerr << "WARNING: Rasterizer() GL_NV_GPU_MULTICAST is enabled. Primary device needs to be inside the devicesMask to display correctly.\n"; + glTexParameteri(GL_TEXTURE_2D, GL_PER_GPU_STORAGE_NV, GL_TRUE); + } + } + + glBindTexture(GL_TEXTURE_2D, 0); + + // The local ImGui sources have been changed to push the GL_TEXTURE_BIT so that this works. + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + if (m_interop == INTEROP_MODE_PBO) + { + // PBO for CUDA-OpenGL interop. + glGenBuffers(1, &m_pbo); + MY_ASSERT(m_pbo != 0); + + // Make sure the buffer is not zero size. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); +#if USE_FP32_OUTPUT + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(float) * 4, (GLvoid*) 0, GL_DYNAMIC_DRAW); // RGBA32F from byte offset 0 in the pixel unpack buffer. +#else + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(Half4), (GLvoid*) 0, GL_DYNAMIC_DRAW); // RGBA16F from byte offset 0 in the pixel unpack buffer. +#endif + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + // GLSL shaders objects and program. Must be initialized before using any shader variable locations. + initGLSL(); + + // This initialization is just to generate the vertex buffer objects and bind the VertexAttribPointers. + // Two hardcoded triangles in the viewport size projection coordinate system with 2D texture coordinates. + // These get updated to the correct values in reshape() and in setResolution(). The resolution is not known at this point. + const float attributes[16] = + { + // vertex2f, + 0.0f, 0.0f, + 1.0, 0.0f, + 1.0, 1.0, + 0.0f, 1.0, + //texcoord2f + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + const unsigned int indices[6] = + { + 0, 1, 2, + 2, 3, 0 + }; + + glGenBuffers(1, &m_vboAttributes); + MY_ASSERT(m_vboAttributes != 0); + + glGenBuffers(1, &m_vboIndices); + MY_ASSERT(m_vboIndices != 0); + + // Setup the vertex arrays from the vertex attributes. + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) sizeof(float) * 16, (GLvoid const*) attributes, GL_DYNAMIC_DRAW); + // This requires a bound array buffer! + glVertexAttribPointer(m_locAttrPosition, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (GLvoid*) 0); + glVertexAttribPointer(m_locAttrTexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (GLvoid*) (sizeof(float) * 8)); + glBindBuffer(GL_ARRAY_BUFFER, 0); // PERF It should be faster to keep these buffers bound. + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vboIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr) sizeof(unsigned int) * 6, (const GLvoid*) indices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // PERF It should be faster to keep these buffers bound. + + // Synchronize data with the current values. + updateProjectionMatrix(); + updateVertexAttributes(); + +#if USE_TIME_VIEW + // Generate the color ramp definition vector. + std::vector colorRampDefinition; + + ColorRampElement cre; + + // Cold to hot: blue, green, red, yellow, white + cre.u = 0.0f; + cre.c[0] = 0.0f; // blue + cre.c[1] = 0.0f; + cre.c[2] = 1.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.25f; + cre.c[0] = 0.0f; // green + cre.c[1] = 1.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.5f; + cre.c[0] = 1.0f; // red + cre.c[1] = 0.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.75f; + cre.c[0] = 1.0f; // yellow + cre.c[1] = 1.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 1.0f; + cre.c[0] = 1.0f; // white + cre.c[1] = 1.0f; + cre.c[2] = 1.0f; + colorRampDefinition.push_back(cre); + + std::vector texels(256 * 4); + + bool success = generateColorRamp(colorRampDefinition, 256, texels.data()); + if (success) + { + glGenTextures(1, &m_colorRampTexture); + MY_ASSERT(m_colorRampTexture != 0); + + glActiveTexture(GL_TEXTURE1); // It's set to texture image unit 1. + glBindTexture(GL_TEXTURE_1D, m_colorRampTexture); + + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, texels.data()); + + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glActiveTexture(GL_TEXTURE0); + } +#endif +} + +Rasterizer::~Rasterizer() +{ + glDeleteTextures(1, &m_hdrTexture); + +#if USE_TIME_VIEW + glDeleteTextures(1, &m_colorRampTexture); +#endif + + if (m_interop) + { + glDeleteBuffers(1, &m_pbo); + } + + glDeleteBuffers(1, &m_vboAttributes); + glDeleteBuffers(1, &m_vboIndices); + + glDeleteProgram(m_glslProgram); +} + + +void Rasterizer::reshape(const int w, const int h) +{ + // No check for zero sizes needed. That's done in Application::reshape() + if (m_width != w || m_height != h) + { + m_width = w; + m_height = h; + + glViewport(0, 0, m_width, m_height); + + updateProjectionMatrix(); + updateVertexAttributes(); + } +} + +void Rasterizer::display() +{ + glClear(GL_COLOR_BUFFER_BIT); // PERF Do not do this for benchmarks! + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vboIndices); + + glEnableVertexAttribArray(m_locAttrPosition); + glEnableVertexAttribArray(m_locAttrTexCoord); + + glUseProgram(m_glslProgram); + + glDrawElements(GL_TRIANGLES, (GLsizei) 6, GL_UNSIGNED_INT, (const GLvoid*) 0); + + glUseProgram(0); + + glDisableVertexAttribArray(m_locAttrPosition); + glDisableVertexAttribArray(m_locAttrTexCoord); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +const int Rasterizer::getNumDevices() const +{ + return m_numDevices; +} + +const unsigned char* Rasterizer::getUUID(const unsigned int index) const +{ + MY_ASSERT(index < 24); + return m_deviceUUID[index]; +} + +const unsigned char* Rasterizer::getLUID() const +{ + return m_deviceLUID; +} + +int Rasterizer::getNodeMask() const +{ + return m_nodeMask; +} + +unsigned int Rasterizer::getTextureObject() const +{ + return m_hdrTexture; +} + +unsigned int Rasterizer::getPixelBufferObject() const +{ + return m_pbo; +} + +void Rasterizer::setResolution(const int w, const int h) +{ + if (m_widthResolution != w || m_heightResolution != h) + { + m_widthResolution = (0 < w) ? w : 1; + m_heightResolution = (0 < h) ? h : 1; + + updateVertexAttributes(); + + // Cannot resize the PBO while it's registered with cuGraphicsGLRegisterBuffer(). Deferred to the Device::render() calls. + } +} + +void Rasterizer::setTonemapper(const TonemapperGUI& tm) +{ +#if !USE_TIME_VIEW + glUseProgram(m_glslProgram); + + glUniform1f(m_locInvGamma, 1.0f / tm.gamma); + glUniform3f(m_locColorBalance, tm.colorBalance[0], tm.colorBalance[1], tm.colorBalance[2]); + glUniform1f(m_locInvWhitePoint, tm.brightness / tm.whitePoint); + glUniform1f(m_locBurnHighlights, tm.burnHighlights); + glUniform1f(m_locCrushBlacks, tm.crushBlacks + tm.crushBlacks + 1.0f); + glUniform1f(m_locSaturation, tm.saturation); + + glUseProgram(0); +#endif +} + + +// Private functions: + +void Rasterizer::checkInfoLog(const char* /* msg */, GLuint object) +{ + GLint maxLength = 0; + + const GLboolean isShader = glIsShader(object); + + if (isShader) + { + glGetShaderiv(object, GL_INFO_LOG_LENGTH, &maxLength); + } + else + { + glGetProgramiv(object, GL_INFO_LOG_LENGTH, &maxLength); + } + + if (1 < maxLength) + { + GLchar *infoLog = new GLchar[maxLength]; + + if (infoLog != nullptr) + { + GLint length = 0; + + if (isShader) + { + glGetShaderInfoLog(object, maxLength, &length, infoLog); + } + else + { + glGetProgramInfoLog(object, maxLength, &length, infoLog); + } + + //fprintf(fileLog, "-- tried to compile (len=%d): %s\n", (unsigned int)strlen(msg), msg); + //fprintf(fileLog, "--- info log contents (len=%d) ---\n", (int) maxLength); + //fprintf(fileLog, "%s", infoLog); + //fprintf(fileLog, "--- end ---\n"); + std::cout << infoLog << '\n'; + // Look at the info log string here... + + delete [] infoLog; + } + } +} + +void Rasterizer::initGLSL() +{ + static const std::string vsSource = + "#version 330\n" + "layout(location = 0) in vec2 attrPosition;\n" + "layout(location = 1) in vec2 attrTexCoord;\n" + "uniform mat4 projection;\n" + "out vec2 varTexCoord;\n" + "void main()\n" + "{\n" + " gl_Position = projection * vec4(attrPosition, 0.0, 1.0);\n" + " varTexCoord = attrTexCoord;\n" + "}\n"; + + +#if USE_TIME_VIEW + static const std::string fsSource = + "#version 330\n" + "uniform sampler2D samplerHDR;\n" + "uniform sampler1D samplerColorRamp;\n" + "in vec2 varTexCoord;\n" + "layout(location = 0, index = 0) out vec4 outColor;\n" + "void main()\n" + "{\n" + " float alpha = texture(samplerHDR, varTexCoord).a;\n" + " outColor = texture(samplerColorRamp, alpha);\n" + "}\n"; +#else + static const std::string fsSource = + "#version 330\n" + "uniform sampler2D samplerHDR;\n" + "uniform vec3 colorBalance;\n" + "uniform float invWhitePoint;\n" + "uniform float burnHighlights;\n" + "uniform float saturation;\n" + "uniform float crushBlacks;\n" + "uniform float invGamma;\n" + "in vec2 varTexCoord;\n" + "layout(location = 0, index = 0) out vec4 outColor;\n" + "void main()\n" + "{\n" + " vec3 hdrColor = texture(samplerHDR, varTexCoord).rgb;\n" + " vec3 ldrColor = invWhitePoint * colorBalance * hdrColor;\n" + " ldrColor *= (ldrColor * burnHighlights + 1.0) / (ldrColor + 1.0);\n" + " float luminance = dot(ldrColor, vec3(0.3, 0.59, 0.11));\n" + " ldrColor = max(mix(vec3(luminance), ldrColor, saturation), 0.0);\n" + " luminance = dot(ldrColor, vec3(0.3, 0.59, 0.11));\n" + " if (luminance < 1.0)\n" + " {\n" + " ldrColor = max(mix(pow(ldrColor, vec3(crushBlacks)), ldrColor, sqrt(luminance)), 0.0);\n" + " }\n" + " ldrColor = pow(ldrColor, vec3(invGamma));\n" + " outColor = vec4(ldrColor, 1.0);\n" + "}\n"; +#endif + + GLint vsCompiled = 0; + GLint fsCompiled = 0; + + GLuint glslVS = glCreateShader(GL_VERTEX_SHADER); + if (glslVS) + { + GLsizei len = (GLsizei) vsSource.size(); + const GLchar *vs = vsSource.c_str(); + glShaderSource(glslVS, 1, &vs, &len); + glCompileShader(glslVS); + checkInfoLog(vs, glslVS); + + glGetShaderiv(glslVS, GL_COMPILE_STATUS, &vsCompiled); + MY_ASSERT(vsCompiled); + } + + GLuint glslFS = glCreateShader(GL_FRAGMENT_SHADER); + if (glslFS) + { + GLsizei len = (GLsizei) fsSource.size(); + const GLchar *fs = fsSource.c_str(); + glShaderSource(glslFS, 1, &fs, &len); + glCompileShader(glslFS); + checkInfoLog(fs, glslFS); + + glGetShaderiv(glslFS, GL_COMPILE_STATUS, &fsCompiled); + MY_ASSERT(fsCompiled); + } + + m_glslProgram = glCreateProgram(); + if (m_glslProgram) + { + GLint programLinked = 0; + + if (glslVS && vsCompiled) + { + glAttachShader(m_glslProgram, glslVS); + } + if (glslFS && fsCompiled) + { + glAttachShader(m_glslProgram, glslFS); + } + + glLinkProgram(m_glslProgram); + checkInfoLog("m_glslProgram", m_glslProgram); + + glGetProgramiv(m_glslProgram, GL_LINK_STATUS, &programLinked); + MY_ASSERT(programLinked); + + if (programLinked) + { + glUseProgram(m_glslProgram); + + // FIXME Put these into a struct. + m_locAttrPosition = glGetAttribLocation(m_glslProgram, "attrPosition"); + m_locAttrTexCoord = glGetAttribLocation(m_glslProgram, "attrTexCoord"); + m_locProjection = glGetUniformLocation(m_glslProgram, "projection"); + + MY_ASSERT(m_locAttrPosition != -1); + MY_ASSERT(m_locAttrTexCoord != -1); + MY_ASSERT(m_locProjection != -1); + + m_locSamplerHDR = glGetUniformLocation(m_glslProgram, "samplerHDR"); + MY_ASSERT(m_locSamplerHDR != -1); + glUniform1i(m_locSamplerHDR, 0); // The rasterizer uses texture image unit 0 to display the HDR image. + +#if USE_TIME_VIEW + m_locSamplerColorRamp = glGetUniformLocation(m_glslProgram, "samplerColorRamp"); + MY_ASSERT(m_locSamplerColorRamp != -1); + glUniform1i(m_locSamplerColorRamp, 1); // The rasterizer uses texture image unit 1 for the color ramp when USE_TIME_VIEW is enabled. +#else + m_locInvGamma = glGetUniformLocation(m_glslProgram, "invGamma"); + m_locColorBalance = glGetUniformLocation(m_glslProgram, "colorBalance"); + m_locInvWhitePoint = glGetUniformLocation(m_glslProgram, "invWhitePoint"); + m_locBurnHighlights = glGetUniformLocation(m_glslProgram, "burnHighlights"); + m_locCrushBlacks = glGetUniformLocation(m_glslProgram, "crushBlacks"); + m_locSaturation = glGetUniformLocation(m_glslProgram, "saturation"); + + MY_ASSERT(m_locInvGamma != -1); + MY_ASSERT(m_locColorBalance != -1); + MY_ASSERT(m_locInvWhitePoint != -1); + MY_ASSERT(m_locBurnHighlights != -1); + MY_ASSERT(m_locCrushBlacks != -1); + MY_ASSERT(m_locSaturation != -1); + + // Set neutral Tonemapper defaults. This will show the linear HDR image. + glUniform1f(m_locInvGamma, 1.0f); + glUniform3f(m_locColorBalance, 1.0f, 1.0f, 1.0f); + glUniform1f(m_locInvWhitePoint, 1.0f); + glUniform1f(m_locBurnHighlights, 1.0f); + glUniform1f(m_locCrushBlacks, 1.0f); + glUniform1f(m_locSaturation, 1.0f); +#endif + + glUseProgram(0); + } + } + + if (glslVS) + { + glDeleteShader(glslVS); + } + if (glslFS) + { + glDeleteShader(glslFS); + } +} + + +void Rasterizer::updateProjectionMatrix() +{ + // No need to set this when using shaders only. + //glMatrixMode(GL_PROJECTION); + //glLoadIdentity(); + //glOrtho(0.0, GLdouble(m_width), 0.0, GLdouble(m_height), -1.0, 1.0); + + //glMatrixMode(GL_MODELVIEW); + + // Full projection matrix calculation: + //const float l = 0.0f; + const float r = float(m_width); + //const float b = 0.0f; + const float t = float(m_height); + //const float n = -1.0f; + //const float f = 1.0; + + //const float m00 = 2.0f / (r - l); // == 2.0f / r with l == 0.0f + //const float m11 = 2.0f / (t - b); // == 2.0f / t with b == 0.0f + //const float m22 = -2.0f / (f - n); // Always -1.0f with f == 1.0f and n == -1.0f + //const float tx = -(r + l) / (r - l); // Always -1.0f with l == 0.0f + //const float ty = -(t + b) / (t - b); // Always -1.0f with b == 0.0f + //const float tz = -(f + n) / (f - n); // Always 0.0f with f = -n + + // Row-major layout, needs transpose in glUniformMatrix4fv. + //const float projection[16] = + //{ + // m00, 0.0f, 0.0f, tx, + // 0.0f, m11, 0.0f, ty, + // 0.0f, 0.0f, m22, tz, + // 0.0f, 0.0f, 0.0f, 1.0f + //}; + + // Optimized version and colum-major layout: + const float projection[16] = + { + 2.0f / r, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / t, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 1.0f + }; + + glUseProgram(m_glslProgram); + glUniformMatrix4fv(m_locProjection, 1, GL_FALSE, projection); // Column-major memory layout, no transpose. + glUseProgram(0); +} + + +void Rasterizer::updateVertexAttributes() +{ + // This routine calculates the vertex attributes for the diplay routine. + // It calculates screen space vertex coordinates to display the full rendered image + // in the correct aspect ratio independently of the window client size. + // The image gets scaled down when it's bigger than the client window. + + // The final screen space vertex coordinates for the texture blit. + float x0; + float y0; + float x1; + float y1; + + // This routine picks the required filtering mode for this texture. + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + if (m_widthResolution <= m_width && m_heightResolution <= m_height) + { + // Texture fits into viewport without scaling. + // Calculate the amount of cleared border pixels. + int w1 = m_width - m_widthResolution; + int h1 = m_height - m_heightResolution; + // Halve the border size to get the lower left offset + int w0 = w1 >> 1; + int h0 = h1 >> 1; + // Subtract from the full border to get the right top offset. + w1 -= w0; + h1 -= h0; + // Calculate the texture blit screen space coordinates. + x0 = float(w0); + y0 = float(h0); + x1 = float(m_width - w1); + y1 = float(m_height - h1); + + // Fill the background with black to indicate that all pixels are visible without scaling. + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + // Use nearest filtering to display the pixels exactly. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else // Case + { + // Texture needs to be scaled down to fit into client window. + // Check which extent defines the necessary scaling factor. + const float wC = float(m_width); + const float hC = float(m_height); + const float wR = float(m_widthResolution); + const float hR = float(m_heightResolution); + + const float scale = std::min(wC / wR, hC / hR); + + const float swR = scale * wR; + const float shR = scale * hR; + + x0 = 0.5f * (wC - swR); + y0 = 0.5f * (hC - shR); + x1 = x0 + swR; + y1 = y0 + shR; + + // Render surrounding pixels in dark red to indicate that the image is scaled down. + glClearColor(0.2f, 0.0f, 0.0f, 0.0f); + + // Use linear filtering to smooth the downscaling. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + // Update the vertex attributes with the new texture blit screen space coordinates. + const float attributes[16] = + { + // vertex2f + x0, y0, + x1, y0, + x1, y1, + x0, y1, + // texcoord2f + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) sizeof(float) * 16, (GLvoid const*) attributes, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); // PERF It should be faster to keep them bound. +} diff --git a/apps/MDL_sdf/src/Raytracer.cpp b/apps/MDL_sdf/src/Raytracer.cpp new file mode 100644 index 00000000..89b80327 --- /dev/null +++ b/apps/MDL_sdf/src/Raytracer.cpp @@ -0,0 +1,2760 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Raytracer.h" +#include "inc/CompileResult.h" + +#include "inc/CheckMacros.h" + +#include "shaders/config.h" + +#include +#include +#include +#include +#include + + +bool static saveString(const std::string& filename, const std::string& text) +{ + std::ofstream outputStream(filename); + + if (!outputStream) + { + std::cerr << "ERROR: saveString() Failed to open file " << filename << '\n'; + return false; + } + + outputStream << text; + + if (outputStream.fail()) + { + std::cerr << "ERROR: saveString() Failed to write file " << filename << '\n'; + return false; + } + + return true; +} + +static std::string getDateTime() +{ +#if defined(_WIN32) + SYSTEMTIME time; + GetLocalTime(&time); +#elif defined(__linux__) + time_t rawtime; + struct tm* ts; + time(&rawtime); + ts = localtime(&rawtime); +#else + #error "OS not supported." +#endif + + std::ostringstream oss; + +#if defined( _WIN32 ) + oss << time.wYear; + if (time.wMonth < 10) + { + oss << '0'; + } + oss << time.wMonth; + if (time.wDay < 10) + { + oss << '0'; + } + oss << time.wDay << '_'; + if (time.wHour < 10) + { + oss << '0'; + } + oss << time.wHour; + if (time.wMinute < 10) + { + oss << '0'; + } + oss << time.wMinute; + if (time.wSecond < 10) + { + oss << '0'; + } + oss << time.wSecond << '_'; + if (time.wMilliseconds < 100) + { + oss << '0'; + } + if (time.wMilliseconds < 10) + { + oss << '0'; + } + oss << time.wMilliseconds; +#elif defined(__linux__) + oss << ts->tm_year; + if (ts->tm_mon < 10) + { + oss << '0'; + } + oss << ts->tm_mon; + if (ts->tm_mday < 10) + { + oss << '0'; + } + oss << ts->tm_mday << '_'; + if (ts->tm_hour < 10) + { + oss << '0'; + } + oss << ts->tm_hour; + if (ts->tm_min < 10) + { + oss << '0'; + } + oss << ts->tm_min; + if (ts->tm_sec < 10) + { + oss << '0'; + } + oss << ts->tm_sec << '_'; + oss << "000"; // No milliseconds available. +#else + #error "OS not supported." +#endif + + return oss.str(); +} + + + + + +Raytracer::Raytracer(const int maskDevices, + const TypeLight typeEnv, + const int interop, + const unsigned int tex, + const unsigned int pbo, + const size_t sizeArena, + const int p2p) +: m_maskDevices(maskDevices) +, m_typeEnv(typeEnv) +, m_interop(interop) +, m_tex(tex) +, m_pbo(pbo) +, m_sizeArena(sizeArena) +, m_peerToPeer(p2p) +, m_isValid(false) +, m_numDevicesVisible(0) +, m_indexDeviceOGL(-1) +, m_maskDevicesActive(0) +, m_iterationIndex(0) +, m_samplesPerPixel(1) +{ + CU_CHECK( cuInit(0) ); // Initialize CUDA driver API. + + int versionDriver = 0; + CU_CHECK( cuDriverGetVersion(&versionDriver) ); + + // The version is returned as (1000 * major + 10 * minor). + int major = versionDriver / 1000; + int minor = (versionDriver - 1000 * major) / 10; + std::cout << "CUDA Driver Version = " << major << "." << minor << '\n'; + + CU_CHECK( cuDeviceGetCount(&m_numDevicesVisible) ); + std::cout << "CUDA Device Count = " << m_numDevicesVisible << '\n'; + + // Match user defined m_maskDevices with the number of visible devices. + // Builds m_maskActiveDevices and fills m_devicesActive which defines the device count. + selectDevices(); + + // This Raytracer is all about sharing data in peer-to-peer islands on multi-GPU setups. + // While that can be individually enabled for texture array and/or GAS and vertex attribute data sharing, + // the compositing of the final image is also done with peer-to-peer copies. + (void) enablePeerAccess(); + + m_isValid = !m_devicesActive.empty(); +} + + +Raytracer::~Raytracer() +{ + try + { + // This function contains throw() calls. + disablePeerAccess(); // Just for cleanliness, the Devices are destroyed anyway after this. + + // The GeometryData is either created on each device or only on one device of an NVLINK island. + // In any case GeometryData is unique and must only be destroyed by the device owning the data. + for (auto& data : m_geometryData) + { + m_devicesActive[data.owner]->destroyGeometry(data); + } + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + delete m_devicesActive[i]; + } + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + +} + +int Raytracer::matchUUID(const char* uuid) +{ + const int size = static_cast(m_devicesActive.size()); + + for (int i = 0; i < size; ++i) + { + if (m_devicesActive[i]->matchUUID(uuid)) + { + // Use the first device which matches with the OpenGL UUID. + // DEBUG This might not be the right thing to do with multicast enabled. + m_indexDeviceOGL = i; + break; + } + } + + std::cout << "OpenGL on active device index " << m_indexDeviceOGL << '\n'; // DEBUG + + return m_indexDeviceOGL; // If this stays -1, the active devices do not contain the one running the OpenGL implementation. +} + +int Raytracer::matchLUID(const char* luid, const unsigned int nodeMask) +{ + const int size = static_cast(m_devicesActive.size()); + + for (int i = 0; i < size; ++i) + { + if (m_devicesActive[i]->matchLUID(luid, nodeMask)) + { + // Use the first device which matches with the OpenGL LUID and test of the node mask bit. + // DEBUG This might not be the right thing to do with multicast enabled. + m_indexDeviceOGL = i; + break; + } + } + + std::cout << "OpenGL on active device index " << m_indexDeviceOGL << '\n'; // DEBUG + + return m_indexDeviceOGL; // If this stays -1, the active devices do not contain the one running the OpenGL implementation. +} + + +int Raytracer::findActiveDevice(const unsigned int domain, const unsigned int bus, const unsigned int device) const +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + const DeviceAttribute& attribute = m_devicesActive[i]->m_deviceAttribute; + + if (attribute.pciDomainId == domain && + attribute.pciBusId == bus && + attribute.pciDeviceId == device) + { + return static_cast(i); + } + } + + return -1; +} + + +bool Raytracer::activeNVLINK(const int home, const int peer) const +{ + // All NVML calls related to NVLINK are only supported by Pascal (SM 6.0) and newer. + if (m_devicesActive[home]->m_deviceAttribute.computeCapabilityMajor < 6) + { + return false; + } + + nvmlDevice_t deviceHome; + + if (m_nvml.m_api.nvmlDeviceGetHandleByPciBusId(m_devicesActive[home]->m_devicePciBusId.c_str(), &deviceHome) != NVML_SUCCESS) + { + return false; + } + + // The NVML deviceHome is part of the active devices at index "home". + for (unsigned int link = 0; link < NVML_NVLINK_MAX_LINKS; ++link) + { + // First check if this link is supported at all and if it's active. + nvmlEnableState_t enableState = NVML_FEATURE_DISABLED; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkState(deviceHome, link, &enableState) != NVML_SUCCESS) + { + continue; + } + if (enableState != NVML_FEATURE_ENABLED) + { + continue; + } + + // Is peer-to-peer over NVLINK supported by this link? + // The requirement for peer-to-peer over NVLINK under Windows is Windows 10 (WDDM2), 64-bit, SLI enabled. + unsigned int capP2P = 0; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkCapability(deviceHome, link, NVML_NVLINK_CAP_P2P_SUPPORTED, &capP2P) != NVML_SUCCESS) + { + continue; + } + if (capP2P == 0) + { + continue; + } + + nvmlPciInfo_t pciInfoPeer; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkRemotePciInfo(deviceHome, link, &pciInfoPeer) != NVML_SUCCESS) + { + continue; + } + + // Check if the NVML remote device matches the desired peer devcice. + if (peer == findActiveDevice(pciInfoPeer.domain, pciInfoPeer.bus, pciInfoPeer.device)) + { + return true; + } + } + + return false; +} + + +bool Raytracer::enablePeerAccess() +{ + bool success = true; + + // Build a peer-to-peer connection matrix which only allows peer-to-peer access over NVLINK bridges. + const int size = static_cast(m_devicesActive.size()); + MY_ASSERT(size <= 32); + + // Peer-to-peer access is encoded in a bitfield of uint32 entries. + // Indexed by [home] device and peer devices are the bit indices, accessed with (1 << peer) masks. + m_peerConnections.resize(size); + + // Initialize the connection matrix diagonal with the trivial case (home == peer). + // This let's building the islands still work if there are any exceptions. + for (int home = 0; home < size; ++home) + { + m_peerConnections[home] = (1 << home); + } + + // Check if the system configuration option "peerToPeer" allowed peer-to-peer via PCI-E irrespective of the NVLINK topology. + // In that case the activeNVLINK() function is not called below. + // PERF In that case NVML wouldn't be needed at all. + const bool allowPCI = ((m_peerToPeer & P2P_PCI) != 0); + + // The NVML_CHECK and CU_CHECK macros can throw exceptions. + // Keep them local in this routine because not having NVLINK islands with peer-to-peer access + // is not a fatal condition for the renderer. It just won't be able to share resources. + try + { + if (m_nvml.initFunctionTable()) + { + NVML_CHECK( m_nvml.m_api.nvmlInit() ); + + for (int home = 0; home < size; ++home) // Home device index. + { + for (int peer = 0; peer < size; ++peer) // Peer device index. + { + if (home != peer && (allowPCI || activeNVLINK(home, peer))) + { + int canAccessPeer = 0; + + // This requires the ordinals of the visible CUDA devices! + CU_CHECK( cuDeviceCanAccessPeer(&canAccessPeer, + m_devicesActive[home]->m_cudaDevice, // If this current home device + m_devicesActive[peer]->m_cudaDevice) ); // can access the peer device's memory. + if (canAccessPeer != 0) + { + // Note that this function changes the current context! + CU_CHECK( cuCtxSetCurrent(m_devicesActive[home]->m_cudaContext) ); + + CUresult result = cuCtxEnablePeerAccess(m_devicesActive[peer]->m_cudaContext, 0); // Flags must be 0! + if (result == CUDA_SUCCESS) + { + m_peerConnections[home] |= (1 << peer); // Set the connection bit if the enable succeeded. + } + else + { + // Print the ordinal here to be consistent with the other output about used devices. + std::cerr << "WARNING: cuCtxEnablePeerAccess() between device ordinals (" + << m_devicesActive[home]->m_ordinal << ", " + << m_devicesActive[peer]->m_ordinal << ") failed with CUresult " << result << '\n'; + } + } + } + } + } + + NVML_CHECK( m_nvml.m_api.nvmlShutdown() ); + } + } + catch (const std::exception& e) + { + // FIXME Reaching this from CU_CHECK macros above means nvmlShutdown() hasn't been called. + std::cerr << e.what() << '\n'; + // No return here. Always build the m_islands from the existing connection matrix information. + success = false; + } + + // Now use the peer-to-peer connection matrix to build peer-to-peer islands. + // First fill a vector with all device indices which have not been assigned to an island. + std::vector unassigned(size); + + for (int i = 0; i < size; ++i) + { + unassigned[i] = i; + } + + while (!unassigned.empty()) + { + std::vector island; + std::vector::const_iterator it = unassigned.begin(); + + island.push_back(*it); + unassigned.erase(it); // This device has been assigned to an island. + + it = unassigned.begin(); // The next unassigned device. + while (it != unassigned.end()) + { + bool isAccessible = true; + + const int peer = *it; + + // Check if this peer device is accessible by all other devices in the island. + for (size_t i = 0; i < island.size(); ++i) + { + const int home = island[i]; + + if ((m_peerConnections[home] & (1 << peer)) == 0 || + (m_peerConnections[peer] & (1 << home)) == 0) + { + isAccessible = false; + } + } + + if (isAccessible) + { + island.push_back(*it); + unassigned.erase(it); // This device has been assigned to an island. + + it = unassigned.begin(); // The next unassigned device. + } + else + { + ++it; // The next unassigned device, without erase in between. + } + } + m_islands.push_back(island); + } + + std::ostringstream text; + + text << m_islands.size() << " peer-to-peer island"; + if (1 < m_islands.size()) + { + text << 's'; + } + text << ": "; + for (size_t i = 0; i < m_islands.size(); ++i) + { + const std::vector& island = m_islands[i]; + + text << "("; + for (size_t j = 0; j < island.size(); ++j) + { + // Print the ordinal here to be consistent with the other output about used devices. + text << m_devicesActive[island[j]]->m_ordinal; + if (j + 1 < island.size()) + { + text << ", "; + } + } + text << ")"; + if (i + 1 < m_islands.size()) + { + text << " + "; + } + } + std::cout << text.str() << '\n'; + + return success; +} + +void Raytracer::disablePeerAccess() +{ + const int size = static_cast(m_devicesActive.size()); + MY_ASSERT(size <= 32); + + // Peer-to-peer access is encoded in a bitfield of uint32 entries. + for (int home = 0; home < size; ++home) // Home device index. + { + for (int peer = 0; peer < size; ++peer) // Peer device index. + { + if (home != peer && (m_peerConnections[home] & (1 << peer)) != 0) + { + // Note that this function changes the current context! + CU_CHECK( cuCtxSetCurrent(m_devicesActive[home]->m_cudaContext) ); // Home context. + CU_CHECK( cuCtxDisablePeerAccess(m_devicesActive[peer]->m_cudaContext) ); // Peer context. + + m_peerConnections[home] &= ~(1 << peer); + } + } + } + + m_islands.clear(); // No peer-to-peer islands anymore. + + // Each device is its own island now. + for (int device = 0; device < size; ++device) + { + std::vector island; + + island.push_back(device); + + m_islands.push_back(island); + + m_peerConnections[device] |= (1 << device); // Should still be set from above. + } +} + +void Raytracer::synchronize() +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->activateContext(); + m_devicesActive[i]->synchronizeStream(); + } +} + +// FIXME This cannot handle cases where the same Picture would be used for different texture objects, but that is not happening in this example. +void Raytracer::initTextures(const std::map& mapPictures) +{ + const bool allowSharingTex = ((m_peerToPeer & P2P_TEX) != 0); // Material texture sharing (very cheap). + const bool allowSharingEnv = ((m_peerToPeer & P2P_ENV) != 0); // HDR Environment and CDF sharing (CDF binary search is expensive). + + for (std::map::const_iterator it = mapPictures.begin(); it != mapPictures.end(); ++it) + { + const Picture* picture = it->second; + + const bool isEnv = ((picture->getFlags() & IMAGE_FLAG_ENV) != 0); + + if ((allowSharingTex && !isEnv) || (allowSharingEnv && isEnv)) + { + for (const auto& island : m_islands) // Resource sharing only works across devices inside a peer-to-peer island. + { + const int deviceHome = getDeviceHome(island); + + const Texture* texture = m_devicesActive[deviceHome]->initTexture(it->first, picture, picture->getFlags()); + + for (auto device : island) + { + if (device != deviceHome) + { + m_devicesActive[device]->shareTexture(it->first, texture); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + (void) m_devicesActive[device]->initTexture(it->first, picture, picture->getFlags()); + } + } + } +} + + +void Raytracer::initCameras(const std::vector& cameras) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initCameras(cameras); + } +} + +// For mesh lights this needs to be aware of the GAS sharing which results in different sizes of the m_geometryData built in initScene()! +void Raytracer::initLights(const std::vector& lightsGUI) +{ + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); // GAS and vertex attribute sharing (GAS sharing is very expensive). + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + for (auto device : island) // Device index in this island. + { + m_devicesActive[device]->initLights(lightsGUI, m_geometryData, numIslands, indexIsland); + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + m_devicesActive[device]->initLights(lightsGUI, m_geometryData, numDevices, device); + } + } +} + +// Traverse the SceneGraph and store Groups, Instances and Triangles nodes in the raytracer representation. +void Raytracer::initScene(std::shared_ptr root, const unsigned int numGeometries) +{ + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); // GAS and vertex attribute sharing (GAS sharing is very expensive). + + if (allowSharingGas) + { + // Allocate the number of GeometryData per island. + m_geometryData.resize(numGeometries * m_islands.size()); // Sharing data per island. + } + else + { + // Allocate the number of GeometryData per active device. + m_geometryData.resize(numGeometries * m_devicesActive.size()); // Not sharing, all devices hold all geometry data. + } + + InstanceData instanceData(~0u, -1, -1, -1); + + float matrix[12]; + + // Set the affine matrix to identity by default. + memset(matrix, 0, sizeof(float) * 12); + matrix[ 0] = 1.0f; + matrix[ 5] = 1.0f; + matrix[10] = 1.0f; + + traverseNode(root, instanceData, matrix); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + for (auto device : island) // Device index in this island. + { + // The IAS and SBT are not shared in this example. + m_devicesActive[device]->createTLAS(); + m_devicesActive[device]->createGeometryInstanceData(m_geometryData, numIslands, indexIsland); + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + m_devicesActive[device]->createTLAS(); + m_devicesActive[device]->createGeometryInstanceData(m_geometryData, numDevices, device); + } + } +} + + +void Raytracer::initState(const DeviceState& state) +{ + m_samplesPerPixel = (unsigned int)(state.samplesSqrt * state.samplesSqrt); + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->setState(state); + } +} + +void Raytracer::updateCamera(const int idCamera, const CameraDefinition& camera) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateCamera(idCamera, camera); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateLight(const int idLight, const LightGUI& lightGUI) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateLight(idLight, lightGUI); + } + m_iterationIndex = 0; // Restart accumulation. +} + +//void Raytracer::updateLight(const int idLight, const LightDefinition& light) +//{ +// for (size_t i = 0; i < m_devicesActive.size(); ++i) +// { +// m_devicesActive[i]->updateLight(idLight, light); +// } +// m_iterationIndex = 0; // Restart accumulation. +//} + +void Raytracer::updateMaterial(const int idMaterial, const MaterialMDL* materialMDL) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateMaterial(idMaterial, materialMDL); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateState(const DeviceState& state) +{ + m_samplesPerPixel = (unsigned int)(state.samplesSqrt * state.samplesSqrt); + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->setState(state); + } + m_iterationIndex = 0; // Restart accumulation. +} + + +// The public function which does the multi-GPU wrapping. +// Returns the count of renderered iterations (m_iterationIndex after it has been incremented). +unsigned int Raytracer::render(const int mode) +{ + // Continue manual accumulation rendering if the samples per pixel have not been reached. + if (m_iterationIndex < m_samplesPerPixel) + { + void* buffer = nullptr; + + // Make sure the OpenGL device is allocating the full resolution backing storage. + const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // This is the device which needs to allocate the peer-to-peer buffer to reside on the same device as the PBO or Texture + m_devicesActive[index]->render(m_iterationIndex, &buffer, mode); // Interactive rendering. All devices work on the same iteration index. + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + if (index != static_cast(i)) + { + // If buffer is still nullptr here, the first device will allocate the full resolution buffer. + m_devicesActive[i]->render(m_iterationIndex, &buffer, mode); + } + } + + ++m_iterationIndex; + } + return m_iterationIndex; +} + +void Raytracer::updateDisplayTexture() +{ + const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // Only need to composite the resulting frame when using multiple decvices. + // Single device renders directly into the full resolution output buffer. + if (1 < m_devicesActive.size()) + { + // First, copy the texelBuffer of the primary device into its tileBuffer and then place the tiles into the outputBuffer. + m_devicesActive[index]->compositor(m_devicesActive[index]); + + // Now copy the other devices' texelBuffers over to the main tileBuffer and repeat the compositing for that other device. + // The cuMemcpyPeerAsync done in that case is fast when the devices are in the same peer island, otherwise it's copied via PCI-E, but only N-1 copies of 1/N size are done. + // The saving here is no peer-to-peer read-modify-write when rendering, because everything is happening in GPU local buffers, which are also tightly packed. + // The final compositing is just a kernel implementing a tiled memcpy. + // PERF If all tiles are copied to the main device at once, such kernel would only need to be called once. + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + if (index != static_cast(i)) + { + m_devicesActive[index]->compositor(m_devicesActive[i]); + } + } + } + // Finally copy the primary device outputBuffer to the display texture. + // FIXME DEBUG Does that work when m_indexDeviceOGL is not in the list of active devices? + m_devicesActive[index]->updateDisplayTexture(); +} + +const void* Raytracer::getOutputBufferHost() +{ + // Same initial steps to fill the outputBuffer on the primary device as in updateDisplayTexture() + const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // Only need to composite the resulting frame when using multiple decvices. + // Single device renders directly into the full resolution output buffer. + if (1 < m_devicesActive.size()) + { + // First, copy the texelBuffer of the primary device into its tileBuffer and then place the tiles into the outputBuffer. + m_devicesActive[index]->compositor(m_devicesActive[index]); + + // Now copy the other devices' texelBuffers over to the main tileBuffer and repeat the compositing for that other device. + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + if (index != static_cast(i)) + { + m_devicesActive[index]->compositor(m_devicesActive[i]); + } + } + } + // The full outputBuffer resides on device "index" and the host buffer is also only resized by that device. + return m_devicesActive[index]->getOutputBufferHost(); +} + +// Private functions. + +void Raytracer::selectDevices() +{ + // Need to determine the number of active devices first to have it available as device constructor argument. + int count = 0; + int ordinal = 0; + + while (ordinal < m_numDevicesVisible) // Don't try to enable more devices than visible to CUDA. + { + const unsigned int mask = (1 << ordinal); + + if (m_maskDevices & mask) + { + // Track which and how many devices have actually been enabled. + m_maskDevicesActive |= mask; + ++count; + } + + ++ordinal; + } + + // Now really construct the Device objects. + ordinal = 0; + + while (ordinal < m_numDevicesVisible) + { + const unsigned int mask = (1 << ordinal); + + if (m_maskDevicesActive & mask) + { + const int index = static_cast(m_devicesActive.size()); + + Device* device = new Device(ordinal, index, count, m_typeEnv, m_interop, m_tex, m_pbo, m_sizeArena); + + m_devicesActive.push_back(device); + + std::cout << "Device ordinal " << ordinal << ": " << device->m_deviceName << " selected as active device index " << index << '\n'; + } + + ++ordinal; + } +} + +#if 1 +// This implementation does not consider the actually free amount of VRAM on the individual devices in an island, but assumes they are equally loaded. +// This method works more fine grained with the arena allocator. +int Raytracer::getDeviceHome(const std::vector& island) const +{ + // Find the device inside each island which has the least amount of allocated memory. + size_t sizeMin = ~0ull; // Biggest unsigned 64-bit number. + int deviceHome = 0; // Default to zero if all devices are OOM. That will fail in CU_CHECK later. + + for (auto device : island) + { + const size_t size = m_devicesActive[device]->getMemoryAllocated(); + + if (size < sizeMin) + { + sizeMin = size; + deviceHome = device; + } + } + + //std::cout << "deviceHome = " << deviceHome << ", allocated [MiB] = " << double(sizeMin) / (1024.0 * 1024.0) << '\n'; // DEBUG + + return deviceHome; +} + +#else + +// This implementation uses the actual free amount of VRAM on the individual devices in an NVLINK island. +// With the arena allocator this will result in less fine grained distribution of resources because the free memory only changes when a new arena is allocated. +// Using a smaller arena size would switch allocations between devices more often in this case. +int Raytracer::getDeviceHome(const std::vector& island) const +{ + // Find the device inside each island which has the most free memory. + size_t sizeMax = 0; + int deviceHome = 0; // Default to zero if all devices are OOM. That will fail in CU_CHECK later. + + for (auto device : island) + { + const size_t size = m_devicesActive[device]->getMemoryFree(); // Actual free VRAM overall. + + if (sizeMax < size) + { + sizeMax = size; + deviceHome = device; + } + } + + //std::cout << "deviceHome = " << deviceHome << ", free [MiB] = " << double(sizeMax) / (1024.0 * 1024.0) << '\n'; // DEBUG + + return deviceHome; +} +#endif + +// m = a * b; +static void multiplyMatrix(float* m, const float* a, const float* b) +{ + m[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8]; // + a[3] * 0 + m[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9]; // + a[3] * 0 + m[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10]; // + a[3] * 0 + m[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[3]; // * 1 + + m[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8]; // + a[7] * 0 + m[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9]; // + a[7] * 0 + m[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10]; // + a[7] * 0 + m[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[7]; // * 1 + + m[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; // + a[11] * 0 + m[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; // + a[11] * 0 + m[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; // + a[11] * 0 + m[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; // * 1 +} + + +// Depth-first traversal of the scene graph to flatten all unique paths to a geometry node to one-level instancing inside the OptiX render graph. +void Raytracer::traverseNode(std::shared_ptr node, InstanceData instanceData, float matrix[12]) +{ + switch (node->getType()) + { + case sg::NodeType::NT_GROUP: + { + std::shared_ptr group = std::dynamic_pointer_cast(node); + + for (size_t i = 0; i < group->getNumChildren(); ++i) + { + traverseNode(group->getChild(i), instanceData, matrix); + } + } + break; + + case sg::NodeType::NT_INSTANCE: + { + std::shared_ptr instance = std::dynamic_pointer_cast(node); + + // Track the assigned material and light indices. Only the bottom-most instance node matters. + instanceData.idMaterial = instance->getMaterial(); + instanceData.idLight = instance->getLight(); + instanceData.idObject = instance->getId(); + + // Concatenate the transformations along the path. + float trafo[12]; + + multiplyMatrix(trafo, matrix, instance->getTransform()); + + traverseNode(instance->getChild(), instanceData, trafo); + } + break; + + case sg::NodeType::NT_TRIANGLES: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + const int deviceHome = getDeviceHome(island); + + // GeometryData is always shared and tracked per island. + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numIslands + indexIsland]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry in this island, try to create one on the home device. + { + geometryData = m_devicesActive[deviceHome]->createGeometry(geometry); + } + else + { + std::cout << "traverseNode() Geometry " << instanceData.idGeometry << " reused\n"; // DEBUG + } + + m_devicesActive[deviceHome]->createInstance(geometryData, instanceData, matrix); + + // Now share the GeometryData on the other devices in this island. + for (const auto device : island) + { + if (device != deviceHome) + { + // Create the instance referencing the shared GAS traversable on the peer device in this island. + // This is only host data. The IAS is created after gathering all flattened instances in the scene. + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numDevices + device]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry on this device, try to create one. + { + geometryData = m_devicesActive[device]->createGeometry(geometry); + } + + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + break; + + case sg::NodeType::NT_CURVES: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + const int deviceHome = getDeviceHome(island); + + // GeometryData is always shared and tracked per island. + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numIslands + indexIsland]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry in this island, try to create one on the home device. + { + geometryData = m_devicesActive[deviceHome]->createGeometry(geometry); + } + else + { + std::cout << "traverseNode() Geometry " << instanceData.idGeometry << " reused\n"; // DEBUG + } + + m_devicesActive[deviceHome]->createInstance(geometryData, instanceData, matrix); + + // Now share the GeometryData on the other devices in this island. + for (const auto device : island) + { + if (device != deviceHome) + { + // Create the instance referencing the shared GAS traversable on the peer device in this island. + // This is only host data. The IAS is created after gathering all flattened instances in the scene. + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numDevices + device]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry on this device, try to create one. + { + geometryData = m_devicesActive[device]->createGeometry(geometry); + } + + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + break; + + case sg::NodeType::NT_SDF: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + const int deviceHome = getDeviceHome(island); + + // GeometryData is always shared and tracked per island. + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numIslands + indexIsland]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry in this island, try to create one on the home device. + { + geometryData = m_devicesActive[deviceHome]->createGeometry(geometry); + } + else + { + std::cout << "traverseNode() Geometry " << instanceData.idGeometry << " reused\n"; // DEBUG + } + + m_devicesActive[deviceHome]->createInstance(geometryData, instanceData, matrix); + + // Now share the GeometryData on the other devices in this island. + for (const auto device : island) + { + if (device != deviceHome) + { + // Create the instance referencing the shared GAS traversable on the peer device in this island. + // This is only host data. The IAS is created after gathering all flattened instances in the scene. + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numDevices + device]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry on this device, try to create one. + { + geometryData = m_devicesActive[device]->createGeometry(geometry); + } + + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + break; + } +} + + +// MDL Material specific functions. + +static std::string replace(const std::string& source, const std::string& from, const std::string& to) +{ + if (source.empty()) + { + return source; + } + + std::string result; + result.reserve(source.length()); + + std::string::size_type lastPos = 0; + std::string::size_type findPos; + + while (std::string::npos != (findPos = source.find(from, lastPos))) + { + result.append(source, lastPos, findPos - lastPos); + result.append(to); + + lastPos = findPos + from.length(); + } + + //result += source.substr(lastPos); + result.append(source, lastPos, source.length() - lastPos); + + return result; +} + + +std::string buildModuleName(const std::string& path) +{ + if (path.empty()) + { + return path; + } + + // Build an MDL name. This assumes the path starts with a backslash (or slash on Linux). + std::string name = path; + +#if defined(_WIN32) + if (name[0] != '\\') + { + name = std::string("\\") + path; + } + name = replace(name, "\\", "::"); +#elif defined(__linux__) + if (name[0] != '/') + { + name = std::string("/") + path; + } + name = replace(name, "/", "::"); +#endif + + return name; +} + + +std::string add_missing_material_signature(const mi::neuraylib::IModule* module, + const std::string& material_name) +{ + // Return input if it already contains a signature. + if (material_name.back() == ')') + { + return material_name; + } + + mi::base::Handle result(module->get_function_overloads(material_name.c_str())); + + // Not supporting multiple function overloads with the same name but different signatures. + if (!result || result->get_length() != 1) + { + return std::string(); + } + + mi::base::Handle overloads(result->get_element(static_cast(0))); + + return overloads->get_c_str(); +} + + +bool isValidDistribution(mi::neuraylib::IExpression const* expr) +{ + if (expr == nullptr) + { + return false; + } + + if (expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle expr_constant(expr->get_interface()); + mi::base::Handle value(expr_constant->get_value()); + + if (value->get_kind() == mi::neuraylib::IValue::VK_INVALID_DF) + { + return false; + } + } + + return true; +} + + +// Returns a string-representation of the given message category +const char* message_kind_to_string(mi::neuraylib::IMessage::Kind message_kind) +{ + switch (message_kind) + { + case mi::neuraylib::IMessage::MSG_INTEGRATION: + return "MDL SDK"; + case mi::neuraylib::IMessage::MSG_IMP_EXP: + return "Importer/Exporter"; + case mi::neuraylib::IMessage::MSG_COMILER_BACKEND: + return "Compiler Backend"; + case mi::neuraylib::IMessage::MSG_COMILER_CORE: + return "Compiler Core"; + case mi::neuraylib::IMessage::MSG_COMPILER_ARCHIVE_TOOL: + return "Compiler Archive Tool"; + case mi::neuraylib::IMessage::MSG_COMPILER_DAG: + return "Compiler DAG generator"; + default: + break; + } + return ""; +} + + +// Returns a string-representation of the given message severity +const char* message_severity_to_string(mi::base::Message_severity severity) +{ + switch (severity) + { + case mi::base::MESSAGE_SEVERITY_ERROR: + return "ERROR"; + case mi::base::MESSAGE_SEVERITY_WARNING: + return "WARNING"; + case mi::base::MESSAGE_SEVERITY_INFO: + return "INFO"; + case mi::base::MESSAGE_SEVERITY_VERBOSE: + return "VERBOSE"; + case mi::base::MESSAGE_SEVERITY_DEBUG: + return "DEBUG"; + default: + break; + } + return ""; +} + + +class Default_logger: public mi::base::Interface_implement +{ +public: + void message(mi::base::Message_severity level, + const char* /* module_category */, + const mi::base::Message_details& /* details */, + const char* message) override + { + const char* severity = 0; + + switch (level) + { + case mi::base::MESSAGE_SEVERITY_FATAL: + severity = "FATAL: "; + MY_ASSERT(!"Default_logger() fatal error."); + break; + case mi::base::MESSAGE_SEVERITY_ERROR: + severity = "ERROR: "; + MY_ASSERT(!"Default_logger() error."); + break; + case mi::base::MESSAGE_SEVERITY_WARNING: + severity = "WARN: "; + break; + case mi::base::MESSAGE_SEVERITY_INFO: + //return; // DEBUG No info messages. + severity = "INFO: "; + break; + case mi::base::MESSAGE_SEVERITY_VERBOSE: + return; // DEBUG No verbose messages. + case mi::base::MESSAGE_SEVERITY_DEBUG: + return; // DEBUG No debug messages. + case mi::base::MESSAGE_SEVERITY_FORCE_32_BIT: + return; + } + + std::cerr << severity << message << '\n'; + } + + void message(mi::base::Message_severity level, + const char* module_category, + const char* message) override + { + this->message(level, module_category, mi::base::Message_details(), message); + } +}; + + +/// Callback that notifies the application about new resources when generating an +/// argument block for an existing target code. +class Resource_callback + : public mi::base::Interface_implement +{ +public: + /// Constructor. + Resource_callback(mi::neuraylib::ITransaction* transaction, + mi::neuraylib::ITarget_code const* target_code, + Compile_result& compile_result) + : m_transaction(mi::base::make_handle_dup(transaction)) + , m_target_code(mi::base::make_handle_dup(target_code)) + , m_compile_result(compile_result) + { + } + + /// Destructor. + virtual ~Resource_callback() = default; + + /// Returns a resource index for the given resource value usable by the target code + /// resource handler for the corresponding resource type. + /// + /// \param resource the resource value + /// + /// \returns a resource index or 0 if no resource index can be returned + mi::Uint32 get_resource_index(mi::neuraylib::IValue_resource const* resource) override + { + // check whether we already know the resource index + auto it = m_resource_cache.find(resource); + if (it != m_resource_cache.end()) + { + return it->second; + } + + // handle resources already known by the target code + mi::Uint32 res_idx = m_target_code->get_known_resource_index(m_transaction.get(), resource); + if (res_idx != 0) + { + // only accept body resources + switch (resource->get_kind()) + { + case mi::neuraylib::IValue::VK_TEXTURE: + if (m_target_code->get_texture_is_body_resource(res_idx)) + return res_idx; + break; + case mi::neuraylib::IValue::VK_LIGHT_PROFILE: + if (m_target_code->get_light_profile_is_body_resource(res_idx)) + return res_idx; + break; + case mi::neuraylib::IValue::VK_BSDF_MEASUREMENT: + if (m_target_code->get_bsdf_measurement_is_body_resource(res_idx)) + return res_idx; + break; + default: + return 0u; // invalid kind + } + } + + switch (resource->get_kind()) + { + case mi::neuraylib::IValue::VK_TEXTURE: + { + mi::base::Handle val_texture(resource->get_interface()); + if (!val_texture) + { + return 0u; // unknown resource + } + + mi::base::Handle texture_type(val_texture->get_type()); + + mi::neuraylib::ITarget_code::Texture_shape shape = mi::neuraylib::ITarget_code::Texture_shape(texture_type->get_shape()); + + m_compile_result.textures.emplace_back(resource->get_value(), shape); + res_idx = m_compile_result.textures.size() - 1; + break; + } + + case mi::neuraylib::IValue::VK_LIGHT_PROFILE: + m_compile_result.light_profiles.emplace_back(resource->get_value()); + res_idx = m_compile_result.light_profiles.size() - 1; + break; + + case mi::neuraylib::IValue::VK_BSDF_MEASUREMENT: + m_compile_result.bsdf_measurements.emplace_back(resource->get_value()); + res_idx = m_compile_result.bsdf_measurements.size() - 1; + break; + + default: + return 0u; // invalid kind + } + + m_resource_cache[resource] = res_idx; + return res_idx; + } + + /// Returns a string identifier for the given string value usable by the target code. + /// + /// The value 0 is always the "not known string". + /// + /// \param s the string value + mi::Uint32 get_string_index(mi::neuraylib::IValue_string const* s) override + { + char const* str_val = s->get_value(); + if (str_val == nullptr) + return 0u; + + for (mi::Size i = 0, n = m_target_code->get_string_constant_count(); i < n; ++i) + { + if (strcmp(m_target_code->get_string_constant(i), str_val) == 0) + { + return mi::Uint32(i); + } + } + + // string not known by code + return 0u; + } + +private: + mi::base::Handle m_transaction; + mi::base::Handle m_target_code; + + std::map m_resource_cache; + Compile_result& m_compile_result; +}; + + +mi::neuraylib::INeuray* Raytracer::load_and_get_ineuray(const char* filename) +{ + if (!filename) + { +//#ifdef IRAY_SDK +// filename = "libneuray" MI_BASE_DLL_FILE_EXT; +//#else + filename = "libmdl_sdk" MI_BASE_DLL_FILE_EXT; +//#endif + } + +#ifdef MI_PLATFORM_WINDOWS + + HMODULE handle = LoadLibraryA(filename); + //if (!handle) + //{ + // // fall back to libraries in a relative lib folder, relevant for install targets + // std::string fallback = std::string("../../../lib/") + filename; + // handle = LoadLibraryA(fallback.c_str()); + //} + if (!handle) + { + DWORD error_code = GetLastError(); + std::cerr << "ERROR: LoadLibraryA(" << filename << ") failed with error code " << error_code << '\n'; + return 0; + } + + void* symbol = GetProcAddress(handle, "mi_factory"); + if (!symbol) + { + DWORD error_code = GetLastError(); + std::cerr << "ERROR: GetProcAddress(handle, \"mi_factory\") failed with error " << error_code << '\n'; + return 0; + } + +#else // MI_PLATFORM_WINDOWS + + void* handle = dlopen(filename, RTLD_LAZY); + //if (!handle) + //{ + // // fall back to libraries in a relative lib folder, relevant for install targets + // std::string fallback = std::string("../../../lib/") + filename; + // handle = dlopen(fallback.c_str(), RTLD_LAZY); + //} + if (!handle) + { + std::cerr << "ERROR: dlopen(" << filename << " , RTLD_LAZY) failed with error code " << dlerror() << '\n'; + return 0; + } + + void* symbol = dlsym(handle, "mi_factory"); + if (!symbol) + { + std::cerr << "ERROR: dlsym(handle, \"mi_factory\") failed with error " << dlerror() << '\n'; + return 0; + } + +#endif // MI_PLATFORM_WINDOWS + + m_dso_handle = handle; + + mi::neuraylib::INeuray* neuray = mi::neuraylib::mi_factory(symbol); + if (!neuray) + { + mi::base::Handle version(mi::neuraylib::mi_factory(symbol)); + if (!version) + { + std::cerr << "ERROR: Incompatible library. Could not determine version.\n"; + } + else + { + std::cerr << "ERROR: Library version " << version->get_product_version() << " does not match header version " << MI_NEURAYLIB_PRODUCT_VERSION_STRING << '\n'; + } + return 0; + } + +//#ifdef IRAY_SDK +// if (authenticate(neuray) != 0) +// { +// std::cerr << "ERROR: Iray SDK Neuray Authentication failed.\n"; +// unload(); +// return 0; +// } +//#endif + + return neuray; +} + + +mi::Sint32 Raytracer::load_plugin(mi::neuraylib::INeuray* neuray, const char* path) +{ + mi::base::Handle plugin_conf(neuray->get_api_component()); + + // Try loading the requested plugin before adding any special handling + mi::Sint32 res = plugin_conf->load_plugin_library(path); + if (res == 0) + { + //std::cerr << "load_plugin(" << path << ") succeeded.\n"; // DEBUG The logger prints this. + return 0; + } + + // Special handling for freeimage in the open source release. + // In the open source version of the plugin we are linking against a dynamic vanilla freeimage library. + // In the binary release, you can download from the MDL website, freeimage is linked statically and + // thereby requires no special handling. +#if defined(MI_PLATFORM_WINDOWS) && defined(MDL_SOURCE_RELEASE) + if (strstr(path, "nv_freeimage" MI_BASE_DLL_FILE_EXT) != nullptr) + { + // Load the freeimage (without nv_ prefix) first. + std::string freeimage_3rd_party_path = replace(path, "nv_freeimage" MI_BASE_DLL_FILE_EXT, "freeimage" MI_BASE_DLL_FILE_EXT); + HMODULE handle_tmp = LoadLibraryA(freeimage_3rd_party_path.c_str()); + if (!handle_tmp) + { + DWORD error_code = GetLastError(); + std::cerr << "ERROR: load_plugin(" << freeimage_3rd_party_path << " failed with error " << error_code << '\n'; + } + else + { + std::cerr << "Pre-loading library " << freeimage_3rd_party_path << " succeeded\n"; + } + + // Try to load the plugin itself now + res = plugin_conf->load_plugin_library(path); + if (res == 0) + { + std::cerr << "load_plugin(" << path << ") succeeded.\n"; // DAR FIXME The logger prints this as info anyway. + return 0; + } + } +#endif + + // return the failure code + std::cerr << "ERROR: load_plugin(" << path << ") failed with error " << res << '\n'; + + return res; +} + + +bool Raytracer::initMDL(const std::vector& searchPaths) +{ + // Load MDL SDK library and create a Neuray handle. + m_neuray = load_and_get_ineuray(nullptr); + if (!m_neuray.is_valid_interface()) + { + std::cerr << "ERROR: Initialization of MDL SDK failed: libmdl_sdk" MI_BASE_DLL_FILE_EXT " not found or wrong version.\n"; + return false; + } + + // Create the MDL compiler. + m_mdl_compiler = m_neuray->get_api_component(); + if (!m_mdl_compiler) + { + std::cerr << "ERROR: Initialization of MDL compiler failed.\n"; + return false; + } + + // Configure Neuray. + // m_mdl_config->set_logger() and get_logger() are deprecated inside the MDL SDK 2023-11-14 + m_logging_config = m_neuray->get_api_component(); + if (!m_logging_config) + { + std::cerr << "ERROR: Retrieving logging configuration failed.\n"; + return false; + } + m_logger = mi::base::make_handle(new Default_logger()); + m_logging_config->set_receiving_logger(m_logger.get()); + + m_mdl_config = m_neuray->get_api_component(); + if (!m_mdl_config) + { + std::cerr << "ERROR: Retrieving MDL configuration failed.\n"; + return false; + } + + // Convenient default search paths for the NVIDIA MDL vMaterials! + + // Environment variable MDL_SYSTEM_PATH. + // Defaults to "C:\ProgramData\NVIDIA Corporation\mdl\" under Windows. + // Required to find ::nvidia::core_definitions imports used inside the vMaterials *.mdl files. + m_mdl_config->add_mdl_system_paths(); + + // Environment variable MDL_USER_PATH. + // Defaults to "C:\Users\\Documents\mdl\" under Windows. + // Required to find the vMaterials *.mdl files and their resources. + m_mdl_config->add_mdl_user_paths(); + + // Add all additional MDL and resource search paths defined inside the system description file as well. + for (auto const& path : searchPaths) + { + mi::Sint32 result = m_mdl_config->add_mdl_path(path.c_str()); + if (result != 0) + { + std::cerr << "WARNING: add_mdl_path( " << path << ") failed with " << result << '\n'; + } + + result = m_mdl_config->add_resource_path(path.c_str()); + if (result != 0) + { + std::cerr << "WARNING: add_resource_path( " << path << ") failed with " << result << '\n'; + } + } + + // Load plugins. +#if USE_OPENIMAGEIO_PLUGIN + if (load_plugin(m_neuray.get(), "nv_openimageio" MI_BASE_DLL_FILE_EXT) != 0) + { + std::cerr << "FATAL: Failed to load nv_openimageio plugin\n"; + return false; + } +#else + if (load_plugin(m_neuray.get(), "nv_freeimage" MI_BASE_DLL_FILE_EXT) != 0) + { + std::cerr << "FATAL: Failed to load nv_freeimage plugin\n"; + return false; + } +#endif + + if (load_plugin(m_neuray.get(), "dds" MI_BASE_DLL_FILE_EXT) != 0) + { + std::cerr << "FATAL: Failed to load dds plugin\n"; + return false; + } + + if (m_neuray->start() != 0) + { + std::cerr << "FATAL: Starting MDL SDK failed.\n"; + return false; + } + + m_database = m_neuray->get_api_component(); + + m_global_scope = m_database->get_global_scope(); + + m_mdl_factory = m_neuray->get_api_component(); + + // Configure the execution context. + // Used for various configurable operations and for querying warnings and error messages. + // It is possible to have more than one, in order to use different settings. + m_execution_context = m_mdl_factory->create_execution_context(); + + m_execution_context->set_option("internal_space", "coordinate_world"); // equals default + m_execution_context->set_option("bundle_resources", false); // equals default + m_execution_context->set_option("meters_per_scene_unit", 1.0f); // equals default + m_execution_context->set_option("mdl_wavelength_min", 380.0f); // equals default + m_execution_context->set_option("mdl_wavelength_max", 780.0f); // equals default + // If true, the "geometry.normal" field will be applied to the MDL state prior to evaluation of the given DF. + m_execution_context->set_option("include_geometry_normal", true); // equals default + + mi::base::Handle mdl_backend_api(m_neuray->get_api_component()); + + m_mdl_backend = mdl_backend_api->get_backend(mi::neuraylib::IMdl_backend_api::MB_CUDA_PTX); + + // Hardcoded values! + MY_STATIC_ASSERT(NUM_TEXTURE_SPACES == 1 || NUM_TEXTURE_SPACES == 2); + // The renderer only supports one or two texture spaces. + // The hair BSDF requires two texture coordinates! + // If you do not use the hair BSDF, NUM_TEXTURE_SPACES should be set to 1 for performance reasons. + + if (m_mdl_backend->set_option("num_texture_spaces", std::to_string(NUM_TEXTURE_SPACES).c_str()) != 0) + { + return false; + } + + if (m_mdl_backend->set_option("num_texture_results", std::to_string(NUM_TEXTURE_RESULTS).c_str()) != 0) + { + return false; + } + + // Use SM 5.0 for Maxwell and above. + if (m_mdl_backend->set_option("sm_version", "50") != 0) + { + return false; + } + + if (m_mdl_backend->set_option("tex_lookup_call_mode", "direct_call") != 0) + { + return false; + } + + // PERF Let expression functions return the result as value, instead of void return and sret pointer argument which is slower. + if (m_mdl_backend->set_option("lambda_return_mode", "value") != 0) + { + return false; + } + + //if (enable_derivatives) // == false. Not supported in this renderer + //{ + // // Option "texture_runtime_with_derivs": Default is disabled. + // // We enable it to get coordinates with derivatives for texture lookup functions. + // if (m_mdl_backend->set_option("texture_runtime_with_derivs", "on") != 0) + // { + // return false; + // } + //} + + if (m_mdl_backend->set_option("inline_aggressively", "on") != 0) + { + return false; + } + + // FIXME Determine what scene data the renderer needs to provide here. + // FIXME scene_data_names is not a supported option anymore! + //if (m_mdl_backend->set_option("scene_data_names", "*") != 0) + //{ + // return false; + //} + + // PERF Disable code generation for distribution pdf functions. + // The unidirectional light transport in this renderer never calls them. + // The sample and evaluate functions return the necessary pdf values. + if (m_mdl_backend->set_option("enable_pdf", "off") != 0) + { + std::cerr << "WARNING: Raytracer::initMDL() Setting backend option enable_pdf to off failed.\n"; + // Not a fatal error if this cannot be set. + // return false; + } + + m_image_api = m_neuray->get_api_component(); + + return true; +} + + +void Raytracer::shutdownMDL() +{ + m_shaderConfigurations.clear(); + m_shaders.clear(); // Code handles must be destroyed or there will be memory leaks indicated by MDL. + + m_mapMaterialHashToShaderIndex.clear(); + + m_image_api.reset(); + m_mdl_backend.reset(); + m_execution_context.reset(); + m_mdl_factory.reset(); + m_global_scope.reset(); + m_database.reset(); + m_mdl_config.reset(); + m_logging_config.reset(); + m_mdl_compiler.reset(); + + m_neuray->shutdown(); + m_neuray = nullptr; +} + + +bool Raytracer::log_messages(mi::neuraylib::IMdl_execution_context* context) +{ + m_last_mdl_error.clear(); + + for (mi::Size i = 0; i < context->get_messages_count(); ++i) + { + mi::base::Handle message(context->get_message(i)); + m_last_mdl_error += message_kind_to_string(message->get_kind()); + m_last_mdl_error += " "; + m_last_mdl_error += message_severity_to_string(message->get_severity()); + m_last_mdl_error += ": "; + m_last_mdl_error += message->get_string(); + m_last_mdl_error += "\n"; + } + return context->get_error_messages_count() == 0; +} + + +// Query expressions inside the compiled material to determine which direct callable functions need to be generated and +// what the closest hit program needs to call to fully render this material. +void Raytracer::determineShaderConfiguration(const Compile_result& res, ShaderConfiguration& config) +{ + config.is_thin_walled_constant = false; + config.thin_walled = false; + + mi::base::Handle thin_walled_expr(res.compiled_material->lookup_sub_expression("thin_walled")); + if (thin_walled_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + config.is_thin_walled_constant = true; + + mi::base::Handle expr_const(thin_walled_expr->get_interface()); + mi::base::Handle value_bool(expr_const->get_value()); + + config.thin_walled = value_bool->get_value(); + } + + mi::base::Handle surface_scattering_expr(res.compiled_material->lookup_sub_expression("surface.scattering")); + + config.is_surface_bsdf_valid = isValidDistribution(surface_scattering_expr.get()); // True if surface.scattering != bsdf(). + + config.is_backface_bsdf_valid = false; + + // The backface scattering is only used for thin-walled materials. + if (!config.is_thin_walled_constant || config.thin_walled) + { + // When backface == bsdf() MDL uses the surface scattering on both sides, irrespective of the thin_walled state. + mi::base::Handle backface_scattering_expr(res.compiled_material->lookup_sub_expression("backface.scattering")); + + config.is_backface_bsdf_valid = isValidDistribution(backface_scattering_expr.get()); // True if backface.scattering != bsdf(). + + if (config.is_backface_bsdf_valid) + { + // Only use the backface scattering when it's valid and different from the surface scattering expression. + config.is_backface_bsdf_valid = (res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_SCATTERING) != + res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_SCATTERING)); + } + } + + // Surface EDF. + mi::base::Handle surface_edf_expr(res.compiled_material->lookup_sub_expression("surface.emission.emission")); + + config.is_surface_edf_valid = isValidDistribution(surface_edf_expr.get()); + + config.is_surface_intensity_constant = true; + config.surface_intensity = mi::math::Color(0.0f, 0.0f, 0.0f); + config.is_surface_intensity_mode_constant = true; + config.surface_intensity_mode = 0; // == intensity_radiant_exitance; + + if (config.is_surface_edf_valid) + { + // Surface emission intensity. + mi::base::Handle surface_intensity_expr(res.compiled_material->lookup_sub_expression("surface.emission.intensity")); + + config.is_surface_intensity_constant = false; + + if (surface_intensity_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle intensity_const(surface_intensity_expr->get_interface()); + mi::base::Handle intensity_color(intensity_const->get_value()); + + if (get_value(intensity_color.get(), config.surface_intensity) == 0) + { + config.is_surface_intensity_constant = true; + } + } + + // Surface emission mode. This is a uniform and normally the default intensity_radiant_exitance + mi::base::Handle surface_intensity_mode_expr(res.compiled_material->lookup_sub_expression("surface.emission.mode")); + + config.is_surface_intensity_mode_constant = false; + + if (surface_intensity_mode_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle expr_const(surface_intensity_mode_expr->get_interface()); + mi::base::Handle value_enum(expr_const->get_value()); + + config.surface_intensity_mode = value_enum->get_value(); + + config.is_surface_intensity_mode_constant = true; + } + } + + // Backface EDF. + config.is_backface_edf_valid = false; + // DEBUG Is any of this needed at all or is the BSDF init() function handling all this? + config.is_backface_intensity_constant = true; + config.backface_intensity = mi::math::Color(0.0f, 0.0f, 0.0f); + config.is_backface_intensity_mode_constant = true; + config.backface_intensity_mode = 0; // == intensity_radiant_exitance; + config.use_backface_edf = false; + config.use_backface_intensity = false; + config.use_backface_intensity_mode = false; + + // A backface EDF is only used on thin-walled materials with a backface.emission.emission != edf() + if (!config.is_thin_walled_constant || config.thin_walled) + { + mi::base::Handle backface_edf_expr(res.compiled_material->lookup_sub_expression("backface.emission.emission")); + + config.is_backface_edf_valid = isValidDistribution(backface_edf_expr.get()); + + if (config.is_backface_edf_valid) + { + // Backface emission intensity. + mi::base::Handle backface_intensity_expr(res.compiled_material->lookup_sub_expression("backface.emission.intensity")); + + config.is_backface_intensity_constant = false; + + if (backface_intensity_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle intensity_const(backface_intensity_expr->get_interface()); + mi::base::Handle intensity_color(intensity_const->get_value()); + + if (get_value(intensity_color.get(), config.backface_intensity) == 0) + { + config.is_backface_intensity_constant = true; + } + } + + // Backface emission mode. This is a uniform and normally the default intensity_radiant_exitance. + mi::base::Handle backface_intensity_mode_expr(res.compiled_material->lookup_sub_expression("backface.emission.mode")); + + config.is_backface_intensity_mode_constant = false; + + if (backface_intensity_mode_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle expr_const(backface_intensity_mode_expr->get_interface()); + mi::base::Handle value_enum(expr_const->get_value()); + + config.backface_intensity_mode = value_enum->get_value(); + + config.is_backface_intensity_mode_constant = true; + } + + // When surface and backface expressions are identical, reuse the surface expression to generate less code. + config.use_backface_edf = (res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_EMISSION_EDF_EMISSION) != + res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_EMISSION_EDF_EMISSION)); + + // If the surface and backface emission use different intensities then use the backface emission intensity. + config.use_backface_intensity = (res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_EMISSION_INTENSITY) != + res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_EMISSION_INTENSITY)); + + // If the surface and backface emission use different modes (radiant exitance vs. power) then use the backface emission intensity mode. + config.use_backface_intensity_mode = (res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_EMISSION_MODE) != + res.compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_EMISSION_MODE)); + } + } + + config.is_ior_constant = true; + config.ior = mi::math::Color(1.0f, 1.0f, 1.0f); + + mi::base::Handle ior_expr(res.compiled_material->lookup_sub_expression("ior")); + if (ior_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle expr_const(ior_expr->get_interface()); + mi::base::Handle value_color(expr_const->get_value()); + + if (get_value(value_color.get(), config.ior) == 0) + { + config.is_ior_constant = true; + } + } + else + { + config.is_ior_constant = false; + } + + // FIXME This renderer currently only supports a single df::anisotropic_vdf() under the volume.scattering expression. + // MDL 1.8 added fog_vdf() and there are also mixers and modifiers on VDFs, which, while valid expressions, won't be evaluated. + mi::base::Handle volume_vdf_expr(res.compiled_material->lookup_sub_expression("volume.scattering")); + + config.is_vdf_valid = isValidDistribution(volume_vdf_expr.get()); + + // Absorption coefficient. Can be used without valid VDF. + config.is_absorption_coefficient_constant = true; // Default to constant and no absorption. + config.use_volume_absorption = false; // If there is no abosorption, the absorption coefficient is constant zero. + config.absorption_coefficient = mi::math::Color(0.0f, 0.0f, 0.0f); // No absorption. + + mi::base::Handle volume_absorption_coefficient_expr(res.compiled_material->lookup_sub_expression("volume.absorption_coefficient")); + + if (volume_absorption_coefficient_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle expr_const(volume_absorption_coefficient_expr->get_interface()); + mi::base::Handle value_color(expr_const->get_value()); + + if (get_value(value_color.get(), config.absorption_coefficient) == 0) + { + config.is_absorption_coefficient_constant = true; + + if (config.absorption_coefficient[0] != 0.0f || config.absorption_coefficient[1] != 0.0f || config.absorption_coefficient[2] != 0.0f) + { + config.use_volume_absorption = true; + } + } + } + else + { + config.is_absorption_coefficient_constant = false; + config.use_volume_absorption = true; + } + + // Scattering coefficient. Only used when there is a valid VDF. + config.is_scattering_coefficient_constant = true; // Default to constant and no scattering. Assumes invalid VDF. + config.use_volume_scattering = false; + config.scattering_coefficient = mi::math::Color(0.0f, 0.0f, 0.0f); // No scattering + + // Directional bias (Henyey_Greenstein g factor.) Only used when there is a valid VDF and volume scattering coefficient not zero. + config.is_directional_bias_constant = true; + config.directional_bias = 0.0f; + + // The anisotropic_vdf() is the only valid VDF. + // The scattering_coefficient, directional_bias (and emission_intensity) are only needed when there is a valid VDF. + if (config.is_vdf_valid) + { + mi::base::Handle volume_scattering_coefficient_expr(res.compiled_material->lookup_sub_expression("volume.scattering_coefficient")); + + if (volume_scattering_coefficient_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + mi::base::Handle expr_const(volume_scattering_coefficient_expr->get_interface()); + mi::base::Handle value_color(expr_const->get_value()); + + if (get_value(value_color.get(), config.scattering_coefficient) == 0) + { + config.is_scattering_coefficient_constant = true; + + if (config.scattering_coefficient[0] != 0.0f || config.scattering_coefficient[1] != 0.0f || config.scattering_coefficient[2] != 0.0f) + { + config.use_volume_scattering = true; + } + } + } + else + { + config.is_scattering_coefficient_constant = false; + config.use_volume_scattering = true; + } + + // FIXME This assumes a single anisotropic_vdf() under the volume.scattering expression! + // MDL 1.8 fog_vdf() or VDF mixers or modifiers are not supported, yet, and the returned expression will be nullptr then. + mi::base::Handle volume_directional_bias_expr(res.compiled_material->lookup_sub_expression("volume.scattering.directional_bias")); + + // Ignore unsupported volume.scattering expressions, instead use anisotropic_vdf() with constant isotropic directional_bias == 0.0f. + if (volume_directional_bias_expr.get() != nullptr) + { + if (volume_directional_bias_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT) + { + config.is_directional_bias_constant = true; + + mi::base::Handle expr_const(volume_directional_bias_expr->get_interface()); + mi::base::Handle value_float(expr_const->get_value()); + + // 0.0f is isotropic. No need to distinguish. The sampleHenyeyGreenstein() function takes this as parameter anyway. + config.directional_bias = value_float->get_value(); + } + else + { + config.is_directional_bias_constant = false; + } + } + else + { + std::cerr << "WARNING: Unsupported volume.scattering expression. directional_bias not found, using isotropic VDF.\n"; + } + + // volume.scattering.emission_intensity is not supported by this renderer. + // Also the volume absorption and scattering coefficients are assumed to be homogeneous in this renderer. + } + + // geometry.displacement is not supported by this renderer. + + // geometry.normal is automatically handled because of set_option("include_geometry_normal", true); + + config.cutout_opacity = 1.0f; // Default is fully opaque. + config.is_cutout_opacity_constant = res.compiled_material->get_cutout_opacity(&config.cutout_opacity); // This sets cutout opacity to -1.0 when it's not constant! + config.use_cutout_opacity = !config.is_cutout_opacity_constant || config.cutout_opacity < 1.0f; + + mi::base::Handle hair_bsdf_expr(res.compiled_material->lookup_sub_expression("hair")); + + config.is_hair_bsdf_valid = isValidDistribution(hair_bsdf_expr.get()); // True if hair != hair_bsdf(). +} + + +void Raytracer::initMaterialsMDL(std::vector& materialsMDL) +{ + // This will compile the material to OptiX PTX code and build the OptiX program and texture data on all devices + // and track the material configuration and parameters stored inside the Application class to be able to build the GUI. + for (MaterialMDL* materialMDL : materialsMDL) + { + initMaterialMDL(materialMDL); + } + + // After all MDL material references have been handled and the device side data has been allocated, upload the necessary data to the GPU. + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initTextureHandler(materialsMDL); + } +} + + +void Raytracer::initMaterialMDL(MaterialMDL* material) +{ + // This function is called per unique material reference. + // No need to check for duplicate reference definitions. + + mi::base::Handle handleTransaction = mi::base::make_handle(m_global_scope->create_transaction()); + mi::neuraylib::ITransaction* transaction = handleTransaction.get(); + + // Local scope for all handles used inside the Compile_result. + { + Compile_result res; + + // Split into separate functions to make the Neuray handles and transaction scope lifetime handling automatic. + // When the function was successful, the Compile_result contains all information required to setup the device resources. + const bool valid = compileMaterial(transaction, material, res); + if (!valid) + { + std::cerr << "ERROR: Raytracer::initMaterialMDL() compileMaterial() failed. Material invalid.\n"; + } + + material->setIsValid(valid); + + if (valid) + { + // Create the OptiX programs on all devices. + for (size_t device = 0; device < m_devicesActive.size(); ++device) + { + m_devicesActive[device]->compileMaterial(transaction, material, res, m_shaderConfigurations[material->getShaderIndex()]); + } + + // Prepare 2D and 3D textures. + const bool allowSharingTex = ((m_peerToPeer & P2P_TEX) != 0); // Material texture sharing (very cheap). + + // Create the CUDA texture arrays on the devices with peer-to-peer sharing when enabled. + for (mi::Size idxRes = 1; idxRes < res.textures.size(); ++idxRes) // The zeroth index is the invalid texture. + { + bool first = true; // Only append each texture index to the MaterialMDL m_indicesToTextures vector once. + + if (allowSharingTex) + { + for (const auto& island : m_islands) // Resource sharing only works across devices inside a peer-to-peer island. + { + const int deviceHome = getDeviceHome(island); + + const TextureMDLHost* shared = m_devicesActive[deviceHome]->prepareTextureMDL(transaction, + m_image_api, + res.textures[idxRes].db_name.c_str(), + res.textures[idxRes].shape); + + // Update the MaterialMDL vector of texture indices into the per-device cache only once! + // This is per material reference and all caches are the same size on the all devices, shared CUarrays or not. + if (first && shared != nullptr) + { + material->m_indicesToTextures.push_back(shared->m_index); + first = false; + } + + for (auto device : island) + { + if (device != deviceHome) + { + m_devicesActive[device]->shareTextureMDL(shared, + res.textures[idxRes].db_name.c_str(), + res.textures[idxRes].shape); + } + } + } + } + else + { + for (size_t device = 0; device < m_devicesActive.size(); ++device) + { + const TextureMDLHost* texture = m_devicesActive[device]->prepareTextureMDL(transaction, + m_image_api, + res.textures[idxRes].db_name.c_str(), + res.textures[idxRes].shape); + if (texture == nullptr) + { + std::cerr << "ERROR: initMaterialMDL(): prepareTextureMDL() failed for " << res.textures[idxRes].db_name << '\n'; + } + else if (device == 0) // Only store the index once into the vector at the MaterialMDL. + { + material->m_indicesToTextures.push_back(texture->m_index); + } + } + } + } + + const bool allowSharingMBSDF = ((m_peerToPeer & P2P_MBSDF) != 0); // MBSDF texture and CDF sharing (medium expensive due to the memory traffic on the CDFs) + + // Prepare Bsdf_measurements. + for (mi::Size idxRes = 1; idxRes < res.bsdf_measurements.size(); ++idxRes) // The zeroth index is the invalid Bsdf_measurement. + { + bool first = true; + + if (allowSharingMBSDF) + { + for (const auto& island : m_islands) // Resource sharing only works across devices inside a peer-to-peer island. + { + const int deviceHome = getDeviceHome(island); + + const MbsdfHost* shared = m_devicesActive[deviceHome]->prepareMBSDF(transaction, res.target_code.get(), idxRes); + + if (shared == nullptr) + { + std::cerr << "ERROR: initMaterialMDL(): prepareMBSDF() failed for BSDF measurement " << idxRes << '\n'; + } + else if (first) + { + material->m_indicesToMBSDFs.push_back(shared->m_index); + first = false; + } + + for (auto device : island) + { + if (device != deviceHome) + { + m_devicesActive[device]->shareMBSDF(shared); + } + } + } + } + else + { + for (size_t device = 0; device < m_devicesActive.size(); ++device) + { + const MbsdfHost* mbsdf = m_devicesActive[device]->prepareMBSDF(transaction, res.target_code.get(), idxRes); + + if (mbsdf == nullptr) + { + std::cerr << "ERROR: initMaterialMDL(): prepareMBSDF() failed for BSDF measurement " << idxRes << '\n'; + } + else if (device == 0) // Only store the index once into the vector at the MaterialMDL. + { + material->m_indicesToMBSDFs.push_back(mbsdf->m_index); + } + } + } + } + + const bool allowSharingLightprofile = ((m_peerToPeer & P2P_IES) != 0); // IES texture and CDF sharing (medium expensive due to the memory traffic on the CDFs) + + // Prepare Light_profiles. + for (mi::Size idxRes = 1; idxRes < res.light_profiles.size(); ++idxRes) // The zeroth index is the invalid light profile. + { + bool first = true; + + if (allowSharingLightprofile) + { + for (const auto& island : m_islands) // Resource sharing only works across devices inside a peer-to-peer island. + { + const int deviceHome = getDeviceHome(island); + + const LightprofileHost* shared = m_devicesActive[deviceHome]->prepareLightprofile(transaction, res.target_code.get(), idxRes); + + if (shared == nullptr) + { + std::cerr << "ERROR: initMaterialMDL(): prepareLightprofile() failed for light profile " << idxRes << '\n'; + } + else if (first) + { + material->m_indicesToLightprofiles.push_back(shared->m_index); + first = false; + } + + for (auto device : island) + { + if (device != deviceHome) + { + m_devicesActive[device]->shareLightprofile(shared); + } + } + } + } + else + { + for (size_t device = 0; device < m_devicesActive.size(); ++device) + { + const LightprofileHost* profile = m_devicesActive[device]->prepareLightprofile(transaction, res.target_code.get(), idxRes); + + if (profile == nullptr) + { + std::cerr << "ERROR: initMaterialMDL(): prepareLightprofile() failed for light profile " << idxRes << '\n'; + } + else if (device == 0) // Only store the index once into the vector at the MaterialMDL. + { + material->m_indicesToLightprofiles.push_back(profile->m_index); + } + } + } + } + } + } + + transaction->commit(); +} + + +bool Raytracer::compileMaterial(mi::neuraylib::ITransaction* transaction, MaterialMDL* materialMDL, Compile_result& res) +{ + // Build the fully qualified MDL module name. + // The *.mdl file path has been converted to the proper OS format during input. + std::string path = materialMDL->getPath(); + + // Path needs to end with ".mdl" so any path with 4 or less characters cannot be a valid path name. + if (path.size() <= 4) + { + std::cerr << "ERROR: compileMaterial() Path name " << path << " too short.\n"; + return false; + } + + const std::string::size_type last = path.size() - 4; + + if (path.substr(last, path.size()) != std::string(".mdl")) + { + std::cerr << "ERROR: compileMaterial() Path name " << path << " not matching \".mdl\".\n"; + return false; + } + + std::string module_name = buildModuleName(path.substr(0, last)); + + // Get everything to load the module. + mi::base::Handle mdl_factory(m_neuray->get_api_component()); // FIXME Redundant, could use m_mdl_factory. + + mi::base::Handle mdl_impexp_api(m_neuray->get_api_component()); + + // Create an execution context for options and error message handling + mi::base::Handle context(mdl_factory->create_execution_context()); + + mi::Sint32 reason = mdl_impexp_api->load_module(transaction, module_name.c_str(), context.get()); + if (reason < 0) + { + std::cerr << "ERROR: compileMaterial() Failed to load module " << module_name << '\n'; + switch (reason) + { + // case 1: // Success (module exists already, loading from file was skipped). + // case 0: // Success (module was actually loaded from file). + case -1: + std::cerr << "The module name is invalid or a NULL pointer.\n"; + break; + case -2: + std::cerr << "Failed to find or to compile the module.\n"; + break; + case -3: + std::cerr << "The database name for an imported module is already in use but is not an MDL module,\n"; + std::cerr << "or the database name for a definition in this module is already in use.\n"; + break; + case -4: + std::cerr << "Initialization of an imported module failed.\n"; + break; + default: + std::cerr << "Unexpected return value " << reason << " from IMdl_impexp_api::load_module().\n"; + MY_ASSERT(!"Unexpected return value from IMdl_compiler::load_module()"); + break; + } + } + + if (!log_messages(context.get())) + { + return false; + } + + // Get the database name for the module we loaded. + mi::base::Handle module_db_name(mdl_factory->get_db_module_name(module_name.c_str())); + + // Note that the lifetime of this module handle must end before the transaction->commit() or there will be warnings. + // This is automatically handled by placing the transaction into the caller. + mi::base::Handle module(transaction->access(module_db_name->get_c_str())); + if (!module) + { + std::cerr << "ERROR: compileMaterial() Failed to access the loaded module " << module_db_name->get_c_str() << '\n'; + return false; + } + + // Build the fully qualified data base name of the material. + const std::string material_simple_name = materialMDL->getName(); + + std::string material_db_name = std::string(module_db_name->get_c_str()) + "::" + material_simple_name; + + material_db_name = add_missing_material_signature(module.get(), material_db_name); + + if (material_db_name.empty()) + { + std::cerr << "ERROR: compileMaterial() Failed to find the material " + material_simple_name + " in the module " + module_name + ".\n"; + return false; + } + + // Compile the material. + + // Create a material instance from the material definition with the default arguments. + mi::base::Handle material_definition(transaction->access(material_db_name.c_str())); + if (!material_definition) + { + std::cerr << "ERROR: compileMaterial() Material definition could not be created for " << material_simple_name << '\n'; + return false; + } + + mi::Sint32 ret = 0; + mi::base::Handle material_instance(material_definition->create_function_call(0, &ret)); + if (ret != 0) + { + std::cerr << "ERROR: compileMaterial() Instantiating material " + material_simple_name + " failed"; + return false; + } + + // Create a compiled material. + // DEBUG Experiment with instance compilation as well to see how the performance changes. + mi::Uint32 flags = mi::neuraylib::IMaterial_instance::CLASS_COMPILATION; + + mi::base::Handle material_instance2(material_instance->get_interface()); + + res.compiled_material = material_instance2->create_compiled_material(flags, context.get()); + if (!log_messages(context.get())) + { + std::cerr << "ERROR: compileMaterial() create_compiled_material() failed.\n"; + return false; + } + + // Check if the target code has already been generated for another material reference name and reuse the existing target code. + int indexShader = -1; // Invalid index. + + mi::base::Uuid material_hash = res.compiled_material->get_hash(); + + std::map::const_iterator it = m_mapMaterialHashToShaderIndex.find(material_hash); + if (it != m_mapMaterialHashToShaderIndex.end()) + { + indexShader = it->second; + + res.target_code = m_shaders[indexShader]; + + // Initialize with body resources always being required. + // Mind that the zeroth resource is the invalid resource. + for (mi::Size i = 1, n = res.target_code->get_texture_count(); i < n; ++i) + { + if (res.target_code->get_texture_is_body_resource(i)) + { + res.textures.emplace_back(res.target_code->get_texture(i), res.target_code->get_texture_shape(i)); + } + } + + for (mi::Size i = 1, n = res.target_code->get_light_profile_count(); i < n; ++i) + { + if (res.target_code->get_light_profile_is_body_resource(i)) + { + res.light_profiles.emplace_back(res.target_code->get_light_profile(i)); + } + } + + for (mi::Size i = 1, n = res.target_code->get_bsdf_measurement_count(); i < n; ++i) + { + if (res.target_code->get_bsdf_measurement_is_body_resource(i)) + { + res.bsdf_measurements.emplace_back(res.target_code->get_bsdf_measurement(i)); + } + } + + if (res.target_code->get_argument_block_count() > 0) + { + // Create argument block for the new compiled material and additional resources + mi::base::Handle res_callback(new Resource_callback(transaction, res.target_code.get(), res)); + + res.argument_block = res.target_code->create_argument_block(0, res.compiled_material.get(), res_callback.get()); + } + } + else + { + // Generate new target code. + indexShader = static_cast(m_shaders.size()); // The amount of different shaders in the code cache gives the next index. + + // Determine the material configuration by checking which minimal amount of expressions need to be generated as direct callable programs. + ShaderConfiguration config; + + determineShaderConfiguration(res, config); + + // Build the required function descriptions for the expression required by the material configuration. + std::vector descs; + + const std::string suffix = std::to_string(indexShader); + + // These are all expressions required for a material which does everything supported in this renderer. + // The Target_function_description only stores the C-pointers to the base names! + // Make sure these are not destroyed as long as the descs vector is used. + std::string name_init = "__direct_callable__init" + suffix; + std::string name_thin_walled = "__direct_callable__thin_walled" + suffix; + std::string name_surface_scattering = "__direct_callable__surface_scattering" + suffix; + std::string name_surface_emission_emission = "__direct_callable__surface_emission_emission" + suffix; + std::string name_surface_emission_intensity = "__direct_callable__surface_emission_intensity" + suffix; + std::string name_surface_emission_mode = "__direct_callable__surface_emission_mode" + suffix; + std::string name_backface_scattering = "__direct_callable__backface_scattering" + suffix; + std::string name_backface_emission_emission = "__direct_callable__backface_emission_emission" + suffix; + std::string name_backface_emission_intensity = "__direct_callable__backface_emission_intensity" + suffix; + std::string name_backface_emission_mode = "__direct_callable__backface_emission_mode" + suffix; + std::string name_ior = "__direct_callable__ior" + suffix; + std::string name_volume_absorption_coefficient = "__direct_callable__volume_absorption_coefficient" + suffix; + std::string name_volume_scattering_coefficient = "__direct_callable__volume_scattering_coefficient" + suffix; + std::string name_volume_directional_bias = "__direct_callable__volume_directional_bias" + suffix; + std::string name_geometry_cutout_opacity = "__direct_callable__geometry_cutout_opacity" + suffix; + std::string name_hair_bsdf = "__direct_callable__hair" + suffix; + + // Centralize the init functions in a single material init(). + // This will only save time when there would have been multiple init functions inside the shader. + // Also for very complicated materials with cutout opacity this is most likely a loss, + // because the geometry.cutout is only needed inside the anyhit program and + // that doesn't need additional evalations for the BSDFs, EDFs, or VDFs at that point. + descs.push_back(mi::neuraylib::Target_function_description("init", name_init.c_str())); + + if (!config.is_thin_walled_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("thin_walled", name_thin_walled.c_str())); + } + if (config.is_surface_bsdf_valid) + { + descs.push_back(mi::neuraylib::Target_function_description("surface.scattering", name_surface_scattering.c_str())); + } + if (config.is_surface_edf_valid) + { + descs.push_back(mi::neuraylib::Target_function_description("surface.emission.emission", name_surface_emission_emission.c_str())); + if (!config.is_surface_intensity_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("surface.emission.intensity", name_surface_emission_intensity.c_str())); + } + if (!config.is_surface_intensity_mode_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("surface.emission.mode", name_surface_emission_mode.c_str())); + } + } + if (config.is_backface_bsdf_valid) + { + descs.push_back(mi::neuraylib::Target_function_description("backface.scattering", name_backface_scattering.c_str())); + } + if (config.is_backface_edf_valid) + { + if (config.use_backface_edf) + { + descs.push_back(mi::neuraylib::Target_function_description("backface.emission.emission", name_backface_emission_emission.c_str())); + } + if (config.use_backface_intensity && !config.is_backface_intensity_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("backface.emission.intensity", name_backface_emission_intensity.c_str())); + } + if (config.use_backface_intensity_mode && !config.is_backface_intensity_mode_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("backface.emission.mode", name_backface_emission_mode.c_str())); + } + } + if (!config.is_ior_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("ior", name_ior.c_str())); + } + if (!config.is_absorption_coefficient_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("volume.absorption_coefficient", name_volume_absorption_coefficient.c_str())); + } + if (config.is_vdf_valid) + { + // The MDL SDK is NOT generating functions for VDFs! This would fail in ILink_unit::add_material(). + // descs.push_back(mi::neuraylib::Target_function_description("volume.scattering", name_volume_scattering.c_str())); + + // The scattering coefficient and directional bias are not used when there is no valid VDF. + if (!config.is_scattering_coefficient_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("volume.scattering_coefficient", name_volume_scattering_coefficient.c_str())); + } + + // FIXME This assumes the volume.scattering expression is exactly df::anisotropic_vdf(), not df::fog_vdf() or VDF mixers or modifiers. + // config.is_directional_bias_constant == true for unsupported volume.scattering expressions and this description is not added. + if (!config.is_directional_bias_constant) + { + descs.push_back(mi::neuraylib::Target_function_description("volume.scattering.directional_bias", name_volume_directional_bias.c_str())); + } + + // volume.scattering.emission_intensity is not implemented. + } + + // geometry.displacement is not implemented. + + // geometry.normal is automatically handled because of set_option("include_geometry_normal", true); + + if (config.use_cutout_opacity) + { + descs.push_back(mi::neuraylib::Target_function_description("geometry.cutout_opacity", name_geometry_cutout_opacity.c_str())); + } + if (config.is_hair_bsdf_valid) + { + descs.push_back(mi::neuraylib::Target_function_description("hair", name_hair_bsdf.c_str())); + } + + // Generate target code for the compiled material. + mi::base::Handle link_unit(m_mdl_backend->create_link_unit(transaction, context.get())); + + mi::Sint32 reason = link_unit->add_material(res.compiled_material.get(), descs.data(), descs.size(), context.get()); + if (reason != 0) + { + std::cerr << "ERROR: compileMaterial() link_unit->add_material() returned " << reason << '\n'; + } + if (!log_messages(context.get())) + { + std::cerr << "ERROR: compileMaterial() On link_unit->add_material()\n"; + return false; + } + + res.target_code = mi::base::Handle(m_mdl_backend->translate_link_unit(link_unit.get(), context.get())); + if (!log_messages(context.get())) + { + std::cerr << "ERROR: compileMaterial() On m_mdl_backend->translate_link_unit()\n"; + return false; + } + + // Store the new shader index in the map. + m_mapMaterialHashToShaderIndex[material_hash] = indexShader; + + // These two vectors are the same size: + // Store the target code handle inside the shader cache. + m_shaders.push_back(res.target_code); + // Store the shader configuration to be able to build the required direct callables on the device later. + m_shaderConfigurations.push_back(config); + + // Add all used resources. The zeroth entry is the invalid resource. + for (mi::Size i = 1, n = res.target_code->get_texture_count(); i < n; ++i) + { + res.textures.emplace_back(res.target_code->get_texture(i), res.target_code->get_texture_shape(i)); + } + + if (res.target_code->get_light_profile_count() > 0) + { + for (mi::Size i = 1, n = res.target_code->get_light_profile_count(); i < n; ++i) + { + res.light_profiles.emplace_back(res.target_code->get_light_profile(i)); + } + } + + if (res.target_code->get_bsdf_measurement_count() > 0) + { + for (mi::Size i = 1, n = res.target_code->get_bsdf_measurement_count(); i < n; ++i) + { + res.bsdf_measurements.emplace_back(res.target_code->get_bsdf_measurement(i)); + } + } + + if (res.target_code->get_argument_block_count() > 0) + { + res.argument_block = res.target_code->get_argument_block(0); + } + +#if 0 // DEBUG Print or write the PTX code when a new shader has been generated. + if (res.target_code) + { + std::string code = res.target_code->get_code(); + + // Print generated PTX source code to the console. + //std::cout << code << std::endl; + + // Dump generated PTX source code to a local folder for offline comparisons. + const std::string filename = std::string("./mdl_ptx/") + material_simple_name + std::string("_") + getDateTime() + std::string(".ptx"); + + saveString(filename, code); + } +#endif // DEBUG + + } // End of generating new target code. + + // Build the material information for this material reference. + mi::base::Handle arg_layout; + + if (res.target_code->get_argument_block_count() > 0) + { + arg_layout = res.target_code->get_argument_block_layout(0); + } + + // Store the material information per reference inside the MaterialMDL structure. + materialMDL->storeMaterialInfo(indexShader, + material_definition.get(), + res.compiled_material.get(), + arg_layout.get(), + res.argument_block.get()); + + // Now that the code and the resources are setup as MDL handles, + // call into the Device class to setup the CUDA and OptiX resources. + + return true; +} + + +bool Raytracer::isEmissiveShader(const int indexShader) const +{ + bool result = false; + + if (0 <= indexShader && indexShader < m_shaderConfigurations.size()) + { + result = m_shaderConfigurations[indexShader].isEmissive(); + } + else + { + std::cout << "ERROR: isEmissiveShader() called with invalid index " << indexShader << '\n'; + } + + return result; +} + diff --git a/apps/MDL_sdf/src/SceneGraph.cpp b/apps/MDL_sdf/src/SceneGraph.cpp new file mode 100644 index 00000000..d86dcbab --- /dev/null +++ b/apps/MDL_sdf/src/SceneGraph.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" +#include "shaders/vector_math.h" + +#include "inc/SceneGraph.h" + +#include +#include +#include + +#include "inc/MyAssert.h" + +namespace sg +{ + // ========== Node + Node::Node(const unsigned int id) + : m_id(id) + { + } + + //Node::~Node() + //{ + //} + + // ========== Group + Group::Group(const unsigned int id) + : Node(id) + { + } + + //Group::~Group() + //{ + //} + + sg::NodeType Group::getType() const + { + return NT_GROUP; + } + + void Group::addChild(std::shared_ptr instance) + { + m_children.push_back(instance); + } + + size_t Group::getNumChildren() const + { + return m_children.size(); + } + + std::shared_ptr Group::getChild(const size_t index) + { + MY_ASSERT(index < m_children.size()); + return m_children[index]; + } + + + // ========== Instance + Instance::Instance(const unsigned int id) + : Node(id) + , m_material(-1) // No material index set by default. Last one >= 0 along a path wins. + , m_light(-1) // No light index set by default. Not a light. + { + // Set the affine matrix to identity by default. + memset(m_matrix, 0, sizeof(float) * 12); + m_matrix[ 0] = 1.0f; + m_matrix[ 5] = 1.0f; + m_matrix[10] = 1.0f; + } + + //Instance::~Instance() + //{ + //} + + sg::NodeType Instance::getType() const + { + return NT_INSTANCE; + } + + void Instance::setTransform(const float m[12]) + { + memcpy(m_matrix, m, sizeof(float) * 12); + } + + const float* Instance::getTransform() const + { + return m_matrix; + } + + void Instance::setChild(std::shared_ptr node) // Instances can hold all other groups. + { + m_child = node; + } + + std::shared_ptr Instance::getChild() + { + return m_child; + } + + void Instance::setMaterial(const int index) + { + m_material = index; + } + + int Instance::getMaterial() const + { + return m_material; + } + + void Instance::setLight(const int index) + { + m_light = index; + } + + int Instance::getLight() const + { + return m_light; + } + + // ========== Triangles + Triangles::Triangles(const unsigned int id) + : Node(id) + { + } + + //Triangles::~Triangles() + //{ + //} + + sg::NodeType Triangles::getType() const + { + return NT_TRIANGLES; + } + + void Triangles::setAttributes(const std::vector& attributes) + { + m_attributes.resize(attributes.size()); + memcpy(m_attributes.data(), attributes.data(), sizeof(TriangleAttributes) * attributes.size()); + } + + const std::vector& Triangles::getAttributes() const + { + return m_attributes; + } + + void Triangles::setIndices(const std::vector& indices) + { + m_indices.resize(indices.size()); + memcpy(m_indices.data(), indices.data(), sizeof(unsigned int) * indices.size()); + } + + const std::vector& Triangles::getIndices() const + { + return m_indices; + } + + // Helper function for arbitrary mesh lights. + // PERF Implement this in a CUDA kernel. + void Triangles::calculateLightArea(LightGUI& lightGUI) const + { + const size_t numTriangles = m_indices.size() / 3; + + lightGUI.cdfAreas.resize(numTriangles + 1); + + float areaSurface = 0.0f; + + lightGUI.cdfAreas[0] = areaSurface; // CDF starts with zero. One element more than number of triangles. + + for (size_t i = 0; i < numTriangles; ++i) + { + const size_t idx = i * 3; + + const unsigned int i0 = m_indices[idx ]; + const unsigned int i1 = m_indices[idx + 1]; + const unsigned int i2 = m_indices[idx + 2]; + + // All in object space. + const float3 v0 = m_attributes[i0].vertex; + const float3 v1 = m_attributes[i1].vertex; + const float3 v2 = m_attributes[i2].vertex; + + // PERF Work in world space to do fewer transforms during explicit light hits. + dp::math::Vec3f p0(dp::math::Vec4f(v0.x, v0.y, v0.z, 1.0f) * lightGUI.matrix); + dp::math::Vec3f p1(dp::math::Vec4f(v1.x, v1.y, v1.z, 1.0f) * lightGUI.matrix); + dp::math::Vec3f p2(dp::math::Vec4f(v2.x, v2.y, v2.z, 1.0f) * lightGUI.matrix); + + dp::math::Vec3f e0 = p1 - p0; + dp::math::Vec3f e1 = p2 - p0; + + // The triangle area is half of the parallelogram area (length of cross product). + const float area = dp::math::length(e0 ^ e1) * 0.5f; + + areaSurface += area; + + lightGUI.cdfAreas[i + 1] = areaSurface; // Store the unnormalized sums of triangle surfaces. + } + + // Normalize the CDF values. + // PERF This means only the lightGUI.area integral value is in world space and the CDF could be reused for instanced mesh lights. + for (auto& val : lightGUI.cdfAreas) + { + val /= areaSurface; + // The last cdf element will automatically be 1.0f. + // If this happens to be smaller due to inaccuracies in the floating point calculations, + // the clamping to valid triangle indices inside the sample_light_mesh() function will + // prevent out of bounds accesses, no need for corrections here. + // (The corrections would be to set all identical values below 1.0f at the end of this array to 1.0f.) + } + + lightGUI.area = areaSurface; + } + + + // ========== Curves + Curves::Curves(const unsigned int id) + : Node(id) + { + } + + //Curves::~Curves() + //{ + //} + + sg::NodeType Curves::getType() const + { + return NT_CURVES; + } + + void Curves::setAttributes(std::vector const& attributes) + { + m_attributes.resize(attributes.size()); + memcpy(m_attributes.data(), attributes.data(), sizeof(CurveAttributes) * attributes.size()); + } + + std::vector const& Curves::getAttributes() const + { + return m_attributes; + } + + void Curves::setIndices(std::vector const& indices) + { + m_indices.resize(indices.size()); + memcpy(m_indices.data(), indices.data(), sizeof(unsigned int) * indices.size()); + } + + std::vector const& Curves::getIndices() const + { + return m_indices; + } + + // ========== SignedDistanceField + SignedDistanceField::SignedDistanceField(const unsigned int id) + : Node(id) + { + } + + //SignedDistanceField::~SignedDistanceField() + //{ + //} + + sg::NodeType SignedDistanceField::getType() const + { + return NT_SDF; + } + + void SignedDistanceField::setAttributes(std::vector const& attributes) + { + m_attributes.resize(attributes.size()); + memcpy(m_attributes.data(), attributes.data(), sizeof(SignedDistanceFieldAttributes) * attributes.size()); + } + + std::vector const& SignedDistanceField::getAttributes() const + { + return m_attributes; + } + + std::string SignedDistanceField::getFilename() const + { + return m_filename; + } + + //void SignedDistanceField::setIndices(std::vector const& indices) + //{ + // m_indices.resize(indices.size()); + // memcpy(m_indices.data(), indices.data(), sizeof(unsigned int) * indices.size()); + //} + + //std::vector const& SignedDistanceField::getIndices() const + //{ + // return m_indices; + //} + +} // namespace sg + diff --git a/apps/MDL_sdf/src/SignedDistanceField.cpp b/apps/MDL_sdf/src/SignedDistanceField.cpp new file mode 100644 index 00000000..dfbc7ab1 --- /dev/null +++ b/apps/MDL_sdf/src/SignedDistanceField.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // DAR FIXME This is pretty redundant when the 3D texture of the SDF is assumes to be placed inside the object space unit cube. + // It would be possible to put many SDF primitives into one GAS when their AABB placement would be different. + // Leave this as it is and hardcode the intersection routine assuming the unit cube object space coordinates first. + // DAR HACK Currently not loading a 3D texture. Implement an intersection shader with am SDF formula first. + bool SignedDistanceField::createSDF(const float3 minimum, + const float3 maximum, + const float lipschitz, + const unsigned int width, + const unsigned int height, + const unsigned int depth, + const std::string& filename) + { + m_attributes.clear(); + //m_indices.clear(); + + SignedDistanceFieldAttributes attrib; + + // Unit cube AABB, plus some epsilon! + // DANGER: When SDFs which are generating coplanar surface with the unit cube (like fBox(p, make_float3(1.0f)) + // there are rendering artifacts if the AABB is exactly the unit cube as well. + // The sphere tracing is only done inside the interval between entry and exit points of the ray-AABB intersections. + // That can miss the SDF and results in circular corruption artifacts on surfaces coplanar with the AABB unit cube. + // To solve that, adjust the AABB extents outwards to make sure the SDF can always be hit. + // This is likely to cost some performane, esp. when instancing a lot of SDF tighly together + // instead of folding the SDF space in a single scaled AABB. + + //attrib.minimum = make_float3(-1.0f - offset); + //attrib.maximum = make_float3( 1.0f + offset); + + // Do not use an offset when using the 3D texture! + // The transformation from object into texture space assumes the 3D texture ends exactly on the AABB extents. + attrib.minimum = minimum; + attrib.maximum = maximum; + attrib.sdfTexture = 0; // Cannot set this to the CUDA texture object before it has been created on the device and is stored in m_mapTextures. + attrib.sdfLipschitz = lipschitz; + attrib.sdfTextureSize = make_uint3(width, height, depth); // 3D texture size is needed for the central differencing. + + m_attributes.push_back(attrib); + + m_filename = filename; // Remember the filename of the SDF 3D texture to be able to look it up inside the m_mapPictures. + + return true; // DAR FIXME This will indicate later if the SDF data could be loaded. + } + +} // namespace sg diff --git a/apps/MDL_sdf/src/Sphere.cpp b/apps/MDL_sdf/src/Sphere.cpp new file mode 100644 index 00000000..2519cd45 --- /dev/null +++ b/apps/MDL_sdf/src/Sphere.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + void Triangles::createSphere(const unsigned int tessU, const unsigned int tessV, const float radius, const float maxTheta) + { + MY_ASSERT(3 <= tessU && 3 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + m_attributes.reserve((tessU + 1) * tessV); + m_indices.reserve(6 * tessU * (tessV - 1)); + + float phi_step = 2.0f * M_PIf / (float) tessU; + float theta_step = maxTheta / (float) (tessV - 1); + + // Latitudinal rings. + // Starting at the south pole going upwards on the y-axis. + for (unsigned int latitude = 0; latitude < tessV; ++latitude) // theta angle + { + float theta = (float) latitude * theta_step; + float sinTheta = sinf(theta); + float cosTheta = cosf(theta); + + float texv = (float) latitude / (float) (tessV - 1); // Range [0.0f, 1.0f] + + // Generate vertices along the latitudinal rings. + // On each latitude there are tessU + 1 vertices. + // The last one and the first one are on identical positions, but have different texture coordinates! + // FIXME Note that each second triangle connected to the two poles has zero area! + for (unsigned int longitude = 0; longitude <= tessU; ++longitude) // phi angle + { + float phi = (float) longitude * phi_step; + float sinPhi = sinf(phi); + float cosPhi = cosf(phi); + + float texu = (float) longitude / (float) tessU; // Range [0.0f, 1.0f] + + // Unit sphere coordinates are the normals. + float3 normal = make_float3(cosPhi * sinTheta, + -cosTheta, // -y to start at the south pole. + -sinPhi * sinTheta); + TriangleAttributes attrib; + + attrib.vertex = normal * radius; + attrib.tangent = make_float3(-sinPhi, 0.0f, -cosPhi); + attrib.normal = normal; + attrib.texcoord = make_float3(texu, texv, 0.0f); + + m_attributes.push_back(attrib); + } + } + + // We have generated tessU + 1 vertices per latitude. + const unsigned int columns = tessU + 1; + + // Calculate m_indices. + for (unsigned int latitude = 0; latitude < tessV - 1; ++latitude) + { + for (unsigned int longitude = 0; longitude < tessU; ++longitude) + { + m_indices.push_back( latitude * columns + longitude); // lower left + m_indices.push_back( latitude * columns + longitude + 1); // lower right + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + m_indices.push_back((latitude + 1) * columns + longitude); // upper left + m_indices.push_back( latitude * columns + longitude); // lower left + } + } + } + +} // namespace sg diff --git a/apps/MDL_sdf/src/Texture.cpp b/apps/MDL_sdf/src/Texture.cpp new file mode 100644 index 00000000..30b7cc6c --- /dev/null +++ b/apps/MDL_sdf/src/Texture.cpp @@ -0,0 +1,2258 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "shaders/vector_math.h" + +#include "inc/Device.h" +//#include "inc/Texture.h" // Device.h includes Texture.h +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include + + +// The ENC_RED|GREEN|BLUE|ALPHA|LUM codes define from which source channel is read when writing R, G, B, A or L. +static unsigned int determineHostEncoding(int format, int type) // format and type are DevIL defines. +{ + unsigned int encoding; + + switch (format) + { + case IL_RGB: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_3; + break; + case IL_RGBA: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; + break; + case IL_BGR: + encoding = ENC_RED_2 | ENC_GREEN_1 | ENC_BLUE_0 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_3; + break; + case IL_BGRA: + encoding = ENC_RED_2 | ENC_GREEN_1 | ENC_BLUE_0 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; + break; + case IL_LUMINANCE: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_0 | ENC_CHANNELS_1; // Source L from channel 0. + //encoding = ENC_RED_0 | ENC_GREEN_0 | ENC_BLUE_0 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_1; // Source RGB from L to expand to (L, L, L, 1). + break; + case IL_ALPHA: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_0 | ENC_LUM_NONE | ENC_CHANNELS_1; // Source A from channel 0. + break; + case IL_LUMINANCE_ALPHA: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_1 | ENC_LUM_0 | ENC_CHANNELS_2; // Source L from 0 and A from 1 + //encoding = ENC_RED_0 | ENC_GREEN_0 | ENC_BLUE_0 | ENC_ALPHA_1 | ENC_LUM_NONE | ENC_CHANNELS_2; // Source RGB from L in channel 0 and A from A in channel 1 to expand to (L, L, L, A). + break; + default: + MY_ASSERT(!"Unsupported user pixel format."); + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_INVALID; // Error! Invalid encoding. + break; + } + + switch (type) + { + case IL_UNSIGNED_BYTE: + encoding |= ENC_TYPE_UNSIGNED_CHAR; + break; + case IL_UNSIGNED_SHORT: + encoding |= ENC_TYPE_UNSIGNED_SHORT; + break; + case IL_UNSIGNED_INT: + encoding |= ENC_TYPE_UNSIGNED_INT; + break; + case IL_BYTE: + encoding |= ENC_TYPE_CHAR; + break; + case IL_SHORT: + encoding |= ENC_TYPE_SHORT; + break; + case IL_INT: + encoding |= ENC_TYPE_INT; + break; + case IL_FLOAT: + encoding |= ENC_TYPE_FLOAT; + break; + case IL_HALF: + encoding |= ENC_TYPE_HALF; + break; + default: + MY_ASSERT(!"Unsupported user data format."); + encoding |= ENC_INVALID; // Error! Invalid encoding. + break; + } + + return encoding; +} + +// For OpenGL interop these formats are supported by CUDA according to the current manual on cudaGraphicsGLRegisterImage: +// GL_RED, GL_RG, GL_RGBA, GL_LUMINANCE, GL_ALPHA, GL_LUMINANCE_ALPHA, GL_INTENSITY +// {GL_R, GL_RG, GL_RGBA} x {8, 16, 16F, 32F, 8UI, 16UI, 32UI, 8I, 16I, 32I} +// {GL_LUMINANCE, GL_ALPHA, GL_LUMINANCE_ALPHA, GL_INTENSITY} x {8, 16, 16F_ARB, 32F_ARB, 8UI_EXT, 16UI_EXT, 32UI_EXT, 8I_EXT, 16I_EXT, 32I_EXT} + +// The following mapping is done for host textures. RGB formats will be expanded to RGBA. + +// While single and dual channel textures can easily be uploaded, the texture doesn't know what the destination format actually is, +// that is, a LUMINANCE_ALPHA texture returns the luminance in the red channel and the alpha in the green channel. +// With IES profiles defined as luminance textures, take that step and actually use 1-, 2-, and 4-component textures now. +// This works because IES emission textures are explicitly sampled only and that function knows to read only a single float component. +// DEBUG Check how the tex*<>(obj, ...) templates react when asking for more data than in the texture. + +static unsigned int determineDeviceEncoding(int format, int type) // format and type are DevIL defines. +{ + unsigned int encoding; + + switch (format) + { + case IL_RGB: + case IL_BGR: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE; // (R, G, B, 1) + break; + case IL_RGBA: + case IL_BGRA: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // (R, G, B, A) + break; + case IL_LUMINANCE: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_0 | ENC_CHANNELS_1; // L to (R) + //encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE; // L to (L, L, L, 1) + break; + case IL_ALPHA: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_0 | ENC_LUM_NONE | ENC_CHANNELS_1; // A to (R) + //encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // A expands to (0, 0, 0, A) + break; + case IL_LUMINANCE_ALPHA: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_1 | ENC_LUM_0 | ENC_CHANNELS_2; // LA to (R, G) + //encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // LA expands to (L, L, L, A) + break; + default: + MY_ASSERT(!"Unsupported user pixel format."); + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_INVALID; // Error! Invalid encoding. + break; + } + + // FIXME Setting ENC_FIXED_POINT for all non-float formats means the conversion to a different destination format + // will always adjust to fixed point representation and not just cast, except for float. + // Means for real integer textures the source and destination formats must match to hit the memcpy path. + switch (type) + { + case IL_BYTE: + encoding |= ENC_TYPE_CHAR | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_BYTE: + encoding |= ENC_TYPE_UNSIGNED_CHAR | ENC_FIXED_POINT; + break; + case IL_SHORT: + encoding |= ENC_TYPE_SHORT | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_SHORT: + encoding |= ENC_TYPE_UNSIGNED_SHORT | ENC_FIXED_POINT; + break; + case IL_INT: + encoding |= ENC_TYPE_INT | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_INT: + encoding |= ENC_TYPE_UNSIGNED_INT | ENC_FIXED_POINT; + break; + case IL_FLOAT: + encoding |= ENC_TYPE_FLOAT; + break; + case IL_HALF: + // DEBUG Why are EXR images loaded as IL_FLOAT (in DevIL 1.7.8)? + encoding |= ENC_TYPE_HALF; + break; + default: + MY_ASSERT(!"Unsupported user data format."); + encoding |= ENC_INVALID; // Error! Invalid encoding. + break; + } + + return encoding; +} + +// Helper function calculating the CUarray_format +static void determineFormatChannels(const unsigned int encodingDevice, CUarray_format& format, unsigned int& numChannels) +{ + const unsigned int type = encodingDevice & (ENC_MASK << ENC_TYPE_SHIFT); + + switch (type) + { + case ENC_TYPE_CHAR: + format = CU_AD_FORMAT_SIGNED_INT8; + break; + case ENC_TYPE_UNSIGNED_CHAR: + format = CU_AD_FORMAT_UNSIGNED_INT8; + break; + case ENC_TYPE_SHORT: + format = CU_AD_FORMAT_SIGNED_INT16; + break; + case ENC_TYPE_UNSIGNED_SHORT: + format = CU_AD_FORMAT_UNSIGNED_INT16; + break; + case ENC_TYPE_INT: + format = CU_AD_FORMAT_SIGNED_INT32; + break; + case ENC_TYPE_UNSIGNED_INT: + format = CU_AD_FORMAT_UNSIGNED_INT32; + break; + case ENC_TYPE_FLOAT: + format = CU_AD_FORMAT_FLOAT; + break; + case ENC_TYPE_HALF: + format = CU_AD_FORMAT_HALF; + break; + default: + MY_ASSERT(!"determineFormatChannels() Unexpected data type."); + break; + } + + numChannels = (encodingDevice >> ENC_CHANNELS_SHIFT) & ENC_MASK; +} + + +static unsigned int getElementSize(const unsigned int encodingDevice) +{ + unsigned int bytes = 0; + + const unsigned int type = encodingDevice & (ENC_MASK << ENC_TYPE_SHIFT); + + switch (type) + { + case ENC_TYPE_CHAR: + case ENC_TYPE_UNSIGNED_CHAR: + bytes = 1; + break; + + case ENC_TYPE_SHORT: + case ENC_TYPE_UNSIGNED_SHORT: + case ENC_TYPE_HALF: + bytes = 2; + break; + + case ENC_TYPE_INT: + case ENC_TYPE_UNSIGNED_INT: + case ENC_TYPE_FLOAT: + bytes = 4; + break; + + default: + MY_ASSERT(!"getElementSize() Unexpected data type."); + break; + } + + const unsigned int numChannels = (encodingDevice >> ENC_CHANNELS_SHIFT) & ENC_MASK; + + return bytes * numChannels; +} + + +// Texture format conversion routines. + +template +T getAlphaOne() +{ + return (std::numeric_limits::is_integer ? std::numeric_limits::max() : T(1)); +} + +// Fixed point adjustment for integer data D and S. +template +D adjust(S value) +{ + const int dstBits = int(sizeof(D)) * 8; + int srcBits = int(sizeof(S)) * 8; + + D result = D(0); // Clear bits to allow OR operations. + + if (std::numeric_limits::is_signed) + { + if (std::numeric_limits::is_signed) + { + // D signed, S signed + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits)); + } + else + { + // Shift value into the most significant bits of result and replicate value into the lower bits until all are touched. + int shifts = dstBits - srcBits; + result = D(value << shifts); // This sets the destination sign bit as well. + value &= std::numeric_limits::max(); // Clear the sign bit inside the source value. + srcBits--; // Reduce the number of srcBits used to replicate the remaining data. + shifts -= srcBits; // Subtracting the now one smaller srcBits from shifts means the next shift will fill up with the remaining non-sign bits as intended. + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) // There can be one to three empty bits left blank in the result now. + { + result |= D(value >> -shifts); // Shift to the right to get the most significant bits of value into the least significant destination bits. + } + } + } + else + { + // D signed, S unsigned + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits + 1)); // + 1 because the destination is signed and the value needs to remain positive. + } + else + { + // Shift value into the most significant bits of result, keep the sign clear, and replicate value into the lower bits until all are touched. + int shifts = dstBits - srcBits - 1; // - 1 because the destination is signed and the value needs to remain positive. + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) + { + result |= D(value >> -shifts); + } + } + } + } + else + { + if (std::numeric_limits::is_signed) + { + // D unsigned, S signed + value = std::max(S(0), value); // Only the positive values will be transferred. + srcBits--; // Skip the sign bit. Means equal bit size won't happen here. + if (dstBits <= srcBits) // When it's really bigger it has at least 7 bits more, no need to care for dangling bits + { + result = D(value >> (srcBits - dstBits)); + } + else + { + int shifts = dstBits - srcBits; + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) + { + result |= D(value >> -shifts); + } + } + } + else + { + // D unsigned, S unsigned + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits)); + } + else + { + // Shift value into the most significant bits of result and replicate into the lower ones until all bits are touched. + int shifts = dstBits - srcBits; + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + // Both bit sizes are even multiples of 8, there are no trailing bits here. + MY_ASSERT(shifts == -srcBits); + } + } + } + return result; +} + + +template +void remapAdjust(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const S *psrc = reinterpret_cast(src); + D *pdst = reinterpret_cast(dst); + const unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const bool fixedPoint = !!(dstEncoding & ENC_FIXED_POINT); + const bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) // Five possible channels: R, G, B, A, L + { + const unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + const unsigned int s = (srcEncoding >> shift) & ENC_MASK; + // If destination alpha was added to support this format or if no source data is given for alpha, fill it with 1. + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + if (s < 4) // There is data for this channel inside the source. (This could be a luminance to RGB mapping as well). + { + const S value = psrc[s]; + pdst[d] = (fixedPoint) ? adjust(value) : D(value); + } + else // no value provided + { + pdst[d] = D(0); + } + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +// Straight channel copy with no adjustment. Since the data types match, fixed point doesn't matter. +template +void remapCopy(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const T *psrc = reinterpret_cast(src); + T *pdst = reinterpret_cast(dst); + const unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) // Five possible channels: R, G, B, A, L + { + const unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + const unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + pdst[d] = (s < 4) ? psrc[s] : T(0); + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +template +void remapFromFloat(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const float *psrc = reinterpret_cast(src); + D *pdst = reinterpret_cast(dst); + const unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const bool fixedPoint = !!(dstEncoding & ENC_FIXED_POINT); + const bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + const float minimum = (std::numeric_limits::is_signed) ? -1.0f : 0.0f; + const float maximum = float(std::numeric_limits::max()); // This will run out of precision for int and unsigned int. + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) + { + const unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + const unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + if (s < 4) // This data channel exists inside the source. + { + float value = psrc[s]; + if (fixedPoint) + { + MY_ASSERT(std::numeric_limits::is_integer); // Destination with float format cannot be fixed point. + + value = std::min(std::max(minimum, value), 1.0f); + pdst[d] = D(std::numeric_limits::max() * value); // Scaled copy. + } + else // element type, clamped copy. + { + pdst[d] = D(std::min(std::max(-maximum, value), maximum)); + } + } + else // no value provided + { + pdst[d] = D(0); + } + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +template +void remapToFloat(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const S *psrc = reinterpret_cast(src); + float *pdst = reinterpret_cast(dst); + const unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + const bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + MY_ASSERT(std::numeric_limits::is_integer); // This function is not called with S being float. + const float maximum = float(std::numeric_limits::max()); // This will run out of precision for int and unsigned int. + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) + { + const unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + const unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = 1.0f; + } + else + { + // If there is data for this source channel, interpret it as fixed point and scale it to its normalized float range! + // This is mainly done to match the native LDR emission texture objects to the integral of the CDF calculation. + // This will run out of precision for int and unsigned int source data. + pdst[d] = (s < 4) ? float(psrc[s]) / maximum : 0.0f; + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + + +typedef void (*PFNREMAP)(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count); + +// Function table with 49 texture format conversion routines from loaded image data to supported CUDA texture formats. +// Index is [destination type][source type] +PFNREMAP remappers[7][7] = +{ + { + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapFromFloat + }, + { + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapCopy + } +}; + + +// Finally the function which converts any loaded image into a texture format supported by CUDA (1, 2, 4 channels only). +static void convert(void *dst, unsigned int encodingDevice, const void *src, unsigned int hostEncoding, size_t elements) +{ + // Only destination encoding knows about the fixed-point encoding. For straight data memcpy() cases that is irrelevant. + // PERF Avoid this conversion altogether when it's just a memcpy(). + if ((encodingDevice & ~ENC_FIXED_POINT) == hostEncoding) + { + memcpy(dst, src, elements * getElementSize(encodingDevice)); // The fastest path. + } + else + { + unsigned int dstType = (encodingDevice >> ENC_TYPE_SHIFT) & ENC_MASK; + unsigned int srcType = (hostEncoding >> ENC_TYPE_SHIFT) & ENC_MASK; + + // FIXME This code path is not supporting IL_HALF. That's used for SDF 3D texture and must take the memcpy() path above! + if (dstType < 7 && srcType < 7) + { + PFNREMAP pfn = remappers[dstType][srcType]; + + (*pfn)(dst, encodingDevice, src, hostEncoding, elements); + } + else + { + std::cerr << "ERROR: No tetxure format remapper implemented from srcType = " << srcType << " to dstType = " << dstType << '\n'; + MY_ASSERT(dstType < 7 && srcType < 7); + } + } +} + +Texture::Texture(Device* device) +: m_owner(device) +, m_width(0) +, m_height(0) +, m_depth(0) +, m_flags(0) +, m_encodingHost(ENC_INVALID) +, m_encodingDevice(ENC_INVALID) +, m_sizeBytesPerElement(0) +, m_textureObject(0) +, m_d_array(0) +, m_d_mipmappedArray(0) +, m_sizeBytesArray(0) +, m_d_cdfU(0) +, m_d_cdfV(0) +, m_integral(1.0f) +{ + m_descArray3D.Width = 0; + m_descArray3D.Height = 0; + m_descArray3D.Depth = 0; + m_descArray3D.Format = CU_AD_FORMAT_UNSIGNED_INT8; + m_descArray3D.NumChannels = 0; + m_descArray3D.Flags = 0; // Things like CUDA_ARRAY3D_SURFACE_LDST, ... + + // Clear the resource description once. They will be set inside the individual Texture::create*() functions. + memset(&m_resourceDescription, 0, sizeof(CUDA_RESOURCE_DESC)); + + // Note that cuTexObjectCreate() fails if the "int reserved[12]" data is not zero! Not mentioned in the CUDA documentation. + memset(&m_textureDescription, 0, sizeof(CUDA_TEXTURE_DESC)); + + // Setup CUDA_TEXTURE_DESC defaults. + // The developer can override these at will before calling Texture::create(). Afterwards they are immutable + // If the flag CU_TRSF_NORMALIZED_COORDINATES is not set, the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP. + m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_WRAP; + + m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + + // Possible flags: CU_TRSF_READ_AS_INTEGER, CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_SRGB + m_textureDescription.flags = CU_TRSF_NORMALIZED_COORDINATES; + + m_textureDescription.maxAnisotropy = 1; + + // LOD 0 only by default. + // This means when using mipmaps it's the developer's responsibility to set at least + // maxMipmapLevelClamp > 0.0f before calling Texture::create() to make sure mipmaps can be sampled! + m_textureDescription.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + m_textureDescription.mipmapLevelBias = 0.0f; + m_textureDescription.minMipmapLevelClamp = 0.0f; + m_textureDescription.maxMipmapLevelClamp = 0.0f; // This should be set to Picture::getNumberOfLevels() when using mipmaps. + + m_textureDescription.borderColor[0] = 0.0f; + m_textureDescription.borderColor[1] = 0.0f; + m_textureDescription.borderColor[2] = 0.0f; + m_textureDescription.borderColor[3] = 0.0f; +} + +Texture::~Texture() +{ + // Note that destroy() handles the shared data destruction. + if (m_textureObject) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(m_textureObject) ); + } +} + +size_t Texture::destroy(Device* device) +{ + if (m_owner == device) + { + if (m_d_array) + { + CU_CHECK_NO_THROW( cuArrayDestroy(m_d_array) ); + } + if (m_d_mipmappedArray) + { + CU_CHECK_NO_THROW( cuMipmappedArrayDestroy(m_d_mipmappedArray) ); + } + + // FIXME Move these into a derived TextureEnvironment class. + if (m_d_cdfU) + { + m_owner->memFree(m_d_cdfU); + } + if (m_d_cdfV) + { + m_owner->memFree(m_d_cdfV); + } + + return m_sizeBytesArray; // Return the size of the CUarray or CUmipmappedArray data in bytes for memory tracking. + } + + return 0; +} + +// For all functions changing the m_textureDescription values, +// make sure they are called before the texture object has been created, +// otherwise the texture object would need to be recreated. + +// Set the whole description. +void Texture::setTextureDescription(const CUDA_TEXTURE_DESC& descr) +{ + m_textureDescription = descr; +} + +void Texture::setAddressMode(CUaddress_mode s, CUaddress_mode t, CUaddress_mode r) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.addressMode[0] = s; + m_textureDescription.addressMode[1] = t; + m_textureDescription.addressMode[2] = r; +} + +void Texture::setFilterMode(CUfilter_mode filter, CUfilter_mode filterMipmap) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.filterMode = filter; + m_textureDescription.mipmapFilterMode = filterMipmap; +} + +void Texture::setBorderColor(float r, float g, float b, float a) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.borderColor[0] = r; + m_textureDescription.borderColor[1] = g; + m_textureDescription.borderColor[2] = b; + m_textureDescription.borderColor[3] = a; +} + +void Texture::setMaxAnisotropy(unsigned int aniso) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.maxAnisotropy = aniso; +} + +void Texture::setMipmapLevelBiasMinMax(float bias, float minimum, float maximum) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.mipmapLevelBias = bias; + m_textureDescription.minMipmapLevelClamp = minimum; + m_textureDescription.maxMipmapLevelClamp = maximum; +} + +void Texture::setReadMode(bool asInteger) +{ + MY_ASSERT(m_textureObject == 0); + + if (asInteger) + { + m_textureDescription.flags |= CU_TRSF_READ_AS_INTEGER; + } + else + { + m_textureDescription.flags &= ~CU_TRSF_READ_AS_INTEGER; + } +} + +void Texture::setSRGB(bool srgb) +{ + MY_ASSERT(m_textureObject == 0); + + if (srgb) + { + m_textureDescription.flags |= CU_TRSF_SRGB; + } + else + { + m_textureDescription.flags &= ~CU_TRSF_SRGB; + } +} + +void Texture::setNormalizedCoords(bool normalized) +{ + MY_ASSERT(m_textureObject == 0); + + if (normalized) + { + m_textureDescription.flags |= CU_TRSF_NORMALIZED_COORDINATES; // Default in this app. + } + else + { + // Note that if the flag CU_TRSF_NORMALIZED_COORDINATES is not set, + // the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP! + m_textureDescription.flags &= ~CU_TRSF_NORMALIZED_COORDINATES; + } +} + + +bool Texture::create1D(const Picture* picture) +{ + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = 0; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Depth = m_depth; // Mind that the layers are always defined via the depth extent. + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + // I don't know of a program which creates image files with 1D layered mipmapped textures, but the Picture class handles that just fine. + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 1D (layered) mipmapped texture. + { + // A 1D mipmapped array is allocated if Height and Depth extents are both zero. + // A 1D layered CUDA mipmapped array is allocated if only Height is zero and the CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 1D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 1D (layered) texture. + { + // A 1D array is allocated if the height and depth extents are both zero. + // A 1D layered CUDA array is allocated if only the height extent is zero and the cudaArrayLayered flag is set. + // Each layer is a 1D array. The number of layers is determined by the depth extent. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +bool Texture::create2D(const Picture* picture) +{ + // Default initialization for a 2D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Depth = m_depth; // Mind that the layers are always defined via the depth extent. + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + // We reuse the data array for the CDF calculation below. Make sure it's big enough to hold either format. + if (m_flags & (IMAGE_FLAG_ENV | IMAGE_FLAG_RECT)) + { + sizeBytes = std::max(sizeBytes, m_width * m_height * 4 * sizeof(float)); // CDF only considers LOD 0 layer 0. + } + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 2D (layered) mipmapped texture // FIXME Add a mechanism to generate mipmaps if there are none. + { + // A 2D mipmapped array is allocated if only Depth extent is zero. + // A 2D layered CUDA mipmapped array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 2D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 2D (layered) texture. + { + // A 2D array is allocated if only Depth extent is zero. + // A 2D layered CUDA array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 2D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + if (m_flags & (IMAGE_FLAG_ENV | IMAGE_FLAG_RECT)) + { + // Spherical environment textures are a special case of 2D textures. + // While the above texture setup can be any native CUDA destination format specified in m_encodingDevice, + // the CDF calculation expects float4 data. + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height; // Layer 0 only + + // Hardcode the conversion to a float4 format for the CDF calculation. + // The input can be any format! Fixed point textures (non-float) will always be normalized. + const unsigned int encodingDevice = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE | ENC_TYPE_FLOAT; + + convert(data, encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + if (m_flags & IMAGE_FLAG_ENV) + { + // Generate the CDFs for direct environment lighting and the environment texture sampler itself. + calculateSphericalCDF(reinterpret_cast(data)); + + // Setup CUDA_TEXTURE_DESC for the spherical environment. + // The defaults are set for a bilinear filtered 2D texture already. + // The spherical environment texture only needs to change the addessmode[1] to clamp. + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + } + else // if (m_flags & IMAGE_FLAG_RECT) + { + // Generate the CDFs for the rectangle texture sampler itself. + calculateRectangularCDF(reinterpret_cast(data)); + + // The rectangle emission texture cannot be tiled and should not filter across borders. + m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_CLAMP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + } + } + else if (m_flags & IMAGE_FLAG_POINT) + { + // The point light projection is spherical and must not filter accross poles. + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + } + else if (m_flags & IMAGE_FLAG_SPOT) + { + // The spot light is projected as spherical cap in the cone angle limits. Do not filter across borders on either side. + m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_CLAMP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + } + else if (m_flags & IMAGE_FLAG_IES) + { + // The IES light projection is spherical and must not filter accross poles. + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::create3D(const Picture* picture) +{ + MY_ASSERT((m_flags & IMAGE_FLAG_LAYER) == 0); // There are no layered 3D textures. The flag is ignored. + + // Default initialization for a 3D texture. There are no layers! + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = m_depth; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 3D mipmapped texture + { + // A 3D mipmapped array is allocated if all three extents are non-zero. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 3D texture. + { + // A 3D array is allocated if all three extents are non-zero. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_height * m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + // SDF 3D texture must clamp their outer values. + if (m_flags & IMAGE_FLAG_SDF) + { + m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_CLAMP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_CLAMP; + } + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::createCube(const Picture* picture) +{ + if (!picture->isCubemap()) // This implies picture->getNumberOfImages() == 6. + { + std::cerr << "ERROR: Texture::createCube() picture is not a cubemap.\n"; + return false; + } + + if (m_width != m_height || m_depth % 6 != 0) + { + std::cerr << "ERROR: Texture::createCube() invalid cubemap image dimensions (" << m_width << ", " << m_height << ", " << m_depth << ")\n"; + return false; + } + + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = m_depth; // depth == 6 * layers. + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = CUDA_ARRAY3D_CUBEMAP; + + const unsigned int numLayers = m_depth / 6; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags |= CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; // LOD 0 size in bytes. + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // cubemap (layered) mipmapped texture + { + // A cubemap CUDA mipmapped array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_CUBEMAP flag is set. + // Width must be equal to \p Height, and Depth must be six. + // A cubemap is a special type of 2D layered CUDA array, where the six layers represent the six faces of a cube. + // The order of the six layers in memory is the same as that listed in ::CUarray_cubemap_face. (+x, -x, +y, -y, +z, -z) + // A cubemap layered CUDA mipmapped array is allocated if all three extents are non-zero, and both, ::CUDA_ARRAY3D_CUBEMAP and ::CUDA_ARRAY3D_LAYERED flags are set. + // Width must be equal to Height, and Depth must be a multiple of six. + // A cubemap layered CUDA array is a special type of 2D layered CUDA array that consists of a collection of cubemaps. + // The first six layers represent the first cubemap, the next six layers form the second cubemap, and so on. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + const Image* image; // The last image of each level defines the extent. + + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + for (unsigned int face = 0; face < 6; ++face) + { + image = picture->getImageLevel(face, level); // image face, LOD level + + const size_t sizeElementsLayer = image->m_width * image->m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytesLayer * numLayers; // Memory tracking. + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // cubemap (layered) texture. + { + // A cubemap CUDA array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_CUBEMAP flag is set. + // Width must be equal to Height, and Depth must be six. + // A cubemap is a special type of 2D layered CUDA array, where the six layers represent the six faces of a cube. + // The order of the six layers in memory is the same as that listed in ::CUarray_cubemap_face. (+x, -x, +y, -y, +z, -z) + // A cubemap layered CUDA array is allocated if all three extents are non-zero, and both, ::CUDA_ARRAY3D_CUBEMAP and ::CUDA_ARRAY3D_LAYERED flags are set. + // Width must be equal to Height, and Depth must be a multiple of six. + // A cubemap layered CUDA array is a special type of 2D layered CUDA array that consists of a collection of cubemaps. + // The first six layers represent the first cubemap, the next six layers form the second cubemap, and so on. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + for (unsigned int face = 0; face < 6; ++face) + { + const Image* image = picture->getImageLevel(face, 0); // image face, LOD 0 + + const size_t sizeElementsLayer = m_width * m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytesLayer * numLayers; // Memory tracking. + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +// The Texture::create() functions set up all immutable members. +// The Texture::update() functions expect the exact same input and only upload new CUDA array data. +bool Texture::create(const Picture* picture, const unsigned int flags) +{ + bool success = false; + + if (m_textureObject != 0) + { + std::cerr << "ERROR: Texture::create() texture object already created.\n"; + return success; + } + + if (picture == nullptr) + { + std::cerr << "ERROR: Texture::create() called with nullptr picture.\n"; + return success; + } + + // The LOD 0 image of the first face defines the basic settings, including the texture m_width, m_height, m_depth. + // This returns nullptr when this image doesn't exist. Everything else in this function relies on it. + const Image* image = picture->getImageLevel(0, 0); + + if (image == nullptr) + { + std::cerr << "ERROR: Texture::create() Picture doesn't contain image 0 level 0.\n"; + return success; + } + + m_flags = flags; + + // Precalculate some values which are required for all create*() functions. + m_encodingHost = determineHostEncoding(image->m_format, image->m_type); + m_encodingDevice = determineDeviceEncoding(image->m_format, image->m_type); + + if ((m_encodingHost | m_encodingDevice) & ENC_INVALID) // If either of the encodings is invalid, bail out. + { + return false; + } + + m_sizeBytesPerElement = getElementSize(m_encodingDevice); + + if (m_flags & IMAGE_FLAG_1D) + { + m_width = image->m_width; + m_height = 1; + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth : 1; + success = create1D(picture); + } + else if (m_flags & IMAGE_FLAG_2D) + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth : 1; + success = create2D(picture); // Also handles CDF calculation for IMAGE_FLAG_ENV and IMAGE_FLAG_RECT! + } + else if (m_flags & IMAGE_FLAG_3D) + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = image->m_depth; + success = create3D(picture); // Also handles SDF 3D textures which need special wrap settings. + } + else if (m_flags & IMAGE_FLAG_CUBE) + { + m_width = image->m_width; + m_height = image->m_height; + // Note that the Picture class holds the six cubemap faces in six separate mipmap chains! + // The LOD 0 image depth is the number of layers. The resulting 3D image is six times that depth. + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth * 6 : 6; + success = createCube(picture); + } + + MY_ASSERT(success); + return success; +} + + +// Texture peer-to-peer sharing! +// Note that the m_owner field indicates which device originally allocated the memory! +// Use all other data of the shared texture on the peer device. Only the m_textureObject is per device! +bool Texture::create(const Texture* shared) +{ + // Copy all elements from the existing shared texture. + // This includes the owner which is the peer device, not the currently active one! + *this = *shared; + + m_textureObject = 0; // Clear the copied texture object. + + // Only create the m_textureObject per Device by using the resource and texture descriptions + // just copied from the shared texture which contains the device pointers to the shared array data. + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +Device* Texture::getOwner() const +{ + return m_owner; +} + +unsigned int Texture::getWidth() const +{ + return m_width; +} + +unsigned int Texture::getHeight() const +{ + return m_height; +} + +unsigned int Texture::getDepth() const +{ + return m_depth; +} + +cudaTextureObject_t Texture::getTextureObject() const +{ + return m_textureObject; +} + +size_t Texture::getSizeBytes() const +{ + return m_sizeBytesArray; +} + +// The following functions are used to build the data needed for an importance sampled spherical HDR environment map. + +// Implement a simple Gaussian 3x3 filter with sigma = 0.5 +// Needed for the CDF generation of the importance sampled HDR environment texture light. +static float gaussianFilter(const float* rgba, unsigned int width, unsigned int height, unsigned int x, unsigned int y, bool isSpherical) +{ + + // Lookup is repeated in x and clamped to edge in y. + unsigned int left; + unsigned int right; + unsigned int bottom = (0 < y) ? y - 1 : y; // clamp + unsigned int top = (y < height - 1) ? y + 1 : y; // clamp + + // Match the filter to the texture object wrap setup for spherical and rectangular emission textures. + if (isSpherical) // Spherical environment light + { + left = (0 < x) ? x - 1 : width - 1; // repeat + right = (x < width - 1) ? x + 1 : 0; // repeat + } + else // Rectangular area light + { + left = (0 < x) ? x - 1 : x; // clamp + right = (x < width - 1) ? x + 1 : x; // clamp + } + + + // Center + const float *p = rgba + (width * y + x) * 4; + float intensity = (p[0] + p[1] + p[2]) * 0.619347f; + + // 4-neighbours + p = rgba + (width * bottom + x) * 4; + float f = p[0] + p[1] + p[2]; + p = rgba + (width * y + left) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * y + right) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + x) * 4; + f += p[0] + p[1] + p[2]; + intensity += f * 0.0838195f; + + // 8-neighbours corners + p = rgba + (width * bottom + left) * 4; + f = p[0] + p[1] + p[2]; + p = rgba + (width * bottom + right) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + left) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + right) * 4; + f += p[0] + p[1] + p[2]; + intensity += f * 0.0113437f; + + return intensity / 3.0f; +} + +// Create cumulative distribution function for importance sampling of spherical environment lights. +// This is a textbook implementation for the CDF generation of a spherical HDR environment. +// See "Physically Based Rendering" v2, chapter 14.6.5 on Infinite Area Lights. +// PERF Implement this with CUDA kernels. +void Texture::calculateSphericalCDF(const float* rgba) +{ + // The original data needs to be retained. The Gaussian filter does not work in place. + float *funcU = new float[m_width * m_height]; + float *funcV = new float[m_height + 1]; + + float sum = 0.0f; + // First generate the function data. + for (unsigned int y = 0; y < m_height; ++y) + { + // Scale distribution by the sine to get the sampling uniform. (Avoid sampling more values near the poles.) + // See Physically Based Rendering v2, chapter 14.6.5 on Infinite Area Lights, page 728. + float sinTheta = float(sin(M_PI * (double(y) + 0.5) / double(m_height))); // Make this as accurate as possible. + + for (unsigned int x = 0; x < m_width; ++x) + { + // Filter to keep the piecewise linear function intact for samples with zero value next to non-zero values. + const float value = gaussianFilter(rgba, m_width, m_height, x, y, true); + funcU[y * m_width + x] = value * sinTheta; + + // Compute integral over the actual function. + const float *p = rgba + (y * m_width + x) * 4; + const float intensity = (p[0] + p[1] + p[2]) / 3.0f; + sum += intensity * sinTheta; + } + } + + // This integral is used inside the light sampling function (see sysData.envIntegral). + m_integral = sum * 2.0f * M_PIf * M_PIf / float(m_width * m_height); + + // Now generate the CDF data. + // Normalized 1D distributions in the rows of the 2D buffer, and the marginal CDF in the 1D buffer. + // Include the starting 0.0f and the ending 1.0f to avoid special cases during the continuous sampling. + float *cdfU = new float[(m_width + 1) * m_height]; + float *cdfV = new float[m_height + 1]; + + for (unsigned int y = 0; y < m_height; ++y) + { + unsigned int row = y * (m_width + 1); // Watch the stride! + cdfU[row + 0] = 0.0f; // CDF starts at 0.0f. + + for (unsigned int x = 1; x <= m_width; ++x) + { + unsigned int i = row + x; + cdfU[i] = cdfU[i - 1] + funcU[y * m_width + x - 1]; // Attention, funcU is only m_width wide! + } + + const float integral = cdfU[row + m_width]; // The integral over this row is in the last element. + funcV[y] = integral; // Store this as function values of the marginal CDF. + + if (integral != 0.0f) + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] /= integral; + } + } + else // All texels were black in this row. Generate an equal distribution. + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] = float(x) / float(m_width); + } + } + } + + // Now do the same thing with the marginal CDF. + cdfV[0] = 0.0f; // CDF starts at 0.0f. + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = cdfV[y - 1] + funcV[y - 1]; + } + + const float integral = cdfV[m_height]; // The integral over this marginal CDF is in the last element. + funcV[m_height] = integral; // For completeness, actually unused. + + if (integral != 0.0f) + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] /= integral; + } + } + else // All texels were black in the whole image. Seriously? :-) Generate an equal distribution. + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = float(y) / float(m_height); + } + } + + // Upload the CDFs into CUDA buffers. + size_t sizeBytes = (m_width + 1) * m_height * sizeof(float); + m_d_cdfU = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_cdfU, cdfU, sizeBytes) ); + + sizeBytes = (m_height + 1) * sizeof(float); + m_d_cdfV = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_cdfV, cdfV, sizeBytes) ); + + delete[] cdfV; + delete[] cdfU; + + delete[] funcV; + delete[] funcU; +} + + +// Create cumulative distribution function for importance sampling of a rectangular textured lights. +// PERF Implement this with CUDA kernels. +void Texture::calculateRectangularCDF(const float* rgba) +{ + // The original data needs to be retained. The Gaussian filer does not work in place. + float *funcU = new float[m_width * m_height]; + float *funcV = new float[m_height + 1]; + + float sum = 0.0f; + // First generate the function data. + for (unsigned int y = 0; y < m_height; ++y) + { + for (unsigned int x = 0; x < m_width; ++x) + { + // Filter to keep the piecewise linear function intact for samples with zero value next to non-zero values. + const float value = gaussianFilter(rgba, m_width, m_height, x, y, false); + funcU[y * m_width + x] = value; + + // Compute integral over the actual function. + const float *p = rgba + (y * m_width + x) * 4; + const float intensity = (p[0] + p[1] + p[2]) / 3.0f; + sum += intensity; + } + } + + // This integral is used inside the light sampling function (see sysData.envIntegral). + m_integral = sum / float(m_width * m_height); + + // Now generate the CDF data. + // Normalized 1D distributions in the rows of the 2D buffer, and the marginal CDF in the 1D buffer. + // Include the starting 0.0f and the ending 1.0f to avoid special cases during the continuous sampling. + float *cdfU = new float[(m_width + 1) * m_height]; + float *cdfV = new float[m_height + 1]; + + for (unsigned int y = 0; y < m_height; ++y) + { + unsigned int row = y * (m_width + 1); // Watch the stride! + cdfU[row + 0] = 0.0f; // CDF starts at 0.0f. + + for (unsigned int x = 1; x <= m_width; ++x) + { + unsigned int i = row + x; + cdfU[i] = cdfU[i - 1] + funcU[y * m_width + x - 1]; // Attention, funcU is only m_width wide! + } + + const float integral = cdfU[row + m_width]; // The integral over this row is in the last element. + funcV[y] = integral; // Store this as function values of the marginal CDF. + + if (integral != 0.0f) + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] /= integral; + } + } + else // All texels were black in this row. Generate an equal distribution. + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] = float(x) / float(m_width); + } + } + } + + // Now do the same thing with the marginal CDF. + cdfV[0] = 0.0f; // CDF starts at 0.0f. + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = cdfV[y - 1] + funcV[y - 1]; + } + + const float integral = cdfV[m_height]; // The integral over this marginal CDF is in the last element. + funcV[m_height] = integral; // For completeness, actually unused. + + if (integral != 0.0f) + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] /= integral; + } + } + else // All texels were black in the whole image. Seriously? :-) Generate an equal distribution. + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = float(y) / float(m_height); + } + } + + // Upload the CDFs into CUDA buffers. + size_t sizeBytes = (m_width + 1) * m_height * sizeof(float); + m_d_cdfU = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_cdfU, cdfU, sizeBytes) ); + + sizeBytes = (m_height + 1) * sizeof(float); + m_d_cdfV = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_cdfV, cdfV, sizeBytes) ); + + delete[] cdfV; + delete[] cdfU; + + delete[] funcV; + delete[] funcU; +} + + + +CUdeviceptr Texture::getCDF_U() const +{ + return m_d_cdfU; +} + +CUdeviceptr Texture::getCDF_V() const +{ + return m_d_cdfV; +} + +float Texture::getIntegral() const +{ + // This is the sum of the piecewise linear function values (roughly the texels' intensity) divided by the number of texels m_width * m_height. + return m_integral; +} + + +bool Texture::update1D(const Picture* picture) +{ + size_t sizeElements = m_width; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 1D (layered) mipmapped texture. + { + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 1D (layered) texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + + +bool Texture::update2D(const Picture* picture) +{ + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + // We reuse the data array for the CDF calculation below. Make sure it's big enough to hold either format. + if (m_flags & (IMAGE_FLAG_ENV | IMAGE_FLAG_RECT)) + { + sizeBytes = std::max(sizeBytes, m_width * m_height * 4 * sizeof(float)); + } + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 2D (layered) mipmapped texture // FIXME Add a mechanism to generate mipmaps if there are none. + { + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 2D (layered) texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + // If this texture is uses as emission for the environment or a rectangle light, calculate the CDF for the importance-sampling. + if (m_flags & (IMAGE_FLAG_ENV | IMAGE_FLAG_RECT)) + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height; // Layer 0 only + + // Hardcode the conversion to a float4 format for the CDF calculation. + const unsigned int encodingDevice = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE | ENC_TYPE_FLOAT; + + convert(data, encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + // There is only one CDF, means a texture cannot be used for both env and rect lights. + if (m_flags & IMAGE_FLAG_ENV) + { + calculateSphericalCDF(reinterpret_cast(data)); + } + else // if (m_flags & IMAGE_FLAG_RECT) + { + calculateRectangularCDF(reinterpret_cast(data)); + } + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::update3D(const Picture* picture) +{ + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 3D mipmapped texture + { + // A 3D mipmapped array is allocated if all three extents are non-zero. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 3D texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_height * m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::updateCube(const Picture* picture) +{ + if (!picture->isCubemap()) // isCubemap() implies picture->getNumberOfImages() == 6. + { + std::cerr << "ERROR: Texture::updateCube() picture is not a cubemap.\n"; + return false; + } + + if (m_width != m_height || m_depth % 6 != 0) + { + std::cerr << "ERROR: Texture::updateCube() invalid cubemap image dimensions (" << m_width << ", " << m_height << ", " << m_depth << ")\n"; + return false; + } + + const unsigned int numLayers = m_depth / 6; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags |= CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; // LOD 0 size in bytes. + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // cubemap (layered) mipmapped texture + { + for (unsigned int level = 0; level < numLevels; ++level) + { + const Image* image; // The last image of each level defines the extent. + + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + for (unsigned int face = 0; face < 6; ++face) + { + image = picture->getImageLevel(face, level); // image face, LOD level + + const size_t sizeElementsLayer = image->m_width * image->m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // cubemap (layered) texture. + { + for (unsigned int face = 0; face < 6; ++face) + { + const Image* image = picture->getImageLevel(face, 0); // image face, LOD 0 + + const size_t sizeElementsLayer = m_width * m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + + +bool Texture::update(const Picture* picture) +{ + bool success = false; + + if (m_textureObject == 0) + { + std::cerr << "ERROR: Texture::update() texture object not reated.\n"; + return success; + } + + if (picture == nullptr) + { + std::cerr << "ERROR: Texture::update() called with nullptr picture.\n"; + return success; + } + + // The LOD 0 image of the first face defines the basic settings, including the texture m_width, m_height, m_depth. + // This returns nullptr when this image doesn't exist. Everything else in this function relies on it. + const Image* image = picture->getImageLevel(0, 0); + + if (image == nullptr) + { + std::cerr << "ERROR: Texture::update() Picture doesn't contain image 0 level 0.\n"; + return success; + } + + const unsigned int hostEncoding = determineHostEncoding(image->m_format, image->m_type); + + if (m_encodingHost != hostEncoding) + { + std::cerr << "ERROR: Texture::update() Picture host encoding doesn't match existing texture\n"; + return success; + } + + if (m_flags & IMAGE_FLAG_1D) + { + success = update1D(picture); + } + else if (m_flags & IMAGE_FLAG_2D) + { + success = update2D(picture); // Also handles CDF update for IMAGE_FLAG_ENV and IMAGE_FLAG_RECT! + } + else if (m_flags & IMAGE_FLAG_3D) + { + success = update3D(picture); + } + else if (m_flags & IMAGE_FLAG_CUBE) + { + success = updateCube(picture); + } + + MY_ASSERT(success); + return success; +} diff --git a/apps/MDL_sdf/src/Timer.cpp b/apps/MDL_sdf/src/Timer.cpp new file mode 100644 index 00000000..2a2c0451 --- /dev/null +++ b/apps/MDL_sdf/src/Timer.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2011-2019, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +// This code is part of the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + +#include "inc/Timer.h" + +#if defined(_WIN32) +# define GETTIME(x) QueryPerformanceCounter(x) +#else +# define GETTIME(x) gettimeofday( x, 0 ) +#endif + +Timer::Timer() + : m_running(false) + , m_seconds(0) +{ +#if defined(_WIN32) + QueryPerformanceFrequency(&m_freq); +#endif +} + +Timer::~Timer() +{ +} + +void Timer::start() +{ + if( !m_running ) + { + m_running = true; + // starting a timer: store starting time last + GETTIME( &m_begin ); + } +} + +void Timer::stop() +{ + // stopping a timer: store stopping time first + Time tmp; + GETTIME( &tmp ); + if( m_running ) + { + m_seconds += calcDuration(m_begin, tmp); + m_running = false; + } +} + +void Timer::reset() +{ + m_running = false; + m_seconds = 0; +} + +void Timer::restart() +{ + reset(); + start(); +} + +double Timer::getTime() const +{ + Time tmp; + GETTIME( &tmp ); + if( m_running ) + { + return m_seconds + calcDuration(m_begin, tmp); + } + else + { + return m_seconds; + } +} + +double Timer::calcDuration(Time begin, Time end) const +{ + double seconds; +#if defined(_WIN32) + LARGE_INTEGER diff; + diff.QuadPart = (end.QuadPart - begin.QuadPart); + seconds = (double)diff.QuadPart / (double)m_freq.QuadPart; +#else + timeval diff; + if( begin.tv_usec <= end.tv_usec ) + { + diff.tv_sec = end.tv_sec - begin.tv_sec; + diff.tv_usec = end.tv_usec - begin.tv_usec; + } + else + { + diff.tv_sec = end.tv_sec - begin.tv_sec - 1; + diff.tv_usec = end.tv_usec - begin.tv_usec + (int)1e6; + } + seconds = diff.tv_sec + diff.tv_usec/1e6; +#endif + return seconds; +} diff --git a/apps/MDL_sdf/src/Torus.cpp b/apps/MDL_sdf/src/Torus.cpp new file mode 100644 index 00000000..916608fd --- /dev/null +++ b/apps/MDL_sdf/src/Torus.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // The torus is a ring with radius outerRadius rotated around the y-axis along the circle with innerRadius. + /* +y + ___ | ___ + / \ / \ + | | | | | + | | | | + \ ___ / | \ ___ / + <---> + outerRadius + <-------> + innerRadius + */ + void Triangles::createTorus(const unsigned int tessU, const unsigned int tessV, const float innerRadius, const float outerRadius) + { + MY_ASSERT(3 <= tessU && 3 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + m_attributes.reserve((tessU + 1) * (tessV + 1)); + m_indices.reserve(8 * tessU * tessV); + + const float u = (float) tessU; + const float v = (float) tessV; + + float phi_step = 2.0f * M_PIf / u; + float theta_step = 2.0f * M_PIf / v; + + // Setup vertices and normals. + // Generate the torus exactly like the sphere with rings around the origin along the latitudes. + for (unsigned int latitude = 0; latitude <= tessV; ++latitude) // theta angle + { + const float theta = (float) latitude * theta_step; + const float sinTheta = sinf(theta); + const float cosTheta = cosf(theta); + + const float radius = innerRadius + outerRadius * cosTheta; + + for (unsigned int longitude = 0; longitude <= tessU; ++longitude) // phi angle + { + const float phi = (float) longitude * phi_step; + const float sinPhi = sinf(phi); + const float cosPhi = cosf(phi); + + TriangleAttributes attrib; + + attrib.vertex = make_float3(radius * cosPhi, outerRadius * sinTheta, radius * -sinPhi); + attrib.tangent = make_float3(-sinPhi, 0.0f, -cosPhi); + attrib.normal = make_float3(cosPhi * cosTheta, sinTheta, -sinPhi * cosTheta); + attrib.texcoord = make_float3((float) longitude / u, (float) latitude / v, 0.0f); + + m_attributes.push_back(attrib); + } + } + + // We have generated tessU + 1 vertices per latitude. + const int columns = tessU + 1; + + // Setup m_indices + for (unsigned int latitude = 0; latitude < tessV; ++latitude) + { + for (unsigned int longitude = 0; longitude < tessU; ++longitude) + { + m_indices.push_back(latitude * columns + longitude); // lower left + m_indices.push_back(latitude * columns + longitude + 1); // lower right + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + m_indices.push_back((latitude + 1) * columns + longitude); // upper left + m_indices.push_back(latitude * columns + longitude); // lower left + } + } + } + +} // namespace sg diff --git a/apps/MDL_sdf/src/main.cpp b/apps/MDL_sdf/src/main.cpp new file mode 100644 index 00000000..589eb141 --- /dev/null +++ b/apps/MDL_sdf/src/main.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2013-2023, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Application.h" + +#include + +#include +#include + + +static Application* g_app = nullptr; + +static void callbackError(int error, const char* description) +{ + std::cerr << "ERROR: "<< error << ": " << description << '\n'; +} + +static int runApp(const Options& options) +{ + int width = std::max(1, options.getWidth()); + int height = std::max(1, options.getHeight()); + + GLFWwindow* window = glfwCreateWindow(width, height, "MDL_sdf - Copyright (c) 2023 NVIDIA Corporation", NULL, NULL); + if (!window) + { + callbackError(APP_ERROR_CREATE_WINDOW, "glfwCreateWindow() failed."); + return APP_ERROR_CREATE_WINDOW; + } + + glfwMakeContextCurrent(window); + + if (glewInit() != GL_NO_ERROR) + { + callbackError(APP_ERROR_GLEW_INIT, "GLEW failed to initialize."); + return APP_ERROR_GLEW_INIT; + } + + ilInit(); // Initialize DevIL once. + + g_app = new Application(window, options); + + if (!g_app->isValid()) + { + std::cerr << "ERROR: Application() failed to initialize successfully.\n"; + ilShutDown(); + return APP_ERROR_APP_INIT; + } + + const int mode = std::max(0, options.getMode()); + + if (mode == 0) // Interactive mode, default. + { + // Main loop + bool finish = false; + while (!finish && !glfwWindowShouldClose(window)) + { + glfwPollEvents(); // Render continuously. Battery drainer! + + glfwGetFramebufferSize(window, &width, &height); + + g_app->reshape(width, height); + g_app->guiNewFrame(); + //g_app->guiReferenceManual(); // HACK The ImGUI "Programming Manual" as example code. + g_app->guiWindow(); // This application's GUI window rendering commands. + g_app->guiEventHandler(); // SPACE to toggle the GUI windows and all mouse tracking via GuiState. + finish = g_app->render(); // OptiX rendering, returns true when benchmark is enabled and the samples per pixel have been rendered. + g_app->display(); // OpenGL display always required to lay the background for the GUI. + g_app->guiRender(); // Render all ImGUI elements at last. + + glfwSwapBuffers(window); + + //glfwWaitEvents(); // Render only when an event is happening. Needs some glfwPostEmptyEvent() to prevent GUI lagging one frame behind when ending an action. + } + } + else if (mode == 1) // Batched benchmark, single shot. + { + g_app->benchmark(); + } + + delete g_app; + + ilShutDown(); + + return APP_EXIT_SUCCESS; +} + + +int main(int argc, char *argv[]) +{ + glfwSetErrorCallback(callbackError); + + if (!glfwInit()) + { + callbackError(APP_ERROR_GLFW_INIT, "GLFW failed to initialize."); + return APP_ERROR_GLFW_INIT; + } + + int result = APP_ERROR_UNKNOWN; + + Options options; + + if (options.parseCommandLine(argc, argv)) + { + result = runApp(options); + } + + glfwTerminate(); + + return result; +} diff --git a/apps/bench_shared/CMakeLists.txt b/apps/bench_shared/CMakeLists.txt new file mode 100644 index 00000000..ed87a4f3 --- /dev/null +++ b/apps/bench_shared/CMakeLists.txt @@ -0,0 +1,273 @@ +# Copyright (c) 2013-2022, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +# FindCUDA.cmake is deprecated since CMake 3.10. +# Use FindCUDAToolkit.cmake added in CMake 3.17 instead. +cmake_minimum_required(VERSION 3.17) + +project( bench_shared ) +message("\nPROJECT_NAME = " "${PROJECT_NAME}") + +find_package(OpenGL REQUIRED) +find_package(GLFW REQUIRED) +find_package(GLEW REQUIRED) +find_package(CUDAToolkit 10.0 REQUIRED) +find_package(DevIL_1_8_0 REQUIRED) +find_package(ASSIMP REQUIRED) + +# OptiX SDK 7.x and 8.x versions are searched inside the top-level CMakeLists.txt. +# Make the build work with all currently released OptiX SDK 7.x and 8.x versions. +if(OptiX80_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX80_INCLUDE_DIR}") +elseif(OptiX77_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX77_INCLUDE_DIR}") +elseif(OptiX76_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX76_INCLUDE_DIR}") +elseif(OptiX75_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX75_INCLUDE_DIR}") +elseif(OptiX74_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX74_INCLUDE_DIR}") +elseif(OptiX73_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX73_INCLUDE_DIR}") +elseif(OptiX72_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX72_INCLUDE_DIR}") +elseif(OptiX71_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX71_INCLUDE_DIR}") +elseif(OptiX70_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX70_INCLUDE_DIR}") +else() + message(FATAL_ERROR "No OptiX SDK 7.x found.") +endif() +#message("OPTIX_INCLUDE_DIR = " "${OPTIX_INCLUDE_DIR}") + +# OptiX SDK 7.5.0 and CUDA 11.7 added support for a new OptiX IR target, which is a binary intermediate format for the module input. +# The default module build target is PTX. +set(USE_OPTIX_IR FALSE) +set(OPTIX_MODULE_EXTENSION ".ptx") +set(OPTIX_PROGRAM_TARGET "--ptx") + +if (OptiX80_FOUND OR OptiX77_FOUND OR OptiX76_FOUND OR OptiX75_FOUND) + # Define USE_OPTIX_IR and change the target to OptiX IR if the combination of OptiX SDK and CUDA Toolkit versions supports this mode. + if ((${CUDAToolkit_VERSION_MAJOR} GREATER 11) OR ((${CUDAToolkit_VERSION_MAJOR} EQUAL 11) AND (${CUDAToolkit_VERSION_MINOR} GREATER_EQUAL 7))) + set(USE_OPTIX_IR TRUE) + set(OPTIX_MODULE_EXTENSION ".optixir") + set(OPTIX_PROGRAM_TARGET "--optix-ir") + endif() +endif() + +set( IMGUI + imgui/imconfig.h + imgui/imgui.h + imgui/imgui_impl_glfw_gl3.h + imgui/imgui_internal.h + imgui/stb_rect_pack.h + imgui/stb_textedit.h + imgui/stb_truetype.h + imgui/imgui.cpp + imgui/imgui_demo.cpp + imgui/imgui_draw.cpp + imgui/imgui_impl_glfw_gl3.cpp +) + +# Reusing some routines from the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline +# Not built as a library, just using classes and functions directly. +# Asserts replaced with my own versions. +set( NVPRO_MATH + # Math functions: + dp/math/Config.h + dp/math/math.h + dp/math/Matmnt.h + dp/math/Quatt.h + dp/math/Trafo.h + dp/math/Vecnt.h + dp/math/src/Math.cpp + dp/math/src/Matmnt.cpp + dp/math/src/Quatt.cpp + dp/math/src/Trafo.cpp +) + +set( HEADERS + inc/Application.h + inc/Arena.h + inc/Camera.h + inc/CheckMacros.h + inc/Device.h + inc/MaterialGUI.h + inc/MyAssert.h + inc/NVMLImpl.h + inc/Options.h + inc/Parser.h + inc/Picture.h + inc/Rasterizer.h + inc/Raytracer.h + inc/SceneGraph.h + inc/Texture.h + inc/Timer.h + inc/TonemapperGUI.h +) + +set( SOURCES + src/Application.cpp + src/Arena.cpp + src/Assimp.cpp + src/Box.cpp + src/Camera.cpp + src/Device.cpp + src/main.cpp + src/NVMLImpl.cpp + src/Options.cpp + src/Parallelogram.cpp + src/Parser.cpp + src/Picture.cpp + src/Plane.cpp + src/Rasterizer.cpp + src/Raytracer.cpp + src/SceneGraph.cpp + src/Sphere.cpp + src/Texture.cpp + src/Timer.cpp + src/Torus.cpp +) + +# Prefix the shaders with the full path name to allow stepping through errors with F8. +set( SHADERS + # Core shaders. + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/anyhit.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/closesthit.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/exception.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/miss.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/raygeneration.cu + + # Direct callables + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/lens_shader.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/light_sample.cu + # BxDFs (BRDF, BTDF, BSDF implementations) + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bxdf_diffuse.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bxdf_ggx_smith.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bxdf_specular.cu +) + +set( KERNELS + # Native CUDA kernels + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/compositor.cu +) + +set( SHADERS_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/camera_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/compositor_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/config.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/function_indices.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/light_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/material_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/per_ray_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/random_number_generators.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shader_common.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/system_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vector_math.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vertex_attributes.h +) + +# When using OptiX SDK 7.5.0 and CUDA 11.7 or higher, the modules can either be built from OptiX IR input or from PTX input. +# OPTIX_PROGRAM_TARGET and OPTIX_MODULE_EXTENSION switch the NVCC compilation between the two options. +NVCUDA_COMPILE_MODULE( + SOURCES ${SHADERS} + DEPENDENCIES ${SHADERS_HEADERS} + TARGET_PATH "${MODULE_TARGET_DIR}/bench_shared_core" + EXTENSION "${OPTIX_MODULE_EXTENSION}" + GENERATED_FILES PROGRAM_MODULES + NVCC_OPTIONS "${OPTIX_PROGRAM_TARGET}" "--machine=64" "--gpu-architecture=compute_50" "--use_fast_math" "--relocatable-device-code=true" "--generate-line-info" "-Wno-deprecated-gpu-targets" "--allow-unsupported-compiler" "-I${OPTIX_INCLUDE_DIR}" "-I${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +# The native CUDA Kernels will be translated to *.ptx unconditionally. +NVCUDA_COMPILE_MODULE( + SOURCES ${KERNELS} + DEPENDENCIES ${SHADERS_HEADERS} + TARGET_PATH "${MODULE_TARGET_DIR}/bench_shared_core" + EXTENSION ".ptx" + GENERATED_FILES KERNEL_MODULES + NVCC_OPTIONS "--ptx" "--machine=64" "--gpu-architecture=compute_50" "--use_fast_math" "--relocatable-device-code=true" "--generate-line-info" "-Wno-deprecated-gpu-targets" "--allow-unsupported-compiler" "-I${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +source_group( "imgui" FILES ${IMGUI} ) +source_group( "nvpro_math" FILES ${NVPRO_MATH} ) +source_group( "headers" FILES ${HEADERS} ) +source_group( "sources" FILES ${SOURCES} ) +source_group( "shaders" FILES ${SHADERS} ) +source_group( "shaders_headers" FILES ${SHADERS_HEADERS} ) +source_group( "prg" FILES ${PROGRAM_MODULES} ) +source_group( "ptx" FILES ${KERNEL_MODULES} ) + +include_directories( + "." + "inc" + "imgui" + ${GLEW_INCLUDE_DIRS} + ${GLFW_INCLUDE_DIR} + ${OPTIX_INCLUDE_DIR} + ${CUDAToolkit_INCLUDE_DIRS} + ${IL_INCLUDE_DIR} + ${ASSIMP_INCLUDE_DIRS} +) + +add_definitions( + # Disable warnings for file operations fopen etc. + "-D_CRT_SECURE_NO_WARNINGS" +) + +if(USE_OPTIX_IR) +add_definitions( + # This define switches the OptiX program module filenames to either *.optixir or *.ptx extensions at compile time. + "-DUSE_OPTIX_IR" +) +endif() + +add_executable( bench_shared + ${IMGUI} + ${NVPRO_MATH} + ${HEADERS} + ${SOURCES} + ${SHADERS_HEADERS} + ${SHADERS} + ${PROGRAM_MODULES} + ${KERNEL_MODULES} +) + +target_link_libraries( bench_shared + OpenGL::GL + ${GLEW_LIBRARIES} + ${GLFW_LIBRARIES} + CUDA::cuda_driver + ${IL_LIBRARIES} + ${ILU_LIBRARIES} + ${ILUT_LIBRARIES} + ${ASSIMP_LIBRARIES} +) + +if (UNIX) + target_link_libraries( bench_shared dl ) +endif() + +set_target_properties( bench_shared PROPERTIES FOLDER "apps") diff --git a/apps/bench_shared/dp/math/Config.h b/apps/bench_shared/dp/math/Config.h new file mode 100644 index 00000000..55901c01 --- /dev/null +++ b/apps/bench_shared/dp/math/Config.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once + +// #include + +//#ifdef DP_OS_WINDOWS +//// microsoft specific storage-class defines +//# ifdef DP_MATH_EXPORTS +//# define DP_MATH_API __declspec(dllexport) +//# else +//# define DP_MATH_API __declspec(dllimport) +//# endif +//#else +// DAR No need for a library, just use the dp::math functions as inline code. +# define DP_MATH_API +//#endif diff --git a/apps/bench_shared/dp/math/Matmnt.h b/apps/bench_shared/dp/math/Matmnt.h new file mode 100644 index 00000000..902f0535 --- /dev/null +++ b/apps/bench_shared/dp/math/Matmnt.h @@ -0,0 +1,1434 @@ +// Copyright (c) 2009-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include + +namespace dp +{ + namespace math + { + + template class Quatt; + + /*! \brief Matrix class of fixed size and type. + * \remarks This class is templated by size and type. It holds \a m rows times \a n columns values of type \a + * T. There are typedefs for the most common usage with 3x3 and 4x4 values of type \c float and \c + * + * The layout in memory is is row-major. + * Vectors have to be multiplied from the left (result = v*M). + * The last row [12-14] contains the translation. + * + * double: Mat33f, Mat33d, Mat44f, Mat44d. */ + template class Matmnt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons, no initialization is performed. */ + Matmnt(); + + /*! \brief Copy constructor from a matrix of possibly different size and type. + * \param rhs A matrix with \a m times \a m values of type \a S. + * \remarks The minimum \a x of \a m and \a k, and the minimum \a y of \a n and \a l is determined. The + * first \a y values of type \a S in the first \a x rows from \a rhs are converted to type \a T and + * assigned as the first \a y values in the first \a x rows of \c this. If \a x is less than \a m, the + * last rows of \a this are not initialized. If \a y is less than \a n, the last values of the first \a + * x rows are not initialized. */ + template + explicit Matmnt( const Matmnt & rhs ); + + /*! \brief Constructor for a matrix by an array of m rows of type Vecnt. + * \param rows An array of m rows of type Vecnt + * \remarks This constructor can easily be called, using an initializer-list + * \code + * Mat33f m33f( { xAxis, yAxis, zAxis } ); + * \endcode */ + explicit Matmnt( const std::array,m> & rows ); + + /*! \brief Constructor for a matrix by an array of 2 rows of type Vecnt. + * \param v1,v2 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2 ); + + /*! \brief Constructor for a matrix by an array of 3 rows of type Vecnt. + * \param v1,v2,v3 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3 ); + + /*! \brief Constructor for a matrix by an array of 4 rows of type Vecnt. + * \param v1,v2,v3,v4 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3, Vecnt const& v4 ); + + + /*! \brief Constructor for a matrix by an array of m*n scalars of type T. + * \param scalars An array of m*n scalars of type T + * \remarks This constructor can easily be called, using an initializer-list + * \code + * Mat33f m33f( { m00, m01, m02, m10, m11, m12, m20, m21, m22 } ); + * \endcode */ + explicit Matmnt( const std::array & scalars ); + + /*! \brief Constructor for a 3 by 3 rotation matrix out of an axis and an angle. + * \param axis A reference to the constant axis to rotate about. + * \param angle The angle, in radians, to rotate. + * \remarks The resulting 3 by 3 matrix is a pure rotation. + * \note The behavior is undefined, if \a axis is not normalized. + * \par Example: + * \code + * Mat33f rotZAxisBy45Degrees( Vec3f( 0.0f, 0.0f, 1.0f ), PI/4 ); + * \endcode */ + Matmnt( const Vecnt<3,T> & axis, T angle ); + + /*! \brief Constructor for a 3 by 3 rotation matrix out of a normalized quaternion. + * \param ori A reference to the normalized quaternion representing the rotation. + * \remarks The resulting 3 by 3 matrix is a pure rotation. + * \note The behavior is undefined, if \a ori is not normalized. */ + explicit Matmnt( const Quatt & ori ); + + /*! \brief Constructor for a 4 by 4 transformation matrix out of a quaternion and a translation. + * \param ori A reference to the normalized quaternion representing the rotational part. + * \param trans A reference to the vector representing the translational part. + * \note The behavior is undefined, if \ ori is not normalized. */ + Matmnt( const Quatt & ori, const Vecnt<3,T> & trans ); + + /*! \brief Constructor for a 4 by 4 transformation matrix out of a quaternion, a translation, + * and a scaling. + * \param ori A reference to the normalized quaternion representing the rotational part. + * \param trans A reference to the vector representing the translational part. + * \param scale A reference to the vector representing the scaling along the three axis directions. + * \note The behavior is undefined, if \ ori is not normalized. */ + Matmnt( const Quatt & ori, const Vecnt<3,T> & trans, const Vecnt<3,T> & scale ); + + public: + /*! \brief Get a constant pointer to the n times n values of the matrix. + * \return A constant pointer to the matrix elements. + * \remarks The matrix elements are stored in row-major order. This function returns the + * address of the first element of the first row. It is assured, that the other elements of + * the matrix follow linearly. + * \par Example: + * If \c m is a 3 by 3 matrix, m.getPtr() gives a pointer to the 9 elements m00, m01, m02, m10, + * m11, m12, m20, m21, m22, in that order. */ + const T * getPtr() const; + + /*! \brief Invert the matrix. + * \return \c true, if the matrix was successfully inverted, otherwise \c false. */ + bool invert(); + + /*! \brief Non-constant subscript operator. + * \param i Index of the row to address. + * \return A reference to the \a i th row of the matrix. */ + Vecnt & operator[]( unsigned int i ); + + /*! \brief Constant subscript operator. + * \param i Index of the row to address. + * \return A constant reference to the \a i th row of the matrix. */ + const Vecnt & operator[]( unsigned int i ) const; + + /*! \brief Matrix addition and assignment operator. + * \param mat A constant reference to the matrix to add. + * \return A reference to \c this. + * \remarks The matrix \a mat has to be of the same size as \c this, but may hold values of a + * different type. The matrix elements of type \a S of \a mat are converted to type \a T and + * added to the corresponding matrix elements of \c this. */ + template + Matmnt & operator+=( const Matmnt & mat ); + + /*! \brief Matrix subtraction and assignment operator. + * \param mat A constant reference to the matrix to subtract. + * \return A reference to \c this. + * \remarks The matrix \a mat has to be of the same size as \c this, but may hold values of a + * different type. The matrix elements of type \a S of \a mat are converted to type \a T and + * subtracted from the corresponding matrix elements of \c this. */ + template + Matmnt & operator-=( const Matmnt & mat ); + + /*! \brief Scalar multiplication and assignment operator. + * \param s A scalar value to multiply with. + * \return A reference to \c this. + * \remarks The type of \a s may be of different type as the elements of the \c this. \a s is + * converted to type \a T and each element of \c this is multiplied with it. */ + template + Matmnt & operator*=( S s ); + + /*! \brief Matrix multiplication and assignment operator. + * \param mat A constant reference to the matrix to multiply with. + * \return A reference to \c this. + * \remarks The matrix multiplication \code *this * mat \endcode is calculated and assigned to + * \c this. */ + Matmnt & operator*=( const Matmnt & mat ); + + /*! \brief Scalar division and assignment operator. + * \param s A scalar value to divide by. + * \return A reference to \c this. + * \remarks The type of \a s may be of different type as the elements of the \c this. \a s is + * converted to type \a T and each element of \c this is divided by it. + * \note The behavior is undefined if \a s is very close to zero. */ + template + Matmnt & operator/=( S s ); + + private: + Vecnt m_mat[m]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine the determinant of a matrix. + * \param mat A constant reference to the matrix to determine the determinant from. + * \return The determinant of \a mat. */ + template + T determinant( const Matmnt & mat ); + + /*! \brief Invert a matrix. + * \param mIn A constant reference to the matrix to invert. + * \param mOut A reference to the matrix to hold the inverse. + * \return \c true, if the matrix \a mIn was successfully inverted, otherwise \c false. + * \note If the mIn was not successfully inverted, the values in mOut are undefined. */ + template + bool invert( const Matmnt & mIn, Matmnt & mOut ); + + /*! \brief Test if a matrix is the identity. + * \param mat A constant reference to the matrix to test for identity. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true, if the matrix is the identity, otherwise \c false. + * \remarks A matrix is considered to be the identity, if each of the diagonal elements differ + * less than \a eps from one, and each of the other matrix elements differ less than \a eps from + * zero. + * \sa isNormalized, isNull, isOrthogonal, isSingular */ + template + bool isIdentity( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool identity = true; + for ( unsigned int i=0 ; identity && i + bool isNormalized( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool normalized = true; + for ( unsigned int i=0 ; normalized && i v; + for ( unsigned int j=0 ; j + bool isNull( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool null = true; + for ( unsigned int i=0 ; null && i + bool isOrthogonal( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool orthogonal = true; + for ( unsigned int i=0 ; orthogonal && i+1 tm = ~mat; + for ( unsigned int i=0 ; orthogonal && i+1 + bool isSingular( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + return( abs( determinant( mat ) ) <= eps ); + } + + /*! \brief Get the value of the maximal absolute element of a matrix. + * \param mat A constant reference to a matrix to get the maximal element from. + * \return The value of the maximal absolute element of \a mat. + * \sa minElement */ + template + T maxElement( const Matmnt & mat ); + + /*! \brief Get the value of the minimal absolute element of a matrix. + * \param mat A constant reference to a matrix to get the minimal element from. + * \return The value of the minimal absolute element of \a mat. + * \sa maxElement */ + template + T minElement( const Matmnt & mat ); + + /*! \brief Matrix equality operator. + * \param m0 A constant reference to the first matrix to compare. + * \param m1 A constant reference to the second matrix to compare. + * \return \c true, if \a m0 and \a m1 are equal, otherwise \c false. + * \remarks Two matrices are considered to be equal, if each element of \a m0 differs less than + * the type dependent epsilon from the the corresponding element of \a m1. */ + template + bool operator==( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix inequality operator. + * \param m0 A constant reference to the first matrix to compare. + * \param m1 A constant reference to the second matrix to compare. + * \return \c true, if \a m0 and \a m1 are not equal, otherwise \c false. + * \remarks Two matrices are considered to be not equal, if at least one element of \a m0 differs + * more than the type dependent epsilon from the the corresponding element of \a m1. */ + template + bool operator!=( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix transpose operator. + * \param mat A constant reference to the matrix to transpose. + * \return The transposed version of \a m. */ + template + Matmnt operator~( const Matmnt & mat ); + + /*! \brief Matrix addition operator. + * \param m0 A constant reference to the first matrix to add. + * \param m1 A constant reference to the second matrix to add. + * \return A matrix representing the sum of \code m0 + m1 \endcode */ + template + Matmnt operator+( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix negation operator. + * \param mat A constant reference to the matrix to negate. + * \return A matrix representing the negation of \a mat. */ + template + Matmnt operator-( const Matmnt & mat ); + + /*! \brief Matrix subtraction operator. + * \param m0 A constant reference to the first argument of the subtraction. + * \param m1 A constant reference to the second argument of the subtraction. + * \return A matrix representing the difference \code m0 - m1 \endcode */ + template + Matmnt operator-( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Scalar multiplication operator. + * \param mat A constant reference to the matrix to multiply. + * \param s The scalar value to multiply with. + * \return A matrix representing the product \code mat * s \endcode */ + template + Matmnt operator*( const Matmnt & mat, T s ); + + /*! \brief Scalar multiplication operator. + * \param s The scalar value to multiply with. + * \param mat A constant reference to the matrix to multiply. + * \return A matrix representing the product \code s * mat \endcode */ + template + Matmnt operator*( T s, const Matmnt & mat ); + + /*! \brief Vector multiplication operator. + * \param mat A constant reference to the matrix to multiply. + * \param v A constant reference to the vector to multiply with. + * \return A vector representing the product \code mat * v \endcode */ + template + Vecnt operator*( const Matmnt & mat, const Vecnt & v ); + + /*! \brief Vector multiplication operator. + * \param v A constant reference to the vector to multiply with. + * \param mat A constant reference to the matrix to multiply. + * \return A vector representing the product \code v * mat \endcode */ + template + Vecnt operator*( const Vecnt & v, const Matmnt & mat ); + + /*! \brief Matrix multiplication operator. + * \param m0 A constant reference to the first operand of the multiplication. + * \param m1 A constant reference to the second operand of the multiplication. + * \return A matrix representing the product \code m0 * m1 \endcode */ + template + Matmnt operator*( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Scalar division operator. + * \param mat A constant reference to the matrix to divide. + * \param s The scalar value to divide by. + * \return A matrix representing the matrix \a mat divided by \a s. */ + template + Matmnt operator/( const Matmnt & mat, T s ); + + /*! \brief Set a matrix to be the identity. + * \param mat The matrix to set to identity. + * \remarks Each diagonal element of \a mat is set to one, each other element is set to zero. */ + template + void setIdentity( Matmnt & mat ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Test if a 3 by 3 matrix represents a rotation. + * \param mat A constant reference to the matrix to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true, if the matrix represents a rotation, otherwise \c false. + * \remarks A 3 by 3 matrix is considered to be a rotation, if it is normalized, orthogonal, and its + * determinant is one. + * \sa isIdentity, isNull, isNormalized, isOrthogonal */ + template + bool isRotation( const Matmnt<3,3,T> & mat, T eps = 9 * std::numeric_limits::epsilon() ) + { + return( isNormalized( mat, eps ) + && isOrthogonal( mat, eps ) + && ( std::abs( determinant( mat ) - 1 ) <= eps ) ); + } + + /*! \brief Set the values of a 3 by 3 matrix using a normalized quaternion. + * \param mat A reference to the matrix to set. + * \param q A constant reference to the normalized quaternion to use. + * \return A reference to \a mat. + * \remarks The matrix is set to represent the same rotation as the normalized quaternion \a q. + * \note The behavior is undefined if \a q is not normalized. */ + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Quatt & q ); + + /*! \brief Set the values of a 3 by 3 matrix using a normalized rotation axis and an angle. + * \param mat A reference to the matrix to set. + * \param axis A constant reference to the normalized rotation axis. + * \param angle The angle in radians to rotate around \a axis. + * \return A reference to \a mat. + * \remarks The matrix is set to represent the rotation by \a angle around \a axis. + * \note The behavior is undefined if \a axis is not normalized. */ + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Vecnt<3,T> & axis, T angle ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 3, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a 3 by 3 matrix. + * \param mat A constant reference to the matrix to decompose. + * \param orientation A reference to the quaternion getting the rotational part of the matrix. + * \param scaling A reference to the vector getting the scaling part of the matrix. + * \param scaleOrientation A reference to the quaternion getting the orientation of the scaling. + * \note The behavior is undefined, if the determinant of \a mat is too small, or the rank of \a mat + * is less than three. */ + DP_MATH_API void decompose( const Matmnt<3,3,float> &mat, Quatt &orientation + , Vecnt<3,float> &scaling, Quatt &scaleOrientation ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*!\brief Test if a 4 by 4 matrix represents a mirror transform + * \param mat A const reference to the matrix to test. + * \return \c true if the given matrix is a mirror transform, otherwise \c false. */ + template + bool isMirrorMatrix( const Matmnt<4,4,T>& mat ) + { + const T* ptr = mat.getPtr(); + + const Vecnt<3,T> &v0 = reinterpret_cast&>(ptr[0]); + const Vecnt<3,T> &v1 = reinterpret_cast&>(ptr[4]); + const Vecnt<3,T> &v2 = reinterpret_cast&>(ptr[8]); + + return (scalarTripleProduct(v0, v1, v2)) < 0; + } + + /*! \brief Set the values of a 4 by 4 matrix by the constituents of a transformation. + * \param mat A reference to the matrix to set. + * \param ori A constant reference of the rotation part of the transformation. + * \param trans An optional constant reference to the translational part of the transformation. The + * default is a null vector. + * \param scale An optional constant reference to the scaling part of the transformation. The default + * is the identity scaling. + * \return A reference to \a mat. */ + template + Matmnt<4,4,T> & setMat( Matmnt<4,4,T> & mat, const Quatt & ori + , const Vecnt<3,T> & trans = Vecnt<3,T>(0,0,0) + , const Vecnt<3,T> & scale = Vecnt<3,T>(1,1,1) ) + { + Matmnt<3,3,T> m3( ori ); + mat[0] = Vecnt<4,T>( scale[0] * m3[0], 0 ); + mat[1] = Vecnt<4,T>( scale[1] * m3[1], 0 ); + mat[2] = Vecnt<4,T>( scale[2] * m3[2], 0 ); + mat[3] = Vecnt<4,T>( trans, 1 ); + return( mat ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 4, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a 4 by 4 matrix of floats. + * \param mat A constant reference to the matrix to decompose. + * \param translation A reference to the vector getting the translational part of the matrix. + * \param orientation A reference to the quaternion getting the rotational part of the matrix. + * \param scaling A reference to the vector getting the scaling part of the matrix. + * \param scaleOrientation A reference to the quaternion getting the orientation of the scaling. + * \note The behavior is undefined, if the determinant of \a mat is too small. + * \note Currently, the behavior is undefined, if the rank of \a mat is less than three. */ + DP_MATH_API void decompose( const Matmnt<4,4,float> &mat, Vecnt<3,float> &translation + , Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Matmnt<3,3,float> Mat33f; + typedef Matmnt<3,3,double> Mat33d; + typedef Matmnt<4,4,float> Mat44f; + typedef Matmnt<4,4,double> Mat44d; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Matmnt::Matmnt() + { + } + + template + template + inline Matmnt::Matmnt( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i(rhs[i]); + } + } + + template + inline Matmnt::Matmnt( const std::array,m> & rows ) + { + for ( unsigned int i=0 ; i + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2 ) + { + MY_STATIC_ASSERT( m == 2 ); + m_mat[0] = v1; + m_mat[1] = v2; + } + + template + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3 ) + { + MY_STATIC_ASSERT( m == 3 ); + m_mat[0] = v1; + m_mat[1] = v2; + m_mat[2] = v3; + } + + template + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3, Vecnt const& v4 ) + { + MY_STATIC_ASSERT( m == 4 ); + m_mat[0] = v1; + m_mat[1] = v2; + m_mat[2] = v3; + m_mat[3] = v4; + } + + template + inline Matmnt::Matmnt( const std::array & scalars ) + { + for ( unsigned int i=0, idx=0 ; i + inline Matmnt::Matmnt( const Vecnt<3,T> & axis, T angle ) + { + MY_STATIC_ASSERT( ( m == 3 ) && ( n == 3 ) ); + setMat( *this, axis, angle ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori ) + { + MY_STATIC_ASSERT( ( m == 3 ) && ( n == 3 ) ); + setMat( *this, ori ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori, const Vecnt<3,T> & trans ) + { + MY_STATIC_ASSERT( ( m == 4 ) && ( n == 4 ) ); + setMat( *this, ori, trans ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori, const Vecnt<3,T> & trans, const Vecnt<3,T> & scale ) + { + MY_STATIC_ASSERT( ( m == 4 ) && ( n == 4 ) ); + setMat( *this, ori, trans, scale ); + } + + template + inline const T * Matmnt::getPtr() const + { + return( m_mat[0].getPtr() ); + } + + template + inline bool Matmnt::invert() + { + MY_STATIC_ASSERT( m == n ); + Matmnt tmp; + bool ok = dp::math::invert( *this, tmp ); + if ( ok ) + { + *this = tmp; + } + return( ok ); + } + + template + inline Vecnt & Matmnt::operator[]( unsigned int i ) + { + MY_ASSERT( i < m ); + return( m_mat[i] ); + } + + template + inline const Vecnt & Matmnt::operator[]( unsigned int i ) const + { + MY_ASSERT( i < m ); + return( m_mat[i] ); + } + + template + template + inline Matmnt & Matmnt::operator+=( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Matmnt & Matmnt::operator-=( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Matmnt & Matmnt::operator*=( S s ) + { + for ( unsigned int i=0 ; i + inline Matmnt & Matmnt::operator*=( const Matmnt & rhs ) + { + *this = *this * rhs; + return( *this ); + } + + template + template + inline Matmnt & Matmnt::operator/=( S s ) + { + for ( unsigned int i=0 ; i + inline T calculateDeterminant( const Matmnt & mat, const Vecnt & first, const Vecnt & second ) + { + Vecnt subFirst, subSecond; + for ( unsigned int i=1 ; i + inline T calculateDeterminant( const Matmnt & mat, const Vecnt<1,unsigned int> & first, const Vecnt<1,unsigned int> & second ) + { + return( mat[first[0]][second[0]] ); + } + + template + inline T determinant( const Matmnt & mat ) + { + Vecnt first, second; + for ( unsigned int i=0 ; i + inline bool invert( const Matmnt & mIn, Matmnt & mOut ) + { + mOut = mIn; + + unsigned int p[n]; + + bool ok = true; + for ( unsigned int k=0 ; ok && k::epsilon() < std::abs(s) ); + if ( ok ) + { + T q = std::abs( mOut[i][k] ) / s; + if ( q > max ) + { + max = q; + p[k] = i; + } + } + } + + ok = ( std::numeric_limits::epsilon() < max ); + if ( ok ) + { + if ( p[k] != k ) + { + for ( unsigned int j=0 ; j::epsilon() < std::abs( pivot ) ); + if ( ok ) + { + for ( unsigned int j=0 ; j ( int k >= 0 ) + { + if ( p[k] != k ) + { + for ( unsigned int i=0 ; i + inline T maxElement( const Matmnt & mat ) + { + T me = maxElement( mat[0] ); + for ( unsigned int i=1 ; i + inline T minElement( const Matmnt & mat ) + { + T me = minElement( mat[0] ); + for ( unsigned int i=1 ; i + inline bool operator==( const Matmnt & m0, const Matmnt & m1 ) + { + bool eq = true; + for ( unsigned int i=0 ; i + inline bool operator!=( const Matmnt & m0, const Matmnt & m1 ) + { + return( ! ( m0 == m1 ) ); + } + + template + inline Matmnt operator~( const Matmnt & mat ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator+( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret(m0); + ret += m1; + return( ret ); + } + + template + inline Matmnt operator-( const Matmnt & mat ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator-( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret(m0); + ret -= m1; + return( ret ); + } + + template + inline Matmnt operator*( const Matmnt & mat, T s ) + { + Matmnt ret(mat); + ret *= s; + return( ret ); + } + + template + inline Matmnt operator*( T s, const Matmnt & mat ) + { + return( mat * s ); + } + + template + inline Vecnt operator*( const Matmnt & mat, const Vecnt & v ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Vecnt operator*( const Vecnt & v, const Matmnt & mat ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator*( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator/( const Matmnt & mat, T s ) + { + Matmnt ret(mat); + ret /= s; + return( ret ); + } + + template + void setIdentity( Matmnt & mat ) + { + for ( unsigned int i=0 ; i + inline T determinant( const Matmnt<3,3,T> & mat ) + { + return( mat[0] * ( mat[1] ^ mat[2] ) ); + } + + template + inline bool invert( const Matmnt<3,3,T> & mIn, Matmnt<3,3,T> & mOut ) + { + double adj00 = ( mIn[1][1] * mIn[2][2] - mIn[1][2] * mIn[2][1] ); + double adj10 = - ( mIn[1][0] * mIn[2][2] - mIn[1][2] * mIn[2][0] ); + double adj20 = ( mIn[1][0] * mIn[2][1] - mIn[1][1] * mIn[2][0] ); + double det = mIn[0][0] * adj00 + mIn[0][1] * adj10 + mIn[0][2] * adj20; + bool ok = ( std::numeric_limits::epsilon() < abs( det ) ); + if ( ok ) + { + double invDet = 1.0 / det; + mOut[0][0] = T( adj00 * invDet ); + mOut[0][1] = T( - ( mIn[0][1] * mIn[2][2] - mIn[0][2] * mIn[2][1] ) * invDet ); + mOut[0][2] = T( ( mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1] ) * invDet ); + mOut[1][0] = T( adj10 * invDet ); + mOut[1][1] = T( ( mIn[0][0] * mIn[2][2] - mIn[0][2] * mIn[2][0] ) * invDet ); + mOut[1][2] = T( - ( mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0] ) * invDet ); + mOut[2][0] = T( adj20 * invDet ); + mOut[2][1] = T( - ( mIn[0][0] * mIn[2][1] - mIn[0][1] * mIn[2][0] ) * invDet ); + mOut[2][2] = T( ( mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0] ) * invDet ); + } + return( ok ); + } + + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Vecnt<3,T> & axis, T angle ) + { + T c = cos( angle ); + T s = sin( angle ); + T t = 1 - c; + T x = axis[0]; + T y = axis[1]; + T z = axis[2]; + + mat[0] = Vecnt<3,T>( t * x * x + c, t * x * y + s * z, t * x * z - s * y ); + mat[1] = Vecnt<3,T>( t * x * y - s * z, t * y * y + c, t * y * z + s * x ); + mat[2] = Vecnt<3,T>( t * x * z + s * y, t * y * z - s * x, t * z * z + c ); + + return( mat ); + } + + template + inline Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Quatt & q ) + { + T x = q[0]; + T y = q[1]; + T z = q[2]; + T w = q[3]; + + mat[0] = Vecnt<3,T>( 1 - 2 * ( y * y + z * z ), 2 * ( x * y + z * w ), 2 * ( x * z - y * w ) ); + mat[1] = Vecnt<3,T>( 2 * ( x * y - z * w ), 1 - 2 * ( x * x + z * z ), 2 * ( y * z + x * w ) ); + mat[2] = Vecnt<3,T>( 2 * ( x * z + y * w ), 2 * ( y * z - x * w ), 1 - 2 * ( x * x + y * y ) ); + + return( mat ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for m,n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline T determinant( const Matmnt<4,4,T> & mat ) + { + const T a0 = mat[0][0]*mat[1][1] - mat[0][1]*mat[1][0]; + const T a1 = mat[0][0]*mat[1][2] - mat[0][2]*mat[1][0]; + const T a2 = mat[0][0]*mat[1][3] - mat[0][3]*mat[1][0]; + const T a3 = mat[0][1]*mat[1][2] - mat[0][2]*mat[1][1]; + const T a4 = mat[0][1]*mat[1][3] - mat[0][3]*mat[1][1]; + const T a5 = mat[0][2]*mat[1][3] - mat[0][3]*mat[1][2]; + const T b0 = mat[2][0]*mat[3][1] - mat[2][1]*mat[3][0]; + const T b1 = mat[2][0]*mat[3][2] - mat[2][2]*mat[3][0]; + const T b2 = mat[2][0]*mat[3][3] - mat[2][3]*mat[3][0]; + const T b3 = mat[2][1]*mat[3][2] - mat[2][2]*mat[3][1]; + const T b4 = mat[2][1]*mat[3][3] - mat[2][3]*mat[3][1]; + const T b5 = mat[2][2]*mat[3][3] - mat[2][3]*mat[3][2]; + return( a0*b5 - a1*b4 + a2*b3 + a3*b2 - a4*b1 + a5*b0 ); + } + + template + inline bool invert( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + T s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; T c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + T s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; T c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + T s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; T c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + T s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; T c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + T s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; T c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + T s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; T c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + T det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( det != T(0) ) + { + T invDet = T(1.0) / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[0][1] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[0][2] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[0][3] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[1][0] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[1][2] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[1][3] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[2][0] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[2][1] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[2][3] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[3][0] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[3][1] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[3][2] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + template + inline bool invertTranspose( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + T s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; T c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + T s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; T c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + T s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; T c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + T s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; T c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + T s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; T c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + T s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; T c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + T det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( det != 0 ) + { + T invDet = T(1.0) / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[1][0] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[2][0] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[3][0] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[0][1] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[2][1] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[3][1] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[0][2] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[1][2] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[3][2] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[0][3] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[1][3] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[2][3] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + template + inline bool invertDouble( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + double s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; double c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + double s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; double c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + double s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; double c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + double s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; double c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + double s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; double c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + double s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; double c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + double det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( ( std::numeric_limits::epsilon() < abs( det ) ) ) + { + double invDet = 1.0 / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[0][1] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[0][2] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[0][3] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[1][0] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[1][2] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[1][3] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[2][0] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[2][1] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[2][3] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[3][0] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[3][1] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[3][2] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + /*! \brief makeLookAt defines a viewing transformation. + * \param eye The position of the eye point. + * \param center The position of the reference point. + * \param up The direction of the up vector. + * \remarks The makeLookAt function creates a viewing matrix derived from an eye point, a reference point indicating the center + * of the scene, and an up vector. The matrix maps the reference point to the negative z-axis and the eye point to the + * origin, so that when you use a typical projection matrix, the center of the scene maps to the center of the viewport. + * Similarly, the direction described by the up vector projected onto the viewing plane is mapped to the positive y-axis so that + * it points upward in the viewport. The up vector must not be parallel to the line of sight from the eye to the reference point. + * \note This documentation is adapted from gluLookAt, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeLookAt( const Vecnt<3,T> & eye, const Vecnt<3,T> & center, const Vecnt<3,T> & up ) + { + Vecnt<3,T> f = center - eye; + normalize( f ); + + #ifndef NDEBUG + // assure up is not parallel to vector from eye to center + Vecnt<3,T> nup = up; + normalize( nup ); + T dot = f * nup; + MY_ASSERT( dot != T(1) && dot != T(-1) ); + #endif + + Vecnt<3,T> s = f ^ up; + normalize( s ); + Vecnt<3,T> u = s ^ f; + + Matmnt<4,4,T> transmat; + transmat[0] = Vec4f( T(1), T(0), T(0), T(0) ); + transmat[1] = Vec4f( T(0), T(1), T(0), T(0) ); + transmat[2] = Vec4f( T(0), T(0), T(1), T(0) ); + transmat[3] = Vec4f( -eye[0], -eye[1], -eye[2], T(1) ); + + Matmnt<4,4,T> orimat; + orimat[0] = Vec4f( s[0], u[0], -f[0], T(0) ); + orimat[1] = Vec4f( s[1], u[1], -f[1], T(0) ); + orimat[2] = Vec4f( s[2], u[2], -f[2], T(0) ); + orimat[3] = Vec4f( T(0), T(0), T(0), T(1) ); + + // must premultiply translation + return transmat * orimat; + } + + /*! \brief makeOrtho defines an orthographic projection matrix. + * \param left Coordinate for the left vertical clipping plane. + * \param right Coordinate for the right vertical clipping plane. + * \param bottom Coordinate for the bottom horizontal clipping plane. + * \param top Coordinate for the top horizontal clipping plane. + * \param znear The distance to the near clipping plane. This distance is negative if the plane is behind the viewer. + * \param zfar The distance to the far clipping plane. This distance is negative if the plane is behind the viewer. + * \remarks The makeOrtho function describes a perspective matrix that produces a parallel projection. Assuming this function + * will be used to build a camera's Projection matrix, the (left, bottom, znear) and (right, top, znear) parameters specify the + * points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, + * assuming that the eye is located at (0, 0, 0). The far parameter specifies the location of the far clipping plane. Both znear + * and zfar can be either positive or negative. + * \note This documentation is adapted from glOrtho, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeOrtho( T left, T right, + T bottom, T top, + T znear, T zfar ) + { + MY_ASSERT( (left != right) && (bottom != top) && (znear != zfar) && (zfar > znear) ); + + return Matmnt<4,4,T>( { T(2)/(right-left), T(0), T(0), T(0) + , T(0), T(2)/(top-bottom), T(0), T(0) + , T(0), T(0), T(-2)/(zfar-znear), T(0) + , -(right+left)/(right-left), -(top+bottom)/(top-bottom), -(zfar+znear)/(zfar-znear), T(1) } ); + } + + /*! \brief makeFrustum defines a perspective projection matrix. + * \param left Coordinate for the left vertical clipping plane. + * \param right Coordinate for the right vertical clipping plane. + * \param bottom Coordinate for the bottom horizontal clipping plane. + * \param top Coordinate for the top horizontal clipping plane. + * \param znear The distance to the near clipping plane. The value must be greater than zero. + * \param zfar The distance to the far clipping plane. The value must be greater than znear. + * \remarks The makeFrustum function describes a perspective matrix that produces a perspective projection. Assuming this function + * will be used to build a camera's Projection matrix, the (left, bottom, znear) and (right, top, znear) parameters specify the + * points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, + * assuming that the eye is located at (0,0,0). The zfar parameter specifies the location of the far clipping plane. Both znear + * and zfar must be positive. + * \note This documentation is adapted from glFrustum, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeFrustum( T left, T right, + T bottom, T top, + T znear, T zfar ) + { + // near and far must be greater than zero + MY_ASSERT( (znear > T(0)) && (zfar > T(0)) && (zfar > znear) ); + MY_ASSERT( (left != right) && (bottom != top) && (znear != zfar) ); + + T v0 = (right+left)/(right-left); + T v1 = (top+bottom)/(top-bottom); + T v2 = -(zfar+znear)/(zfar-znear); + T v3 = T(-2)*zfar*znear/(zfar-znear); + T v4 = T(2)*znear/(right-left); + T v5 = T(2)*znear/(top-bottom); + + return Matmnt<4,4,T>( { v4, T(0), T(0), T(0) + , T(0), v5, T(0), T(0) + , v0, v1, v2, T(-1) + , T(0), T(0), v3, T(0) } ); + } + + /*! \brief makePerspective builds a perspective projection matrix. + * \param fovy The vertical field of view, in degrees. + * \param aspect The ratio of the viewport width / height. + * \param znear The distance to the near clipping plane. The value must be greater than zero. + * \param zfar The distance to the far clipping plane. The value must be greater than znear. + * \remarks Assuming makePerspective will be used to build a camera's Projection matrix, it specifies a viewing frustum into the + * world coordinate system. In general, the aspect ratio in makePerspective should match the aspect ratio of the associated + * viewport. For example, aspect = 2.0 means the viewer's angle of view is twice as wide in x as it is in y. If the viewport + * is twice as wide as it is tall, it displays the image without distortion. + * \note This documentation is adapted from gluPerspective, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makePerspective( T fovy, T aspect, T znear, T zfar ) + { + MY_ASSERT( (znear > (T)0) && (zfar > (T)0) ); + + T tanfov = tan( degToRad( fovy ) * (T)0.5 ); + T r = tanfov * aspect * znear; + T l = -r; + T t = tanfov * znear; + T b = -t; + + return makeFrustum( l, r, b, t, znear, zfar ); + } + + template + inline Vecnt<4,T> operator*( const Vecnt<4,T>& v, const Matmnt<4,4,T>& mat ) + { + return Vecnt<4,T> ( + v[0] * mat[0][0] + v[1]*mat[1][0] + v[2]*mat[2][0] + v[3]*mat[3][0], + v[0] * mat[0][1] + v[1]*mat[1][1] + v[2]*mat[2][1] + v[3]*mat[3][1], + v[0] * mat[0][2] + v[1]*mat[1][2] + v[2]*mat[2][2] + v[3]*mat[3][2], + v[0] * mat[0][3] + v[1]*mat[1][3] + v[2]*mat[2][3] + v[3]*mat[3][3] ); + } + + template + inline Matmnt<4,4,T> operator*( const Matmnt<4,4,T> & m0, const Matmnt<4,4,T> & m1 ) + { + Matmnt<4,4,T> result; + + result[0] = Vecnt<4,T>( + m0[0][0]*m1[0][0] + m0[0][1]*m1[1][0] + m0[0][2]*m1[2][0] + m0[0][3]*m1[3][0], + m0[0][0]*m1[0][1] + m0[0][1]*m1[1][1] + m0[0][2]*m1[2][1] + m0[0][3]*m1[3][1], + m0[0][0]*m1[0][2] + m0[0][1]*m1[1][2] + m0[0][2]*m1[2][2] + m0[0][3]*m1[3][2], + m0[0][0]*m1[0][3] + m0[0][1]*m1[1][3] + m0[0][2]*m1[2][3] + m0[0][3]*m1[3][3] + ); + + result[1] = Vecnt<4,T>( + m0[1][0]*m1[0][0] + m0[1][1]*m1[1][0] + m0[1][2]*m1[2][0] + m0[1][3]*m1[3][0], + m0[1][0]*m1[0][1] + m0[1][1]*m1[1][1] + m0[1][2]*m1[2][1] + m0[1][3]*m1[3][1], + m0[1][0]*m1[0][2] + m0[1][1]*m1[1][2] + m0[1][2]*m1[2][2] + m0[1][3]*m1[3][2], + m0[1][0]*m1[0][3] + m0[1][1]*m1[1][3] + m0[1][2]*m1[2][3] + m0[1][3]*m1[3][3] + ); + + result[2] = Vecnt<4,T>( + m0[2][0]*m1[0][0] + m0[2][1]*m1[1][0] + m0[2][2]*m1[2][0] + m0[2][3]*m1[3][0], + m0[2][0]*m1[0][1] + m0[2][1]*m1[1][1] + m0[2][2]*m1[2][1] + m0[2][3]*m1[3][1], + m0[2][0]*m1[0][2] + m0[2][1]*m1[1][2] + m0[2][2]*m1[2][2] + m0[2][3]*m1[3][2], + m0[2][0]*m1[0][3] + m0[2][1]*m1[1][3] + m0[2][2]*m1[2][3] + m0[2][3]*m1[3][3] + ); + + result[3] = Vecnt<4,T>( + m0[3][0]*m1[0][0] + m0[3][1]*m1[1][0] + m0[3][2]*m1[2][0] + m0[3][3]*m1[3][0], + m0[3][0]*m1[0][1] + m0[3][1]*m1[1][1] + m0[3][2]*m1[2][1] + m0[3][3]*m1[3][1], + m0[3][0]*m1[0][2] + m0[3][1]*m1[1][2] + m0[3][2]*m1[2][2] + m0[3][3]*m1[3][2], + m0[3][0]*m1[0][3] + m0[3][1]*m1[1][3] + m0[3][2]*m1[2][3] + m0[3][3]*m1[3][3] + ); + + return result; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for m,n == 4, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline void decompose( const Matmnt<4,4,float> &mat, Vecnt<3,float> &translation + , Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ) + { + translation = Vecnt<3,float>( mat[3] ); + Matmnt<3,3,float> m33( mat ); + decompose( m33, orientation, scaling, scaleOrientation ); + } + + + + //! global identity matrix. + extern DP_MATH_API const Mat44f cIdentity44f; + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/Quatt.h b/apps/bench_shared/dp/math/Quatt.h new file mode 100644 index 00000000..8ace2d8f --- /dev/null +++ b/apps/bench_shared/dp/math/Quatt.h @@ -0,0 +1,551 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include + +namespace dp +{ + namespace math + { + + template class Matmnt; + + /*! \brief Quaternion class. + * \remarks Quaternions are an alternative to the 3x3 matrices that are typically used for 3-D + * rotations. A unit quaternion represents an axis in 3-D space and a rotation around that axis. + * Every rotation can be expressed that way. There are typedefs for the most common usage with \c + * float and \c double: Quatf, Quatd. + * \note Only unit quaternions represent a rotation. */ + template class Quatt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons no initialization is performed. */ + Quatt(); + + /*! \brief Constructor using four scalar values. + * \param x X-component of the quaternion. + * \param y Y-component of the quaternion. + * \param z Z-component of the quaternion. + * \param w W-component of the quaternion. + * \note \c x, \c y, and \c z are \b not the x,y,z-component of the rotation axis, and \c w + * is \b not the rotation angle. If you have such values handy, use the constructor that + * takes the axis as a Vecnt<3,T> and an angle. */ + Quatt( T x, T y, T z, T w ); + + /*! \brief Constructor using a Vecnt<4,T>. + * \param v Vector to construct the quaternion from. + * \remarks The four values of \c v are just copied over to the quaternion. It is assumed + * that this operation gives a normalized quaternion. */ + explicit Quatt( const Vecnt<4,T> & v ); + + /*! \brief Copy constructor using a Quaternion of possibly different type. + * \param q The quaternion to copy. */ + template + explicit Quatt( const Quatt & q ); + + /*! \brief Constructor using an axis and an angle. + * \param axis Axis to rotate around. + * \param angle Angle in radians to rotate. + * \remarks The resulting quaternion represents a rotation by \c angle (in radians) around + * \c axis. */ + Quatt( const Vecnt<3,T> & axis, T angle ); + + /*! \brief Constructor by two vectors. + * \param v0 Start vector. + * \param v1 End vector. + * \remarks The resulting quaternion represents the rotation that maps \a vec0 to \a vec1. + * \note The quaternion out of two anti-parallel vectors is not uniquely defined. We select just + * one out of the possible candidates, which might not be the one you would expect. For better + * control on the quaternion in such a case, we recommend to use the constructor out of an axis + * and an angle. */ + Quatt( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ); + + /*! \brief Constructor by a rotation matrix. + * \param rot The rotation matrix to convert to a unit quaternion. + * \remarks The resulting quaternion represents the same rotation as the matrix. */ + explicit Quatt( const Matmnt<3,3,T> & rot ); + + /*! \brief Normalize the quaternion. + * \return A reference to \c this, as the normalized quaternion. + * \remarks It is always assumed, that a quaternion is normalized. But when getting data from + * outside or due to numerically instabilities, a quaternion might become unnormalized. You + * can use this function then to normalize it again. */ + Quatt & normalize(); + + /*! \brief Non-constant subscript operator. + * \param i Index to the element to use (i=0,1,2,3). + * \return A reference to the \a i th element of the quaternion. */ + template + T & operator[]( S i ); + + /*! \brief Constant subscript operator. + * \param i Index to the element to use (i=0,1,2,3). + * \return A reference to the constant \a i th element of the quaternion. */ + template + const T & operator[]( S i ) const; + + /*! \brief Quaternion assignment operator from a Quaternion of possibly different type. + * \param q The quaternion to assign. + * \return A reference to \c this, as the assigned Quaternion from q. */ + template + Quatt & operator=( const Quatt & q ); + + /*! \brief Quaternion multiplication with a quaternion and assignment operator. + * \param q A Quaternion to multiply with. + * \return A reference to \c this, as the product (or concatenation) of \c this and \a q. + * \remarks Multiplying two quaternions give a quaternion that represents the concatenation + * of the rotations represented by the two quaternions. */ + Quatt & operator*=( const Quatt & q ); + + /*! \brief Quaternion division by a quaternion and assignment operator. + * \param q A Quaternion to divide by. + * \return A reference to \c this, as the division of \c this by \a q. + * \remarks Dividing a quaternion by an other gives a quaternion that represents the + * concatenation of the rotation represented by the numerator quaternion and the rotation + * represented by the conjugated denumerator quaternion. */ + Quatt & operator/=( const Quatt & q ); + + private: + T m_quat[4]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a quaternion into an axis and an angle. + * \param q A reference to the constant quaternion to decompose. + * \param axis A reference to the resulting axis. + * \param angle A reference to the resulting angle. */ + template + void decompose( const Quatt & q, Vecnt<3,T> & axis, T & angle ); + + /*! \brief Determine the distance between two quaternions. + * \param q0 A reference to the left constant quaternion. + * \param q1 A reference to the right constant quaternion. + * \return The euclidean distance between \a q0 and \c q1, interpreted as Vecnt<4,T>. */ + template + T distance( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Test if a quaternion is normalized. + * \param q A reference to the constant quaternion to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true if the quaternion is normalized, otherwise \c false. + * \remarks A quaternion \a q is considered to be normalized, when it's magnitude differs less + * than some small value \a eps from one. */ + template + bool isNormalized( const Quatt & q, T eps = std::numeric_limits::epsilon() * 8 ) + { + return( std::abs( magnitude( q ) - 1 ) <= eps ); + } + + /*! \brief Test if a quaternion is null. + * \param q A reference to the constant quaternion to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true if the quaternion is null, otherwise \c false. + * \remarks A quaternion \a q is considered to be null, if it's magnitude is less than some small + * value \a eps. */ + template + bool isNull( const Quatt & q, T eps = std::numeric_limits::epsilon() ) + { + return( magnitude( q ) <= eps ); + } + + /*! \brief Determine the magnitude of a quaternion. + * \param q A reference to the quaternion to determine the magnitude of. + * \return The magnitude of the quaternion \a q. + * \remarks The magnitude of a normalized quaternion is one. */ + template + T magnitude( const Quatt & q ); + + /*! \brief Quaternion equality operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return \c true if the quaternions \a q0 and \a q are equal, otherwise \c false. + * \remarks Two quaternions are considered to be equal, if each component of the one quaternion + * deviates less than epsilon from the corresponding element of the other quaternion. */ + template + bool operator==( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion inequality operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return \c true if \a q0 is not equal to \a q1, otherwise \c false + * \remarks Two quaternions are considered to be equal, if each component of the one quaternion + * deviates less than epsilon from the corresponding element of the other quaternion. */ + template + bool operator!=( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion conjugation operator. + * \param q A reference to the constant quaternion to conjugate. + * \return The conjugation of \a q. + * \remarks The conjugation of a quaternion is a rotation of the same angle around the negated + * axis. */ + template + Quatt operator~( const Quatt & q ); + + /*! \brief Negation operator. + * \param q A reference to the constant quaternion to get the negation from. + * \return The negation of \a q. + * \remarks The negation of a quaternion is a rotation around the same axis by the negated angle. */ + template + Quatt operator-( const Quatt & q ); + + /*! \brief Quaternion multiplication operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return The product of \a q0 with \a a1. + * \remarks Multiplying two quaternions gives a quaternion that represents the concatenation of + * the rotation represented by the two quaternions. Besides rounding errors, the following + * equation holds: + * \code + * Matmnt<3,3,T>( q0 * q1 ) == Matmnt<3,3,T>( q0 ) * Matmnt<3,3,T>( q1 ) + * \endcode */ + template + Quatt operator*( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion multiplication operator with a vector. + * \param q A reference to the constant left operand. + * \param v A reference to the constant right operand. + * \return The vector \a v rotated by the inverse of \a q. + * \remarks Multiplying a quaternion \a q with a vector \a v applies the inverse rotation + * represented by \a q to \a v. */ + template + Vecnt<3,T> operator*( const Quatt & q, const Vecnt<3,T> & v ); + + /*! \brief Vector multiplication operator with a quaternion. + * \param v A reference to the constant left operand. + * \param q A reference to the constant right operand. + * \return The vector \a v rotated by \a q. + * \remarks Multiplying a vector \a v by a quaternion \a q applies the rotation represented by + * \a q to \a v. */ + template + Vecnt<3,T> operator*( const Vecnt<3,T> & v, const Quatt & q ); + + /*! \brief Quaternion division operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return A quaternion representing the quotient of \a q0 and \a q1. + * \remarks Dividing a quaternion by an other gives a quaternion that represents the + * concatenation of the rotation represented by the numerator quaternion and the rotation + * represented by the conjugated denumerator quaternion. */ + template + Quatt operator/( const Quatt & q0, const Quatt & q1 ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Spherical linear interpolation between two quaternions \a q0 and \a q1. + * \param alpha The interpolation parameter. + * \param q0 The starting value. + * \param q1 The ending value. + * \return The quaternion that represents the spherical linear interpolation between \a q0 and \a + * q1. */ + DP_MATH_API Quatt lerp( float alpha, const Quatt & q0, const Quatt & q1 ); + + /*! \brief Spherical linear interpolation between two quaternions \a q0 and \a q1. + * \param alpha The interpolation parameter. + * \param q0 The starting value. + * \param q1 The ending value. + * \param qr The quaternion that represents the spherical linear interpolation between \a q0 and \a + * q1. */ + DP_MATH_API void lerp( float alpha, const Quatt & q0, const Quatt & q1, Quatt &qr ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Quatt Quatf; + typedef Quatt Quatd; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline Quatt::Quatt() + { + } + + template + inline Quatt::Quatt( T x, T y, T z, T w ) + { + m_quat[0] = x; + m_quat[1] = y; + m_quat[2] = z; + m_quat[3] = w; + } + + template + inline Quatt::Quatt( const Vecnt<4,T> & v ) + { + m_quat[0] = v[0]; + m_quat[1] = v[1]; + m_quat[2] = v[2]; + m_quat[3] = v[3]; + } + + template + template + inline Quatt::Quatt( const Quatt & q ) + { + *this = q; + } + + template + inline Quatt::Quatt( const Vecnt<3,T> & axis, T angle ) + { + T dummy = sin( T(0.5) * angle ); + m_quat[0] = axis[0] * dummy; + m_quat[1] = axis[1] * dummy; + m_quat[2] = axis[2] * dummy; + m_quat[3] = cos( T(0.5) * angle ); + } + + template + inline Quatt::Quatt( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ) + { + Vecnt<3,T> axis = v0 ^ v1; + axis.normalize(); + T cosAngle = clamp( v0 * v1, -1.0f, 1.0f ); // make sure, cosine is in [-1.0,1.0]! + if ( cosAngle + 1.0f < std::numeric_limits::epsilon() ) + { + // In case v0 and v1 are (closed to) anti-parallel, the standard + // procedure would not create a valid quaternion. + // As the rotation axis isn't uniquely defined in that case, we + // just pick one. + axis = orthonormal( v0 ); + } + T s = sqrt( T(0.5) * ( 1 - cosAngle ) ); + m_quat[0] = axis[0] * s; + m_quat[1] = axis[1] * s; + m_quat[2] = axis[2] * s; + m_quat[3] = sqrt( T(0.5) * ( 1 + cosAngle ) ); + } + + template + inline Quatt::Quatt( const Matmnt<3,3,T> & rot ) + { + T tr = rot[0][0] + rot[1][1] + rot[2][2] + 1; + if ( std::numeric_limits::epsilon() < tr ) + { + T s = sqrt( tr ); + m_quat[3] = T(0.5) * s; + s = T(0.5) / s; + m_quat[0] = ( rot[1][2] - rot[2][1] ) * s; + m_quat[1] = ( rot[2][0] - rot[0][2] ) * s; + m_quat[2] = ( rot[0][1] - rot[1][0] ) * s; + } + else + { + unsigned int i = 0; + if ( rot[i][i] < rot[1][1] ) + { + i = 1; + } + if ( rot[i][i] < rot[2][2] ) + { + i = 2; + } + unsigned int j = ( i + 1 ) % 3; + unsigned int k = ( j + 1 ) % 3; + T s = sqrt( rot[i][i] - rot[j][j] - rot[k][k] + 1 ); + m_quat[i] = T(0.5) * s; + s = T(0.5) / s; + m_quat[j] = ( rot[i][j] + rot[j][i] ) * s; + m_quat[k] = ( rot[i][k] + rot[k][i] ) * s; + m_quat[3] = ( rot[k][j] - rot[j][k] ) * s; + } + normalize(); + } + + template + inline Quatt & Quatt::normalize() + { + T mag = sqrt( square(m_quat[0]) + square(m_quat[1]) + square(m_quat[2]) + square(m_quat[3]) ); + T invMag = T(1) / mag; + for ( int i=0 ; i<4 ; i++ ) + { + m_quat[i] = m_quat[i] * invMag; + } + return( *this ); + } + + template<> + inline Quatt & Quatt::normalize() + { + *this = (Quatt(*this)).normalize(); + return( *this ); + } + + template + template + inline T & Quatt::operator[]( S i ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i && i <= 3 ); + return( m_quat[i] ); + } + + template + template + inline const T & Quatt::operator[]( S i ) const + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i && i <= 3 ); + return( m_quat[i] ); + } + + template + template + inline Quatt & Quatt::operator=( const Quatt & q ) + { + m_quat[0] = T(q[0]); + m_quat[1] = T(q[1]); + m_quat[2] = T(q[2]); + m_quat[3] = T(q[3]); + return( *this ); + } + + template + inline Quatt & Quatt::operator*=( const Quatt & q ) + { + *this = *this * q; + return( *this ); + } + + template + inline Quatt & Quatt::operator/=( const Quatt & q ) + { + *this = *this / q; + return( *this ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline void decompose( const Quatt & q, Vecnt<3,T> & axis, T & angle ) + { + angle = 2 * acos( q[3] ); + if ( angle < std::numeric_limits::epsilon() ) + { + // no angle to rotate about => take just any one + axis[0] = 0.0f; + axis[1] = 0.0f; + axis[2] = 1.0f; + } + else + { + T dummy = 1 / sin( T(0.5) * angle ); + axis[0] = q[0] * dummy; + axis[1] = q[1] * dummy; + axis[2] = q[2] * dummy; + axis.normalize(); + } + } + + template + inline T distance( const Quatt & q0, const Quatt & q1 ) + { + return( sqrt( square( q0[0] - q1[0] ) + + square( q0[1] - q1[1] ) + + square( q0[2] - q1[2] ) + + square( q0[3] - q1[3] ) ) ); + } + + template + inline T magnitude( const Quatt & q ) + { + return( sqrt( q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3] ) ); + } + + template + inline bool operator==( const Quatt & q0, const Quatt & q1 ) + { + return( ( std::abs( q0[0] - q1[0] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[1] - q1[1] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[2] - q1[2] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[3] - q1[3] ) < std::numeric_limits::epsilon() ) ); + } + + template + inline bool operator!=( const Quatt & q0, const Quatt & q1 ) + { + return( ! ( q0 == q1 ) ); + } + + template + inline Quatt operator~( const Quatt & q ) + { + return( Quatt( -q[0], -q[1], -q[2], q[3] ) ); + } + + template + inline Quatt operator-( const Quatt & q ) + { + return( Quatt( q[0], q[1], q[2], -q[3] ) ); + } + + template + inline Quatt operator*( const Quatt & q0, const Quatt & q1 ) + { + Quatt q( q0[3]*q1[0] + q0[0]*q1[3] - q0[1]*q1[2] + q0[2]*q1[1] + , q0[3]*q1[1] + q0[0]*q1[2] + q0[1]*q1[3] - q0[2]*q1[0] + , q0[3]*q1[2] - q0[0]*q1[1] + q0[1]*q1[0] + q0[2]*q1[3] + , q0[3]*q1[3] - q0[0]*q1[0] - q0[1]*q1[1] - q0[2]*q1[2] ); + q.normalize(); + return( q ); + } + + template + inline Vecnt<3,T> operator*( const Quatt & q, const Vecnt<3,T> & v ) + { + return( Matmnt<3,3,T>(q) * v ); + } + + template + inline Vecnt<3,T> operator*( const Vecnt<3,T> & v, const Quatt & q ) + { + return( v * Matmnt<3,3,T>(q) ); + } + + template + inline Quatt operator/( const Quatt & q0, const Quatt & q1 ) + { + return( q0 * ~q1 /*/ magnitude( q1 )*/ ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/Trafo.h b/apps/bench_shared/dp/math/Trafo.h new file mode 100644 index 00000000..a4eb1d33 --- /dev/null +++ b/apps/bench_shared/dp/math/Trafo.h @@ -0,0 +1,264 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include + +namespace dp +{ + namespace math + { + + //! Transformation class. + /** This class is used to ease transformation handling. It has an interface to rotate, scale, + * and translate and can produce a \c Mat44f that combines them. */ + class Trafo + { + public: + //! Constructor: initialized to identity + DP_MATH_API Trafo( void ); + + //! Copy Constructor + DP_MATH_API Trafo( const Trafo &rhs ); //!< Trafo to copy + + //! Get the center of rotation of this transformation. + /** \returns \c Vec3f that describes the center or rotation. */ + DP_MATH_API const Vec3f & getCenter( void ) const; + + //! Get the rotational part of this transformation. + /** \returns \c Quatf that describes the rotational part */ + DP_MATH_API const Quatf & getOrientation( void ) const; + + //! Get the scale orientation part of this transform. + /** \return \c Quatf that describes the scale orientational part */ + DP_MATH_API const Quatf & getScaleOrientation( void ) const; + + //! Get the scaling part of this transformation. + /** \returns \c Vec3f that describes the scaling part */ + DP_MATH_API const Vec3f & getScaling( void ) const; + + //! Get the translational part of this transformation. + /** \returns \c Vec3f that describes the translational part */ + DP_MATH_API const Vec3f & getTranslation( void ) const; + + /*! \brief Get the current transformation. + * \return The \c Mat44f that describes the transformation. + * \remarks The transformation is the concatenation of the center translation C, scale + * orientation SO, scaling S, rotation R, and translation T, by the following formula: + * \code + * M = -C * SO^-1 * S * SO * R * C * T + * \endcode + * \sa getInverse */ + DP_MATH_API const Mat44f& getMatrix( void ) const; + + /*! \brief Get the current inverse transformation. + * \return The \c Mat44f that describes the inverse transformation. + * \remarks The inverse transformation is the concatenation of the center translation C, + * scale orientation SO, scaling S, rotation R, and translation T, by the following + * formula: + * \code + * M = T^-1 * C^-1 * R^-1 * SO^-1 * S^-1 * SO * -C^-1 + * \endcode + * \sa getMatrix */ + DP_MATH_API Mat44f getInverse( void ) const; + + //! Set the center of ration of the transformation. + DP_MATH_API void setCenter( const Vec3f ¢er //!< center of rotation + ); + + //! Set the \c Trafo to identity. + DP_MATH_API void setIdentity( void ); + + //! Set the rotational part of the transformation, using a quaternion. + DP_MATH_API void setOrientation( const Quatf &orientation //!< rotational part of transformation + ); + + //! Set the scale orientational part of the transformation. + DP_MATH_API void setScaleOrientation( const Quatf &scaleOrientation //!< scale orientational part of transform + ); + + //! Set the scaling part of the transformation. + DP_MATH_API void setScaling( const Vec3f &scaling //!< scaling part of transformation + ); + + //! Set the translational part of the transformation. + DP_MATH_API void setTranslation( const Vec3f &translation //!< translational part of transformation + ); + + //! Set the complete transformation by a Mat44f. + /** The matrix is internally decomposed into a translation, rotation, scaleOrientation, and scaling. */ + DP_MATH_API void setMatrix( const Mat44f &matrix //!< complete transformation + ); + + //! Copy operator. + DP_MATH_API Trafo & operator=( const Trafo &rhs //!< Trafo to copy + ); + + //! Equality operator. + /** \returns \c true if \c this is equal to \a t, otherwise \c false */ + DP_MATH_API bool operator==( const Trafo &t //!< \c Trafo to compare with + ) const; + + DP_MATH_API bool operator!=( const Trafo &t ) const; + + private: + DP_MATH_API void decompose() const; + + mutable Mat44f m_matrix; + mutable Vec3f m_center; //!< center of rotation + mutable Quatf m_orientation; //!< orientational part of the transformation + mutable Quatf m_scaleOrientation; //!< scale orientation + mutable Vec3f m_scaling; //!< scaling part of the transformation + mutable Vec3f m_translation; //!< translational part of the transformation + + mutable bool m_matrixValid; + mutable bool m_decompositionValid; + }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*! \relates dp::math::Trafo + * This calculates the linear interpolation \code ( 1 - alpha ) * t0 + alpha * t1 \endcode */ + DP_MATH_API Trafo lerp( float alpha //!< interpolation parameter + , const Trafo &t0 //!< starting value + , const Trafo &t1 //!< ending value + ); + + DP_MATH_API void lerp( float alpha, const Trafo & t0, const Trafo & t1, Trafo & tr ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlines + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline const Vec3f & Trafo::getCenter( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_center ); + } + + inline const Quatf & Trafo::getOrientation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_orientation ); + } + + inline const Quatf & Trafo::getScaleOrientation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_scaleOrientation ); + } + + inline const Vec3f & Trafo::getScaling( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_scaling ); + } + + inline const Vec3f & Trafo::getTranslation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_translation ); + } + + inline void Trafo::setCenter( const Vec3f ¢er ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_center = center; + } + + inline void Trafo::setOrientation( const Quatf &orientation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_orientation = orientation; + } + + inline void Trafo::setScaleOrientation( const Quatf &scaleOrientation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_scaleOrientation = scaleOrientation; + } + + inline void Trafo::setScaling( const Vec3f &scaling ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_scaling = scaling; + } + + inline void Trafo::setTranslation( const Vec3f &translation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_translation = translation; + } + + inline bool Trafo::operator!=( const Trafo & t ) const + { + return( ! ( *this == t ) ); + } + + inline void lerp( float alpha, const Trafo & t0, const Trafo & t1, Trafo & tr ) + { + tr = lerp( alpha, t0, t1 ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/Vecnt.h b/apps/bench_shared/dp/math/Vecnt.h new file mode 100644 index 00000000..8c1061dc --- /dev/null +++ b/apps/bench_shared/dp/math/Vecnt.h @@ -0,0 +1,1223 @@ +// Copyright (c) 2002-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include +#include +#include + +//#include +#include "inc/MyAssert.h" + +namespace dp +{ + namespace math + { + + template class Spherent; + + /*! \brief Vector class of fixed size and type. + * \remarks This class is templated by size and type. It holds \a n values of type \a T. There + * are typedefs for the most common usage with 2, 3, and 4 values of type \c float and \c double: + * Vec2f, Vec2d, Vec3f, Vec3d, Vec4f, Vec4d. */ + template class Vecnt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons, no initialization is performed. */ + Vecnt(); + + /*! \brief Copy constructor from a vector of possibly different size and type. + * \param rhs A vector with \a m values of type \a S. + * \remarks The minimum \a k of \a n and \a m is determined. The first \a k values of type \a + * S from \a rhs are converted to type \a T and assigned as the first \a k values of \c this. + * If \a k is less than \a n, the \a n - \a k last values of \c this are not initialized. */ + template + explicit Vecnt( const Vecnt & rhs ); + + /*! \brief Copy constructor from a vector with one less value than \c this, and an explicit last value. + * \param rhs A vector with \a m values of type \a S, where \a m has to be one less than \a n. + * \param last A single value of type \a R, that will be set as the last value of \c this. + * \remarks This constructor contains a compile-time assertion, to make sure that \a m is one + * less than \a n. The values of \a rhs of type \a S are converted to type \a T and assigned + * as the first values of \c this. The value \a last of type \a R also is converted to type + * \a T and assigned as the last value of \c this. + * \par Example: + * \code + * Vec3f v3f(0.0f,0.0f,0.0f); + * Vec4f v4f(v3f,1.0f); + * \endcode */ + template + Vecnt( const Vecnt & rhs, R last ); + + /*! \brief Constructor for a one-element vector. + * \param x First element of the vector. + * \remarks This constructor can only be used with one-element vectors. + * \par Example: + * \code + * Vec1f v1f( 1.0f ); + * Vecnt<1,int> v1i( 0 ); + * \endcode */ + Vecnt( T x); + + /*! \brief Constructor for a two-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \remarks This constructor can only be used with two-element vectors. + * \par Example: + * \code + * Vec2f v2f( 1.0f, 2.0f ); + * Vecnt<2,int> v2i( 0, 1 ); + * \endcode */ + Vecnt( T x, T y ); + + /*! \brief Constructor for a three-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \param z Third element of the vector. + * \remarks This constructor contains a compile-time assertion, to make sure it is used for + * three-element vectors, like Vec3f, only. + * \par Example: + * \code + * Vec3f v3f( 1.0f, 2.0f, 3.0f ); + * Vecnt<3,int> v3i( 0, 1, 2 ); + * \endcode */ + Vecnt( T x, T y, T z ); + + /*! \brief Constructor for a four-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \param z Third element of the vector. + * \param w Fourth element of the vector. + * \remarks This constructor contains a compile-time assertion, to make sure it is used for + * four-element vectors, like Vec4f, only. + * \par Example: + * \code + * Vec4f v4f( 1.0f, 2.0f, 3.0f, 4.0f ); + * Vecnt<4,int> v4i( 0, 1, 2, 3 ); + * \endcode */ + Vecnt( T x, T y, T z, T w ); + + public: + /*! \brief Get a pointer to the constant values of this vector. + * \return A pointer to the constant values of this vector. + * \remarks It is assured, that the values of a vector are contiguous. + * \par Example: + * \code + * GLColor3fv( p->getDiffuseColor().getPtr() ); + * \endcode */ + const T * getPtr() const; + + /*! \brief Normalize this vector and get it's previous length. + * \return The length of the vector before the normalization. */ + T normalize(); + + /*! \brief Access operator to the values of a vector. + * \param i The index of the value to access. + * \return A reference to the value at position \a i in this vector. + * \remarks The index \a i has to be less than the size of the vector, given by the template + * argument \a n. + * \note The behavior is undefined if \ i is greater or equal to \a n. */ + T & operator[]( size_t i ); + + /*! \brief Constant access operator to the values of a vector. + * \param i The index of the value to access. + * \return A constant reference to the value at position \a i in this vector. + * \remarks The index \a i has to be less than the size of the vector, given by the template + * argument \a n. + * \note The behavior is undefined if \ i is greater or equal to \a n. */ + const T & operator[]( size_t i ) const; + + /*! \brief Vector assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to assign to \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise assigned to the values of \c this. */ + template + Vecnt & operator=( const Vecnt & rhs ); + + /*! \brief Vector addition and assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to add to \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise added to the values of \c this. */ + template + Vecnt & operator+=( const Vecnt & rhs ); + + /*! \brief Vector subtraction and assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to subtract from \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise subtracted from the values of \c this. */ + template + Vecnt & operator-=( const Vecnt & rhs ); + + /*! \brief Scalar multiplication and assignment operator with a scalar of possibly different type. + * \param s A scalar to multiply \c this with. + * \return A reference to \c this. + * \remarks The values of \c this are component-wise multiplied with \a s. */ + template + Vecnt & operator*=( S s ); + + /*! \brief Scalar division and assignment operator with a scalar of possibly different type. + * \param s A scalar to divide \c this by. + * \return A reference to \c this. + * \remarks The values of \c this are component-wise divided by \a s. + * \note The behavior is undefined if \a s is less than the type-dependent epsilon. */ + template + Vecnt & operator/=( S s ); + + /*! \brief Orthonormalize \c this with respect to the vector \a v. + * \param v A constant reference to the vector to orthonormalize \c this to. + * \remarks Subtracts the orthogonal projection of \c this on \a v from \c this and + * normalizes the it, resulting in a normalized vector that is orthogonal to \a v. */ + void orthonormalize( const Vecnt & v ); + + private: + T m_vec[n]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine the bounding box of a number of points. + * \param points A vector to the points. + * \param numberOfPoints The number of points in \a points. + * \param min Reference to the calculated lower left front vertex of the bounding box. + * \param max Reference to the calculated upper right back vertex of the bounding box. */ + template + void boundingBox( const Vecnt * points, size_t numberOfPoints, Vecnt & min, Vecnt & max ); + + /*! \brief Determine the bounding box of a number of points. + * \param points A random access iterator to the points. + * \param numberOfPoints The number of points in \a points. + * \param min Reference to the calculated lower left front vertex of the bounding box. + * \param max Reference to the calculated upper right back vertex of the bounding box. */ + template + inline void boundingBox( RandomAccessIterator points, size_t numberOfPoints, Vecnt & min, Vecnt & max ); + + /*! \brief Determine if two vectors point in opposite directions. + * \param v0 A constant reference to the first normalized vector to use. + * \param v1 A constant reference to the second normalized vector to use. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v0 and \a v1 are anti-parallel, otherwise \c false. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \sa areCollinear, isNormalized */ + template + bool areOpposite( const Vecnt & v0, const Vecnt & v1 + , T eps = std::numeric_limits::epsilon() ) + { + return( ( 1 + v0 * v1 ) <= eps ); + } + + /*! \brief Determine if two vectors are orthogonal. + * \param v0 A constant reference to the first vector to use. + * \param v1 A constant reference to the second vector to use. + * \param eps An optional deviation from orthonormality. The default is the type dependent + * epsilon. + * \return \c true, if \a v0 and \a v1 are orthogonal, otherwise \c false. + * \sa areOrthonormal */ + template + bool areOrthogonal( const Vecnt & v0, const Vecnt & v1 + , T eps = 2 * std::numeric_limits::epsilon() ) + { + return( std::abs(v0*v1) <= std::max(T(1),length(v0)) * std::max(T(1),length(v1)) * eps ); + } + + /*! \brief Determine if two vectors are orthonormal. + * \param v0 A constant reference to the first normalized vector to use. + * \param v1 A constant reference to the second normalized vector to use. + * \param eps An optional deviation from orthonormality. The default is the type dependent + * epsilon. + * \return \c true, if \a v0 and \a v1 are orthonormal, otherwise \c false. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \sa areOrthogonal */ + template + bool areOrthonormal( const Vecnt & v0, const Vecnt & v1 + , T eps = 2 * std::numeric_limits::epsilon() ) + { + return( isNormalized(v0) && isNormalized(v1) && ( std::abs(v0*v1) <= eps ) ); + } + + /*! \brief Determine if two vectors differ less than a given epsilon in each component. + * \param v0 A constant reference to the first vector to use. + * \param v1 A constant reference to the second vector to use. + * \param eps The acceptable deviation for each component. + * \return \c true, if \ v0 and \a v1 differ less than or equal to \a eps in each component, otherwise \c + * false. + * \sa distance, operator==() */ + template + bool areSimilar( const Vecnt & v0, const Vecnt & v1, T eps ); + + /*! \brief Determine the distance between vectors. + * \param v0 A constant reference to the first vector. + * \param v1 A constant reference to the second vector. + * \return The euclidean distance between \a v0 and \a v1. + * \sa length, lengthSquared */ + template + T distance( const Vecnt & v0, const Vecnt & v1 ); + + template + T intensity( const Vecnt & v ); + + /*! \brief Determine if a vector is normalized. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v is normalized, otherwise \c false. + * \sa isNull, length, lengthSquared, normalize */ + template + bool isNormalized( const Vecnt & v, T eps = 2 * std::numeric_limits::epsilon() ) + { + return( std::abs( length( v ) - 1 ) <= eps ); + } + + /*! \brief Determine if a vector is the null vector. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v is the null vector, otherwise \c false. + * \sa isNormalized, length, lengthSquared */ + template + bool isNull( const Vecnt & v, T eps = std::numeric_limits::epsilon() ) + { + return( length( v ) <= eps ); + } + + /*! \brief Determine if a vector is uniform, that is, all its components are equal. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if all components of \a v are equal, otherwise \c false. */ + template + bool isUniform( const Vecnt & v, T eps = std::numeric_limits::epsilon() ) + { + bool uniform = true; + for ( unsigned int i=1 ; i + T length( const Vecnt & v ); + + /*! \brief Determine the squared length of a vector. + * \param v A constant reference to the vector to use. + * \return The squared length of the vector \a v. + * \sa length */ + template + T lengthSquared( const Vecnt & v ); + + /*! \brief Determine the maximal element of a vector. + * \param v A constant reference to the vector to use. + * \return The largest absolute value of \a v. + * \sa minElement */ + template + T maxElement( const Vecnt & v ); + + /*! \brief Determine the minimal element of a vector. + * \param v A constant reference to the vector to use. + * \return The smallest absolute value of \a v. + * \sa maxElement */ + template + T minElement( const Vecnt & v ); + + /*! \brief Normalize a vector. + * \param v A reference to the vector to normalize. + * \return The length of the unnormalized vector. + * \sa isNormalized, length */ + template + T normalize( Vecnt & v ); + + /*! \brief Test for equality of two vectors. + * \param v0 A constant reference to the first vector to test. + * \param v1 A constant reference to the second vector to test. + * \return \c true, if the two vectors component-wise differ less than the type dependent + * epsilon, otherwise \c false. + * \sa operator!=() */ + template + bool operator==( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Test for inequality of two vectors. + * \param v0 A constant reference to the first vector to test. + * \param v1 A constant reference to the second vector to test. + * \return \c true, if the two vectors component-wise differ more than the type dependent + * epsilon in at least one component, otherwise \c false. + * \sa operator==() */ + template + bool operator!=( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Vector addition operator + * \param v0 A constant reference to the left operand. + * \param v1 A second reference to the right operand. + * \return A vector holding the component-wise sum of \a v0 and \a v1. + * \sa operator-() */ + template + Vecnt operator+( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Unary vector negation operator. + * \param v A constant reference to a vector. + * \return A vector holding the component-wise negation of \a v. + * \sa operator-() */ + template + Vecnt operator-( const Vecnt & v ); + + /*! \brief Vector subtraction operator. + * \param v0 A constant reference to the left operand. + * \param v1 A second reference to the right operand. + * \return A vector holding the component-wise difference of \a v0 and \a v1. + * \sa operator+() */ + template + Vecnt operator-( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Scalar multiplication of a vector. + * \param v A constant reference to the left operand. + * \param s A scalar as the right operand. + * \return A vector holding the component-wise product with s. + * \sa operator/() */ + template + Vecnt operator*( const Vecnt & v, S s ); + + /*! \brief Scalar multiplication of a vector. + * \param s A scalar as the left operand. + * \param v A constant reference to the right operand. + * \return A vector holding the component-wise product with \a s. + * \sa operator/() */ + template + Vecnt operator*( S s, const Vecnt & v ); + + /*! \brief Vector multiplication. + * \param v0 A constant reference to the left operand. + * \param v1 A constant reference to the right operand. + * \return The dot product of \a v0 and \a v1. + * \sa operator^() */ + template + T operator*( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Scalar division of a vector. + * \param v A constant reference to the left operand. + * \param s A scalar as the right operand. + * \return A vector holding the component-wise division by \a s. + * \sa operator*() */ + template + Vecnt operator/( const Vecnt & v, S s ); + + /*! \brief Determine a vector that's orthonormal to \a v. + * \param v A vector to determine an orthonormal vector to. + * \return A vector that's orthonormal to \a v. + * \note The result of this function is not uniquely defined. In two dimensions, the orthonormal + * to a vector can be one of two anti-parallel vectors. In higher dimensions, there are infinitely + * many possible results. This function just select one of them. + * \sa orthonormalize */ + template + Vecnt orthonormal( const Vecnt & v ); + + /*! \brief Determine the orthonormal vector of \a v1 with respect to \a v0. + * \param v0 A constant reference to the normalized vector to orthonormalize against. + * \param v1 A constant reference to the normalized vector to orthonormalize. + * \return A normalized vector representing the orthonormalized version of \a v1 with respect to + * \a v0. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \par Example: + * \code + * Vec3f newYAxis = orthonormalize( newZAxis, oldYAxis ); + * \endcode + * \sa normalize */ + template + Vecnt orthonormalize( const Vecnt & v0, const Vecnt & v1 ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 1 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a two-component vector. + * \param v A reference to the vector to set with \a x. + * \param x The first component to set. */ + template + Vecnt<1,T> & setVec( Vecnt<1,T> & v, T x ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 2 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a two-component vector. + * \param v A reference to the vector to set with \a x and \a y. + * \param x The first component to set. + * \param y The second component to set. */ + template + Vecnt<2,T> & setVec( Vecnt<2,T> & v, T x, T y ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine if two vectors are collinear. + * \param v0 A constant reference to the first vector. + * \param v1 A constant reference to the second vector. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v0 and \a v1 are collinear, otherwise \c false. + * \sa areOpposite */ + template + bool areCollinear( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 + , T eps = std::numeric_limits::epsilon() ) + { + return( length( v0 ^ v1 ) < eps ); + } + + /*! \brief Cross product operator. + * \param v0 A constant reference to the left operand. + * \param v1 A constant reference to the right operand. + * \return A vector that is the cross product of v0 and v1. + * \sa operator*() */ + template + Vecnt<3,T> operator^( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 ); + + /*! \brief Set the values of a three-component vector. + * \param v A reference to the vector to set with \a x, \a y and \a z. + * \param x The first component to set. + * \param y The second component to set. + * \param z The third component to set. */ + template + Vecnt<3,T> & setVec( Vecnt<3,T> & v, T x, T y, T z ); + + /*! \brief Determine smoothed normals for a set of vertices. + * \param vertices A constant reference to a vector of vertices. + * \param sphere A constant reference to the bounding sphere of the vertices. + * \param creaseAngle The angle in radians to crease at. + * \param normals A reference to a vector of normals. + * \remarks Given \a vertices with \a normals and their bounding \a sphere, this function + * calculates smoothed normals in \a normals for the vertices. For all vertices, that are similar + * within a tolerance that depends on the radius of \a sphere, and whose normals differ less than + * \a creaseAngle (in radians), their normals are set to the average of their normals. */ + template + void smoothNormals( const std::vector > &vertices, const Spherent<3,T> &sphere + , T creaseAngle, std::vector > &normals ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a four-component vector. + * \param v A reference to the vector to set with \a x, \a y, \a z, and \a w. + * \param x The first component to set. + * \param y The second component to set. + * \param z The third component to set. + * \param w The fourth component to set. */ + template + Vecnt<4,T> & setVec( Vecnt<4,T> & v, T x, T y, T z, T w ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Vecnt<1,float> Vec1f; + typedef Vecnt<1,double> Vec1d; + typedef Vecnt<2,float> Vec2f; + typedef Vecnt<2,double> Vec2d; + typedef Vecnt<3,float> Vec3f; + typedef Vecnt<3,double> Vec3d; + typedef Vecnt<4,float> Vec4f; + typedef Vecnt<4,double> Vec4d; + typedef Vecnt<1,int> Vec1i; + typedef Vecnt<2,int> Vec2i; + typedef Vecnt<3,int> Vec3i; + typedef Vecnt<4,int> Vec4i; + typedef Vecnt<1,unsigned int> Vec1ui; + typedef Vecnt<2,unsigned int> Vec2ui; + typedef Vecnt<3,unsigned int> Vec3ui; + typedef Vecnt<4,unsigned int> Vec4ui; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt::Vecnt() + { + } + + template + template + inline Vecnt::Vecnt( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt::Vecnt( const Vecnt & rhs, R last ) + { + for ( unsigned int i=0 ; i + inline Vecnt::Vecnt( T x ) + { + setVec( *this, x ); + } + + template + inline Vecnt::Vecnt( T x, T y ) + { + setVec( *this, x, y ); + } + + template + inline Vecnt::Vecnt( T x, T y, T z ) + { + setVec( *this, x, y, z ); + } + + template + inline Vecnt::Vecnt( T x, T y, T z, T w ) + { + setVec( *this, x, y, z, w ); + } + + template + inline const T * Vecnt::getPtr() const + { + return( &m_vec[0] ); + } + + template + inline T Vecnt::normalize() + { + return( dp::math::normalize( *this ) ); + } + + template + inline T & Vecnt::operator[]( size_t i ) + { + MY_ASSERT( i < n ); + return( m_vec[i] ); + } + + template + inline const T & Vecnt::operator[]( size_t i ) const + { + MY_ASSERT( i < n ); + return( m_vec[i] ); + } + + template + template + inline Vecnt & Vecnt::operator=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator+=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator-=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator*=( S s ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator/=( S s ) + { + for ( unsigned int i=0 ; i + inline void Vecnt::orthonormalize( const Vecnt & v ) + { + *this = dp::math::orthonormalize( v, *this ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline void boundingBox( const Vecnt * points, size_t numberOfPoints, Vecnt & min, Vecnt & max ) + { + MY_ASSERT( 0 < n ); + min = max = points[0]; + for ( size_t i=1 ; i + inline void boundingBox( RandomAccessIterator points, size_t numberOfPoints, Vecnt & min, Vecnt & max ) + { + MY_ASSERT( 0 < n ); + min = max = points[0]; + for ( size_t i=1 ; i + inline bool areSimilar( const Vecnt & v0, const Vecnt & v1, T eps ) + { + for ( unsigned int i=0 ; i + inline T distance( const Vecnt & v0, const Vecnt & v1 ) + { + return( length( v0 - v1 ) ); + } + + template + inline T intensity( const Vecnt & v ) + { + T intens(0); + for ( unsigned int i=0 ; i + inline T length( const Vecnt & v ) + { + return( sqrt( lengthSquared( v ) ) ); + } + + template + inline T lengthSquared( const Vecnt & v ) + { + return( v * v ); + } + + template + inline T maxElement( const Vecnt & v ) + { + T me = std::abs( v[0] ); + for ( unsigned int i=1 ; i + inline T minElement( const Vecnt & v ) + { + T me = std::abs( v[0] ); + for ( unsigned int i=1 ; i + inline T normalize( Vecnt & v ) + { + T norm = length( v ); + if ( std::numeric_limits::epsilon() < norm ) + { + v /= norm; + } + return( norm ); + } + + template + inline float normalize( Vecnt & v ) + { + Vecnt vd(v); + double norm = normalize( vd ); + v = vd; + return( (float)norm ); + } + + template + inline bool operator==( const Vecnt & v0, const Vecnt & v1 ) + { + for (unsigned int i = 0; i < n; i++) + { + if (v0[i] != v1[i]) + { + return(false); + } + } + return(true); + } + + template + inline bool operator==(const Vecnt<1, T> & v0, const Vecnt<1, T> & v1) + { + return (v0[0] == v1[0]); + } + + template + inline bool operator==(const Vecnt<2, T> & v0, const Vecnt<2, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]); + } + + + template + inline bool operator==(const Vecnt<3, T> & v0, const Vecnt<3, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]) && (v0[2] == v1[2]); + } + + template + inline bool operator==(const Vecnt<4, T> & v0, const Vecnt<3, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]) && (v0[2] == v1[2]) && (v0[3] == v1[3]); + } + + template + inline bool operator!=( const Vecnt & v0, const Vecnt & v1 ) + { + return( ! ( v0 == v1 ) ); + } + + template + inline Vecnt operator+( const Vecnt & v0, const Vecnt & v1 ) + { + Vecnt ret(v0); + ret += v1; + return( ret ); + } + + template + inline Vecnt operator-( const Vecnt & v ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Vecnt operator-( const Vecnt & v0, const Vecnt & v1 ) + { + Vecnt ret(v0); + ret -= v1; + return( ret ); + } + + template + inline Vecnt operator*( const Vecnt & v, S s ) + { + Vecnt ret(v); + ret *= s; + return( ret ); + } + + template + inline Vecnt operator*( S s, const Vecnt & v ) + { + return( v * s ); + } + + template + inline T operator*( const Vecnt & v0, const Vecnt & v1 ) + { + T ret(0); + for ( unsigned int i=0 ; i + inline Vecnt operator/( const Vecnt & v, S s ) + { + Vecnt ret(v); + ret /= s; + return( ret ); + } + + template + inline bool operator<(const Vecnt& lhs, const Vecnt& rhs) + { + for ( unsigned int i=0 ; i + inline bool operator>(const Vecnt& lhs, const Vecnt& rhs) + { + for ( unsigned int i=0 ; i + inline Vecnt orthonormal( const Vecnt & v ) + { + MY_STATIC_ASSERT( 1 < n ); + MY_STATIC_ASSERT( ! std::numeric_limits::is_integer ); + + T firstV(-1), secondV(-1); + unsigned int firstI(0), secondI(0); + Vecnt result; + for ( unsigned int i=0 ; i + inline Vecnt orthonormalize( const Vecnt & v0, const Vecnt & v1 ) + { + // determine the orthogonal projection of v1 on v0 : ( v0 * v1 ) * v0 + // and subtract it from v1 resulting in the orthogonalized version of v1 + Vecnt vr = v1 - ( v0 * v1 ) * v0; + vr.normalize(); + // don't assert the general case, because this orthonormalization is far from exact + return( vr ); + } + + template + inline Vecnt<3,T> orthonormalize( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ) + { + Vecnt<3,T> vr = v0 ^ ( v1 ^ v0 ); + vr.normalize(); + return( vr ); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 1 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<1,T> & setVec( Vecnt<1,T> & v, T x ) + { + v[0] = x; + return( v ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 2 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<2,T> & setVec( Vecnt<2,T> & v, T x, T y ) + { + v[0] = x; + v[1] = y; + return( v ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<3,T> operator^( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 ) + { + Vecnt<3,T> ret; + ret[0] = v0[1] * v1[2] - v0[2] * v1[1]; + ret[1] = v0[2] * v1[0] - v0[0] * v1[2]; + ret[2] = v0[0] * v1[1] - v0[1] * v1[0]; + return( ret ); + } + + template + inline Vecnt<3,T> & setVec( Vecnt<3,T> & v, T x, T y, T z ) + { + v[0] = x; + v[1] = y; + v[2] = z; + return( v ); + } + + template + size_t _hash( const Vecnt<3,T> &vertex, const Vecnt<3,T> &base, const Vecnt<3,T> &scale + , size_t numVertices ) + { + #if 1 + size_t value = static_cast(floor( ( vertex - base ) * scale )); + MY_ASSERT( value < numVertices ); + return( value ); + #else + int value = (int) floor( ( vertex - base ) * scale ); + if ( value < 0 ) + { + value = 0; + } + else if ( numVertices <= value ) + { + value = (int) numVertices - 1; + } + return( value ); + #endif + } + + template + inline void smoothNormals( const std::vector > &vertices, const Spherent<3,T> &sphere + , T creaseAngle, std::vector > &normals ) + { + if ( creaseAngle < T(0.01) ) + { + for_each( normals.begin(), normals.end(), std::mem_fun_ref(&Vecnt<3,T>::normalize) ); + } + else + { + // the face normals are un-normalized (for weighting them in the smoothing process) + // and we need the normalized too + std::vector > normalizedNormals(normals); + for_each( normalizedNormals.begin(), normalizedNormals.end() + , std::mem_fun_ref(&Vecnt<3,T>::normalize) ); + + // the tolerance to distinguish different points is a function of the given radius + T tolerance = sphere.getRadius() / 10000; + + // the toleranceVector is used to determine all slices of the bounding box (_hash entries) + // within the given tolerance from the current point + Vecnt<3,T> toleranceVector( tolerance, tolerance, tolerance ); + + // we create a _hash by using diagonal slices through the bounding box (or some approximation + // to it) + T halfWidth = sphere.getRadius() + tolerance; + Vecnt<3,T> hashBase = sphere.getCenter() - Vecnt<3,T>( halfWidth, halfWidth, halfWidth ); + + // The _hash function is a linear function of x, y, and z such that one corner of the + // bounding box (hashBase) maps to the key "0" and the opposite corner maps to the key + // "numVertices()". + T scale = vertices.size() / ( 3 * 2 * halfWidth ) ; + Vecnt<3,T> hashScale( scale, scale, scale ); + + // The "indirect" table is a circularly linked list of indices that are within tolerance of + // each other. + std::vector indirect( vertices.size() ); + + // create a _hash table as a vector of lists of indices into the vertex array + std::vector > hashTable( vertices.size() ); + for ( size_t i=0 ; i::const_iterator hlit=hashTable[hv].begin() + ; ! found && hlit!=hashTable[hv].end() + ; ++hlit ) + { + if ( areSimilar( vertices[i], vertices[*hlit], tolerance ) ) + { + indirect[i] = indirect[*hlit]; + indirect[*hlit] = i; + found = true; + } + } + } + + if ( ! found ) + { + // there is no similar (previous) point, so add it to the hashTable + size_t hashValue = _hash( vertices[i], hashBase, hashScale, vertices.size() ); + hashTable[hashValue].push_back( i ); + indirect[i] = i; + } + } + + // now the indirect vector maps all vertices i to the (similar) vertex j + std::vector > vertexNormals( vertices.size() ); + T cosCreaseAngle = cos( creaseAngle ); + for ( size_t i=0 ; i sum = normals[i]; + for ( size_t j=indirect[i] ; j!=i ; j=indirect[j] ) + { + if ( normalizedNormals[i] * normalizedNormals[j] > cosCreaseAngle ) + { + sum += normals[j]; + } + } + sum.normalize(); + vertexNormals[i] = sum; + } + + // finally, set the vertex normals + normals.swap( vertexNormals ); + } + } + + /*! \brief Compute the scalar triple product (v0 ^ v1) * v2. + **/ + template + inline T scalarTripleProduct( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1, const Vecnt<3,T> &v2 ) + { + return (v0 ^ v1) * v2; + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<4,T> & setVec( Vecnt<4,T> & v, T x, T y, T z, T w ) + { + v[0] = x; + v[1] = y; + v[2] = z; + v[3] = w; + return( v ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/math.h b/apps/bench_shared/dp/math/math.h new file mode 100644 index 00000000..4177e822 --- /dev/null +++ b/apps/bench_shared/dp/math/math.h @@ -0,0 +1,401 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include +#include + +//#include +#include "inc/MyAssert.h" +#include + + +namespace dp +{ + namespace math + { + + // define the stand trig values + #ifdef PI + # undef PI + # undef PI_HALF + # undef PI_QUARTER + #endif + //! constant PI + const float PI = 4 * atan( 1.0f ); + //! constant PI half + const float PI_HALF = 0.5f * PI; + //! constant PI quarter + const float PI_QUARTER = 0.25f * PI; + //! constant square root two + const float SQRT_TWO = sqrt( 2.0f ); + //! constant square root two half + const float SQRT_TWO_HALF = 0.5f * SQRT_TWO; + //! constant square root three + const float SQRT_THREE = sqrt( 3.0f ); + //! constant square root three + const float SQRT_THREE_HALF = 0.5f * SQRT_THREE; + + const double LOG_TWO = log( 2.0f ); + const double ONE_OVER_LOG_TWO = 1.0f / LOG_TWO; + + //! Template to clamp an object of type T to a lower and an upper limit. + /** \returns clamped value of \a v between \a l and \a u */ + template T clamp( T v //!< value to clamp + , T l //!< lower limit + , T u //!< upper limit + ) + { + return std::min(u, std::max(l,v)); + } + + //! Template to cube an object of Type T. + /** \param t value to cube + * \returns triple product of \a t with itself */ + template + inline T cube( const T& t ) + { + return( t * t * t ); + } + + //! Transform an angle in degrees to radians. + /** \returns angle in radians */ + template + inline T degToRad( T angle ) + { + return( angle*(PI/180) ); + } + + template + inline T exp2( T x ) + { + return( (T)pow( T(2), x ) ); + } + + /*! \brief Returns the highest bit set for the specified positive input value */ + template + inline int highestBit( T i ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i ); + int hb = -1; + for ( T h = i ; h ; h>>=1, hb++ ) + ; + return hb; + } + + /*! \brief Returns the highest bit value for the specified positive input value */ + template + inline T highestBitValue(T i) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i ); + T h = 0; + while (i) + { + h = (i & (~i+1)); // grab lowest bit + i &= ~h; // clear lowest bit + } + return h; + } + + /*! \brief Calculate the vertical field of view out of the horizontal field of view */ + inline float horizontalToVerticalFieldOfView( float hFoV, float aspectRatio ) + { + return( 2.0f * atanf( tanf( 0.5f * hFoV ) / aspectRatio ) ); + } + + //! Determine if an integer is a power of two. + /** \returns \c true if \a n is a power of two, otherwise \c false */ + template + inline bool isPowerOfTwo( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + return (n && !(n & (n-1))); + } + + //! Linear interpolation between two values \a v0 and \a v1. + /** v = ( 1 - alpha ) * v0 + alpha * v1 */ + template + inline T lerp( float alpha //!< interpolation parameter + , const T &v0 //!< starting value + , const T &v1 //!< ending value + ) + { + return( (T)( ( 1.0f - alpha ) * v0 + alpha * v1 ) ); + } + + template + inline void lerp( float alpha, const T & v0, const T & v1, T & vr ) + { + vr = (T)( ( 1.0f - alpha ) * v0 + alpha * v1 ); + } + + template + inline double log2( T x ) + { + return( ONE_OVER_LOG_TWO * log( x ) ); + } + + /*! \brief Determine the maximal value out of three. + * \param a A constant reference of the first value to consider. + * \param b A constant reference of the second value to consider. + * \param c A constant reference of the third value to consider. + * \return A constant reference to the maximal value out of \a a, \a b, and \a c. + * \sa min */ + template + inline const T & max( const T &a, const T &b, const T &c ) + { + return( std::max( a, std::max( b, c ) ) ); + } + + /*! \brief Determine the minimal value out of three. + * \param a A constant reference of the first value to consider. + * \param b A constant reference of the second value to consider. + * \param c A constant reference of the third value to consider. + * \return A constant reference to the minimal value out of \a a, \a b, and \a c. + * \sa max */ + template + inline const T & min( const T &a, const T &b, const T &c ) + { + return( std::min( a, std::min( b, c ) ) ); + } + + /*! \brief Determine the smallest power of two equal to or larger than \a n. + * \param n The value to determine the nearest power of two above. + * \return \a n, if \a n is a power of two, otherwise the smallest power of two above \a n. + * \sa powerOfTowBelow */ + template + inline T powerOfTwoAbove( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return( n ); + } + else + { + return highestBitValue(n) << 1; + } + } + + /*! \brief Determine the largest power of two equal to or smaller than \a n. + * \param n The value to determine the nearest power of two below. + * \return \a n, if \a n is a power of two, otherwise the largest power of two below \a n. + * \sa powerOfTowAbove */ + template + inline T powerOfTwoBelow( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return( n ); + } + else + { + return highestBitValue(n); + } + } + + /*! \brief Calculates the nearest power of two for the specified integer. + * \param n Integer for which to calculate the nearest power of two. + * \returns nearest power of two for integer \a n. */ + template + inline T powerOfTwoNearest( T n) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return n; + } + + T prev = highestBitValue(n); // POT below + T next = prev<<1; // POT above + return (next-n) < (n-prev) ? next : prev; // return nearest + } + + //! Transform an angle in radian to degree. + /** \param angle angle in radians + * \returns angle in degrees */ + inline float radToDeg( float angle ) + { + return( angle*180.0f/PI ); + } + + //! Determine the sign of a scalar. + /** \param t scalar value + * \returns sign of \a t */ + template + inline int sign( const T &t ) + { + return( ( t < 0 ) ? -1 : ( ( t > 0 ) ? 1 : 0 ) ); + } + + //! Solve the quadratic equation a*x^2 + b*x + c = 0 + /** \param a Coefficient of the quadratic term. + * \param b Coefficient of the linear term. + * \param c Coefficient of the constant term. + * \param x0 Reference to hold the first of up to two solutions. + * \param x1 Reference to hold the second of up to two solutions. + * \returns The number of real solutions (0, 1, or 2) + * \remarks If 2 is returned, x0 and x1 hold those two solutions. If 1 is returned, it is a double solution + * (turning point), returned in x0. If 0 is returned, there are only two complex conjugated solution, that + * are not calculated here. */ + template + inline unsigned int solveQuadraticEquation( T a, T b, T c, T & x0, T & x1 ) + { + if ( std::numeric_limits::epsilon() < fabs( a ) ) + { + T D = b * b - 4 * a * c; + if ( 0 < D ) + { + D = sqrt( D ); + x0 = 0.5f * ( - b + D ) / a; + x1 = 0.5f * ( - b - D ) / a; + return( 2 ); + } + if ( D < 0 ) + { + return( 0 ); + } + x0 = - 0.5f * b / a; + return( 1 ); + } + if ( std::numeric_limits::epsilon() < fabs( b ) ) + { + x0 = - c / b; + return( 1 ); + } + return( 0 ); + } + + //! Solve the cubic equation a*x^3 + b*x^2 + c*x + d = 0 + /** \param a Coefficient of the cubic term. + * \param b Coefficient of the quadratic term. + * \param c Coefficient of the linear term. + * \param d Coefficient of the constant term. + * \param x0 Reference to hold the first of up to three solutions. + * \param x1 Reference to hold the second of up to three solutions. + * \param x2 Reference to hold the second of up to three solutions. + * \returns The number of real solutions (0, 1, 2, or 3) + * \remarks If the absolute value of \a a is less than the type specific epsilon, the cubic equation is handled + * as a quadratic only. + * If 3 is returned, x0, x1, and x2 hold those three solutions. If 2 is returned, the cubic equation + * in fact was a quadratic one, and x0 and x1 hold those two solutions. If 1 is returned, the cubic equation + * has two conjugate complex, and one real solution; as complex numbers are not treated by this function, it is + * just the real solution returned in x0. If 0 is returned, the cubic equationn again war in fact a quadratic one, + * and there are only two complex conjugated solution, that are not calculated here. */ + template + inline unsigned int solveCubicEquation( T a, T b, T c, T d, T & x0, T & x1, T & x2 ) + { + if ( std::numeric_limits::epsilon() < abs( a ) ) + { + T bOverThreeA = b / ( 3 * a ); + T p = - square( bOverThreeA ) + c / ( 3 * a ); + T q = cube( bOverThreeA ) - T(0.5) * bOverThreeA * c / a + T(0.5) * d / a; + T discriminant = square( q ) + cube( p ); + if ( discriminant < - std::numeric_limits::epsilon() ) // < 0 ? + { + MY_ASSERT( p <= 0 ); + T f = 2 * sqrt( -p ); + T phi = acos( -q / sqrt( - cube( p ) ) ); + x0 = f * cos( phi / 3 ) - bOverThreeA; + x1 = - f * cos( ( phi + PI ) / 3 ) - bOverThreeA; + x2 = - f * cos( ( phi - PI ) / 3 ) - bOverThreeA; + return( 3 ); + } + else if ( std::numeric_limits::epsilon() < discriminant ) // > 0 ? + { + T t = sqrt( discriminant ); + T u = sign( - q + t ) * pow( abs( - q + t ), 1 / T(3) ); + T v = sign( - q - t ) * pow( abs( - q - t ), 1 / T(3) ); + x0 = u + v - bOverThreeA; + return( 1 ); // plus two complex solutions + } + else + { + T t = sign( - q ) * pow( abs( - q ), 1 / T(3) ); + x0 = 2 * t - bOverThreeA; + x1 = -t - bOverThreeA; + x2 = -t - bOverThreeA; + return( 3 ); + } + } + else + { + return( solveQuadraticEquation( b, c, d, x0, x1 ) ); + } + } + + //! Template to square an object of Type T. + /** \param t value to square + * \returns product of \a t with itself */ + template + inline T square( const T& t ) + { + return( t * t ); + } + + /*! \brief Calculate the horizontal field of view out of the vertical field of view */ + inline float verticalToHorizontalFieldOfView( float vFoV, float aspectRatio ) + { + return( 2.0f * atanf( tanf( 0.5f * vFoV ) * aspectRatio ) ); + } + + //! Compares two numerical values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template + inline int compare(T lhs, T rhs) + { + return (lhs == rhs) ? 0 : (lhs < rhs) ? -1 : 1; + } + + // specializations for float and double below + + //! Compares two float values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template<> + inline int compare(float lhs, float rhs) + { + return ((fabs(lhs-rhs) < FLT_EPSILON) ? 0 : (lhs < rhs) ? -1 : 1); + } + + //! Compares two double values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template<> + inline int compare(double lhs, double rhs) + { + return ((fabs(lhs-rhs) < DBL_EPSILON) ? 0 : (lhs < rhs) ? -1 : 1); + } + + DP_MATH_API float _atof( const std::string &str ); + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/src/Math.cpp b/apps/bench_shared/dp/math/src/Math.cpp new file mode 100644 index 00000000..9c216ee5 --- /dev/null +++ b/apps/bench_shared/dp/math/src/Math.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +#include + + +namespace dp +{ + namespace math + { + + DP_MATH_API float _atof( const std::string &str ) + { + int pre = 0; + int post = 0; + float divisor = 1.0f; + bool negative = false; + size_t i = 0; + while ( ( i < str.length() ) && ( ( str[i] == ' ' ) || ( str[i] == '\t' ) ) ) + { + i++; + } + if ( ( i < str.length() ) && ( ( str[i] == '+' ) || ( str[i] == '-' ) ) ) + { + negative = ( str[i] == '-' ); + i++; + } + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + pre = pre * 10 + ( str[i] - 0x30 ); + i++; + } + if ( ( i < str.length() ) && ( str[i] == '.' ) ) + { + i++; + while ( ( i < str.length() ) && isdigit( str[i] ) && ( post <= INT_MAX/10-9 ) ) + { + post = post * 10 + ( str[i] - 0x30 ); + divisor *= 10; + i++; + } + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + i++; + } + } + if ( negative ) + { + pre = - pre; + post = - post; + } + float f = post ? pre + post / divisor : (float)pre; + if ( ( i < str.length() ) && ( ( str[i] == 'd' ) || ( str[i] == 'D' ) || ( str[i] == 'e' ) || ( str[i] == 'E' ) ) ) + { + i++; + negative = false; + if ( ( i < str.length() ) && ( ( str[i] == '+' ) || ( str[i] == '-' ) ) ) + { + negative = ( str[i] == '-' ); + i++; + } + int exponent = 0; + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + exponent = exponent * 10 + ( str[i] - 0x30 ); + i++; + } + if ( negative ) + { + exponent = - exponent; + } + f *= pow( 10.0f, float(exponent) ); + } + #if !defined( NDEBUG ) + float z = (float)atof( str.c_str() ); + MY_ASSERT( fabsf( f - z ) < FLT_EPSILON * std::max( 1.0f, fabsf( z ) ) ); + #endif + return( f ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/src/Matmnt.cpp b/apps/bench_shared/dp/math/src/Matmnt.cpp new file mode 100644 index 00000000..920c6ba0 --- /dev/null +++ b/apps/bench_shared/dp/math/src/Matmnt.cpp @@ -0,0 +1,411 @@ +// Copyright (c) 2012-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +using std::abs; + +namespace dp +{ + namespace math + { + const Mat44f cIdentity44f( { 1.0f, 0.0f, 0.0f, 0.0f + , 0.0f, 1.0f, 0.0f, 0.0f + , 0.0f, 0.0f, 1.0f, 0.0f + , 0.0f, 0.0f, 0.0f, 1.0f } ); + + template + static T _colNorm( const Matmnt &mat ) + { + T max(0); + for ( unsigned int i=0 ; i + static T _rowNorm( const Matmnt &mat ) + { + T max(0); + for ( unsigned int i=0 ; i + static int _maxColumn( const Matmnt &mat ) + { + T max(0); + int col(-1); + for ( unsigned int i=0 ; i + static void _makeReflector( Vecnt<3,T> &v ) + { + T s = sqrt( v * v ); + v[2] += ( v[2] < 0 ) ? -s : s; + s = sqrt( T(2) / ( v * v ) ); + v *= s; + } + + // Apply Householder reflection represented by u to column vectors of M + template + static void _reflectCols( Matmnt &M, const Vecnt &u ) + { + Vecnt v = u * M; + for ( unsigned int i=0 ; i + static void _reflectRows( Matmnt &M, const Vecnt &u ) + { + Vecnt v = M * u; + for ( unsigned int i=0 ; i write to mk + template + static void _decomposeRank1( Matmnt<3,3,T> & mk ) + { + // if rank(mk) is 1 we should find a non-zero column in M + int col = _maxColumn( mk ); + if ( col < 0 ) + { + // rank 0 + setIdentity( mk ); + } + else + { + Vecnt<3,T> v1( mk[0][col], mk[1][col], mk[2][col] ); + _makeReflector( v1 ); + _reflectCols( mk, v1 ); + Vecnt<3,T> v2( mk[2][0], mk[2][1], mk[2][2] ); + _makeReflector( v2 ); + _reflectRows( mk, v2 ); + T s = mk[2][2]; + setIdentity( mk ); + if ( s < 0 ) + { + mk[2][2] = T(-1); + } + _reflectCols( mk, v1 ); + _reflectRows( mk, v2 ); + } + } + + // Find orthogonal factor Q or rank 2 (or less) of mk using adjoint transpose -> write to mk + template + static void _decomposeRank2( Matmnt<3,3,T> & mk, const Matmnt<3,3,T> mAdjTk ) + { + // if rank(mk) is 2, we should find a non-zero column in mAdjTk + int col = _maxColumn( mAdjTk ); + if ( col < 0 ) + { + // rank 1 + _decomposeRank1( mk ); + } + else + { + Vecnt<3,T> v1( mAdjTk[0][col], mAdjTk[1][col], mAdjTk[2][col] ); + _makeReflector( v1 ); + _reflectCols( mk, v1 ); + Vecnt<3,T> v2 = mk[0] ^ mk[1]; + _makeReflector( v2 ); + _reflectRows( mk, v2 ); + if ( mk[0][1] * mk[1][0] < mk[0][0] * mk[1][1] ) + { + T c = mk[1][1] + mk[0][0]; + T s = mk[1][0] - mk[0][1]; + T d = sqrt( c * c + s * s ); + c /= d; + s /= d; + mk[0][0] = c; + mk[0][1] = -s; + mk[1][0] = s; + mk[1][1] = c; + } + else + { + T c = mk[1][1] - mk[0][0]; + T s = mk[1][0] + mk[0][1]; + T d = sqrt( c * c + s * s ); + c /= d; + s /= d; + mk[0][0] = -c; + mk[0][1] = s; + mk[1][0] = s; + mk[1][1] = c; + } + mk[0][2] = T(0); + mk[1][2] = T(0); + mk[2][0] = T(0); + mk[2][1] = T(0); + mk[2][2] = T(1); + _reflectCols( mk, v1 ); + _reflectRows( mk, v2 ); + } + } + + template + static T _polarDecomposition( const Matmnt<3,3,T> &mat, Matmnt<3,3,T> &rot, Matmnt<3,3,T> &sca ) + { + Matmnt<3,3,T> mk = ~mat; + T det; + T eCol; + T mCol = _colNorm( mk ); + T mRow = _rowNorm( mk ); + do + { + Matmnt<3, 3, T> mAdjTk{ mk[1] ^ mk[2], mk[2] ^ mk[0], mk[0] ^ mk[1] }; + det = mk[0] * mAdjTk[0]; + T absDet = abs( det ); + if ( std::numeric_limits::epsilon() < absDet ) + { + T mAdjTCol = _colNorm( mAdjTk ); + T mAdjTRow = _rowNorm( mAdjTk ); + T gamma = sqrt( sqrt( ( mAdjTCol * mAdjTRow ) / ( mCol * mRow ) ) / absDet ); + Matmnt<3,3,T> ek = mk; + mk = T(0.5) * ( gamma * mk + mAdjTk / ( gamma * det ) ); + ek -= mk; + eCol = _colNorm( ek ); + mCol = _colNorm( mk ); + mRow = _rowNorm( mk ); + } + else + { + // rank 2 + _decomposeRank2( mk, mAdjTk ); + break; + } + } while( ( 2 * mCol * std::numeric_limits::epsilon() ) < eCol ); + if ( det < T(0) ) + { + mk = - mk; + } + rot = ~mk; + + sca = mat * mk; + for ( unsigned int i=0 ; i<3 ; ++i ) + { + for ( unsigned int j=i+1 ; j<3 ; ++j ) + { + sca[i][j] = sca[j][i] = T(0.5) * ( sca[i][j] + sca[j][i] ); + } + } + return( det ); + } + + template + static Vecnt<3,T> _spectralDecomposition( const Matmnt<3,3,T> &mat, Matmnt<3,3,T> &u ) + { + setIdentity( u ); + Vecnt<3,T> diag( mat[0][0], mat[1][1], mat[2][2] ); + Vecnt<3,T> offd( mat[2][1], mat[0][2], mat[1][0] ); + bool done = false; + for ( int sweep=20 ; 0::epsilon() ); + if ( !done ) + { + done = true; + for ( int i=2 ; 0<=i ; i-- ) + { + int p = (i+1)%3; + int q = (p+1)%3; + T absOffDi = abs(offd[i]); + if ( std::numeric_limits::epsilon() < absOffDi ) + { + done = false; + T g = 100 * absOffDi; + T h = diag[q] - diag[p]; + T absh = abs( h ); + T t; + if ( absh + g == absh ) + { + t = offd[i] / h; + } + else + { + T theta = T(0.5) * h / offd[i]; + t = 1 / ( abs(theta) + sqrt(theta*theta+1) ); + if ( theta < 0 ) + { + t = -t; + } + } + T c = 1 / sqrt( t*t+1); + T s = t * c; + T tau = s / (c + 1); + T ta = t * offd[i]; + offd[i] = 0; + diag[p] -= ta; + diag[q] += ta; + T offdq = offd[q]; + offd[q] -= s * ( offd[p] + tau * offd[q] ); + offd[p] += s * ( offdq - tau * offd[p] ); + for ( int j=2 ; 0<=j ; j-- ) + { + T a = u[p][j]; + T b = u[q][j]; + u[p][j] -= s * ( b + tau * a ); + u[q][j] += s * ( a - tau * b ); + } + } + } + } + } + return( diag ); + } + + void decompose( const Matmnt<3,3,float> &mat, Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ) + { +#if 0 + + Matmnt<3,3,float> rot, sca; + float det = _polarDecomposition( mat, rot, sca ); +#if !defined(NDEBUG) + { + Matmnt<3,3,float> diff = mat - (float)sign(det) * sca * rot; + float eps = std::max( 1.0f, maxElement( sca ) ) * std::numeric_limits::epsilon(); + } +#endif + + orientation = Quatt(rot); + + // get the scaling out of sca + Matmnt<3,3,float> so; + scaling = (float)sign(det) * _spectralDecomposition( sca, so ); +#if !defined( NDEBUG ) + { + Matmnt<3,3,float> k( scaling[0], 0, 0, 0, scaling[1], 0, 0, 0, scaling[2] ); + Matmnt<3,3,float> diff = sca - ~so * k * so; + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + } +#endif + + scaleOrientation = Quatt( so ); + +#if !defined( NDEBUG ) + { + Matmnt<3,3,float> ms( scaling[0], 0, 0, 0, scaling[1], 0, 0, 0, scaling[2] ); + Matmnt<3,3,float> mso( so ); + Matmnt<3,3,float> diff = mat - ~mso * ms * mso * rot; + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + } +#endif + +#else + + Matmnt<3,3,double> rot, sca; + double det = _polarDecomposition( Matmnt<3,3,double>(mat), rot, sca ); +#if !defined(NDEBUG) + { + Matmnt<3,3,double> diff = Matmnt<3,3,double>(mat) - sca * rot; + double eps = std::max( 1.0, maxElement( sca ) ) * std::numeric_limits::epsilon(); + } +#endif + + orientation = Quatt(Quatt(rot)); + + // get the scaling out of sca + Matmnt<3,3,double> so; + scaling = _spectralDecomposition( sca, so ); +#if !defined( NDEBUG ) + { + Matmnt<3, 3, double> k( { (double)scaling[0], 0.0, 0.0, 0.0, (double)scaling[1], 0.0, 0.0, 0.0, (double)scaling[2] } ); + Matmnt<3,3,double> diff = sca - ~so * k * so; + double eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + int a = 0; + } +#endif + + scaleOrientation = Quatt(Quatt( so )); + +#if !defined( NDEBUG ) + Matmnt<3, 3, double> ms( { (double)scaling[0], 0.0, 0.0, 0.0, (double)scaling[1], 0.0, 0.0, 0.0, (double)scaling[2] } ); + Matmnt<3,3,double> mso( so ); + Matmnt<3,3,float> diff = mat - Matmnt<3,3,float>(~mso * ms * mso * rot); + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); +#endif + +#endif + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/src/Quatt.cpp b/apps/bench_shared/dp/math/src/Quatt.cpp new file mode 100644 index 00000000..6b79158a --- /dev/null +++ b/apps/bench_shared/dp/math/src/Quatt.cpp @@ -0,0 +1,83 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +namespace dp +{ + namespace math + { + template + Quatt _lerp( T alpha, const Quatt & q0, const Quatt & q1 ) + { + // cosine theta = dot product of q0 and q1 + T cosTheta = q0[0] * q1[0] + q0[1] * q1[1] + q0[2] * q1[2] + q0[3] * q1[3]; + + // if q1 is on opposite hemisphere from q0, use -q1 instead + bool flip = ( cosTheta < 0 ); + if ( flip ) + { + cosTheta = - cosTheta; + } + + T beta; + if ( 1 - cosTheta < std::numeric_limits::epsilon() ) + { + // if q1 is (within precision limits) the same as q0, just linear interpolate between q0 and q1. + beta = 1 - alpha; + } + else + { + // normal case + T theta = acos( cosTheta ); + T oneOverSinTheta = 1 / sin( theta ); + beta = sin( theta * ( 1 - alpha ) ) * oneOverSinTheta; + alpha = sin( theta * alpha ) * oneOverSinTheta; + } + + if ( flip ) + { + alpha = - alpha; + } + + return( Quatt( beta * q0[0] + alpha * q1[0] + , beta * q0[1] + alpha * q1[1] + , beta * q0[2] + alpha * q1[2] + , beta * q0[3] + alpha * q1[3] ) ); + } + + Quatt lerp( float alpha, const Quatt & q0, const Quatt & q1 ) + { + return( _lerp( alpha, q0, q1 ) ); + } + + void lerp( float alpha, const Quatt & q0, const Quatt & q1, Quatt &qr ) + { + qr = _lerp( alpha, q0, q1 ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/dp/math/src/Trafo.cpp b/apps/bench_shared/dp/math/src/Trafo.cpp new file mode 100644 index 00000000..844f1200 --- /dev/null +++ b/apps/bench_shared/dp/math/src/Trafo.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2002-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + + +#include + + +namespace dp +{ + namespace math + { + + Trafo::Trafo( void ) + : m_center( Vec3f( 0.0f, 0.0f, 0.0f ) ) + , m_orientation( Quatf( 0.0f, 0.0f, 0.0f, 1.0f ) ) + , m_scaleOrientation( Quatf( 0.0f, 0.0f, 0.0f, 1.0f ) ) + , m_scaling( Vec3f( 1.0f, 1.0f, 1.0f ) ) + , m_translation( Vec3f( 0.0f, 0.0f, 0.0f ) ) + , m_matrixValid( false ) + , m_decompositionValid( true ) + { + }; + + Trafo::Trafo( const Trafo &rhs ) + : m_matrixValid( rhs.m_matrixValid ) + , m_decompositionValid( rhs.m_decompositionValid ) + { + if ( m_matrixValid ) + { + m_matrix = rhs.m_matrix; + } + if ( m_decompositionValid ) + { + m_center = rhs.m_center; + m_orientation = rhs.m_orientation; + m_scaleOrientation = rhs.m_scaleOrientation; + m_scaling = rhs.m_scaling; + m_translation = rhs.m_translation; + } + } + + Trafo & Trafo::operator=( const Trafo & rhs ) + { + MY_ASSERT( rhs.m_matrixValid || rhs.m_decompositionValid ); + if (&rhs != this) + { + if ( rhs.m_decompositionValid ) + { + m_center = rhs.m_center; + m_orientation = rhs.m_orientation; + m_scaleOrientation = rhs.m_scaleOrientation; + m_scaling = rhs.m_scaling; + m_translation = rhs.m_translation; + } + if ( rhs.m_matrixValid ) + { + m_matrix = rhs.m_matrix; + } + + m_matrixValid = rhs.m_matrixValid; + m_decompositionValid = rhs.m_decompositionValid; + } + return *this; + } + + const Mat44f& Trafo::getMatrix( void ) const + { + MY_ASSERT( m_matrixValid || m_decompositionValid ); + + if ( !m_matrixValid ) + { + // Calculates -C * SO^-1 * S * SO * R * C * T, with + // C being the center translation + // SO being the scale orientation + // S being the scale + // R being the rotation + // T being the translation + Mat33f soInv( -m_scaleOrientation ); + Mat44f m0( Vec4f( soInv[0], 0.0f ) + , Vec4f( soInv[1], 0.0f ) + , Vec4f( soInv[2], 0.0f ) + , Vec4f( -m_center*soInv, 1.0f ) ); + Mat33f rot( m_scaleOrientation * m_orientation ); + Mat44f m1( Vec4f( m_scaling[0] * rot[0], 0.0f ) + , Vec4f( m_scaling[1] * rot[1], 0.0f ) + , Vec4f( m_scaling[2] * rot[2], 0.0f ) + , Vec4f( m_center + m_translation, 1.0f ) ); + m_matrix = m0 * m1; + m_matrixValid = true; + } + return( m_matrix ); + } + + Mat44f Trafo::getInverse( void ) const + { + dp::math::Mat44f inverse = getMatrix(); // Automatically makes the matrix valid. + if ( !inverse.invert() ) + { + // Inverting the matrix directly failed. + // Try the more robust but also more expensive decomposition. + // (Note that this still barely handles a uniform scaling of 2.2723e-6, + // while the direct matrix inversion already dropped below the + // std::numeric_limits::epsilon() of 2.2204460492503131e-016 for the determinant.) + if ( !m_decompositionValid ) + { + decompose(); + } + // Calculates T^-1 * C^-1 * R^-1 * SO^-1 * S^-1 * SO * -C^-1, with + // C being the center translation + // SO being the scale orientation + // S being the scale + // R being the rotation + // T being the translation + Mat33f rot( -m_orientation * -m_scaleOrientation ); + Mat44f m0; + m0[0] = Vec4f( rot[0], 0.0f ); + m0[1] = Vec4f( rot[1], 0.0f ); + m0[2] = Vec4f( rot[2], 0.0f ); + m0[3] = Vec4f( ( -m_center - m_translation ) * rot, 1.0f ); + + Mat33f so( m_scaleOrientation ); + + Mat44f m1; + m1[0] = Vec4f( so[0], 0.0f ) / m_scaling[0]; + m1[1] = Vec4f( so[1], 0.0f ) / m_scaling[1]; + m1[2] = Vec4f( so[2], 0.0f ) / m_scaling[2]; + m1[3] = Vec4f( m_center, 1.0f ); + + inverse = m0 * m1; + } + return inverse; + } + + void Trafo::setIdentity( void ) + { + m_center = Vec3f( 0.0f, 0.0f, 0.0f ); + m_orientation = Quatf( 0.0f, 0.0f, 0.0f, 1.0f ); + m_scaling = Vec3f( 1.0f, 1.0f, 1.0f ); + m_scaleOrientation = Quatf( 0.0f, 0.0f, 0.0f, 1.0f ); + m_translation = Vec3f( 0.0f, 0.0f, 0.0f ); + m_matrixValid = false; + m_decompositionValid = true; + } + + void Trafo::setMatrix( const Mat44f &matrix ) + { + m_matrix = matrix; + m_matrixValid = true; + m_decompositionValid = false; + } + + void Trafo::decompose() const + { + MY_ASSERT( m_matrixValid ); + MY_ASSERT( m_decompositionValid == false ); + + dp::math::decompose( m_matrix, m_translation, m_orientation, m_scaling, m_scaleOrientation); + + m_decompositionValid = true; + } + + + bool Trafo::operator==( const Trafo &t ) const + { + if ( m_decompositionValid && t.m_decompositionValid ) + { + return( + ( getCenter() == t.getCenter() ) + && ( getOrientation() == t.getOrientation() ) + && ( getScaling() == t.getScaling() ) + && ( getScaleOrientation() == t.getScaleOrientation() ) + && ( getTranslation() == t.getTranslation() ) ); + } + else + { + return getMatrix() == t.getMatrix(); + } + + } + + Trafo lerp( float alpha, const Trafo &t0, const Trafo &t1 ) + { + Trafo t; + t.setCenter( lerp( alpha, t0.getCenter(), t1.getCenter() ) ); + t.setOrientation( lerp( alpha, t0.getOrientation(), t1.getOrientation() ) ); + t.setScaling( lerp( alpha, t0.getScaling(), t1.getScaling() ) ); + t.setScaleOrientation( lerp( alpha, t0.getScaleOrientation(), t1.getScaleOrientation() ) ); + t.setTranslation( lerp( alpha, t0.getTranslation(), t1.getTranslation() ) ); + return( t ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared/imgui/LICENSE b/apps/bench_shared/imgui/LICENSE new file mode 100644 index 00000000..b28ef225 --- /dev/null +++ b/apps/bench_shared/imgui/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 Omar Cornut and ImGui contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/bench_shared/imgui/imconfig.h b/apps/bench_shared/imgui/imconfig.h new file mode 100644 index 00000000..8abf26f3 --- /dev/null +++ b/apps/bench_shared/imgui/imconfig.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// Most options (memory allocation, clipboard callbacks, etc.) can be set at runtime via the ImGuiIO structure - ImGui::GetIO(). +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h) +// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" +// Note that options such as IMGUI_API, IM_VEC2_CLASS_EXTRA or ImDrawIdx needs to be defined consistently everywhere you include imgui.h, not only for the imgui*.cpp compilation units. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. +// DAR HACK Compiling directly into the application, not using DLLs. Not doing this will lead to "incosistent DLL definitions". +//#ifndef IMGUI_API +//# if imgui_EXPORTS /* Set by CMake */ +//# if defined( _WIN32 ) || defined( _WIN64 ) +//# define IMGUI_API __declspec( dllexport ) +//# endif +//# else /* imgui_EXPORTS */ +//# if defined( _WIN32 ) || defined( _WIN64 ) +//# define IMGUI_API __declspec( dllimport ) +//# endif +//# endif +//#endif + +//---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce likelihood of using already obsolete function/names +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Don't implement default handlers for Windows (so as not to link with certain functions) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // Don't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // Don't use and link with ImmGetContext/ImmSetCompositionWindow. + +//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) +//---- It is very strongly recommended to NOT disable the demo windows. Please read the comment at the top of imgui_demo.cpp. +//#define IMGUI_DISABLE_DEMO_WINDOWS + +//---- Don't implement ImFormatString(), ImFormatStringV() so you can reimplement them yourself. +//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +//---- Include imgui_user.h at the end of imgui.h as a convenience +//#define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Pack colors to BGRA8 instead of RGBA8 (if you needed to convert from one to another anyway) +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Implement STB libraries in a namespace to avoid linkage conflicts (defaults to global namespace) +//#define IMGUI_STB_NAMESPACE ImGuiStb + +//---- Define constructor and implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ + +//---- Use 32-bit vertex indices (instead of default 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it. +//#define ImDrawIdx unsigned int + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +/* +namespace ImGui +{ + void MyFunction(const char* name, const MyMatrix44& v); +} +*/ diff --git a/apps/bench_shared/imgui/imgui.cpp b/apps/bench_shared/imgui/imgui.cpp new file mode 100644 index 00000000..d6d9f40f --- /dev/null +++ b/apps/bench_shared/imgui/imgui.cpp @@ -0,0 +1,13277 @@ +// dear imgui, v1.60 WIP +// (main code and documentation) + +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui +// Releases change-log at https://github.com/ocornut/imgui/releases +// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// This library is free but I need your support to sustain development and maintenance. +// If you work for a company, please consider financial support, see Readme. For individuals: https://www.patreon.com/imgui + +/* + + Index + - MISSION STATEMENT + - END-USER GUIDE + - PROGRAMMER GUIDE (read me!) + - Read first + - How to update to a newer version of Dear ImGui + - Getting started with integrating Dear ImGui in your code/engine + - Using gamepad/keyboard navigation [BETA] + - API BREAKING CHANGES (read me when you update!) + - ISSUES & TODO LIST + - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + - How can I help? + - How can I display an image? What is ImTextureID, how does it works? + - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels and the ID stack. + - How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + - How can I load a different font than the default? + - How can I easily use icons in my application? + - How can I load multiple fonts? + - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? + - How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + - I integrated Dear ImGui in my engine and the text or lines are blurry.. + - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + - ISSUES & TODO-LIST + - CODE + + + MISSION STATEMENT + ================= + + - Easy to use to create code-driven and data-driven tools + - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools + - Easy to hack and improve + - Minimize screen real-estate usage + - Minimize setup and maintenance + - Minimize state storage on user side + - Portable, minimize dependencies, run on target (consoles, phones, etc.) + - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, opening a tree node + for the first time, etc. but a typical frame won't allocate anything) + + Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: + - Doesn't look fancy, doesn't animate + - Limited layout features, intricate layouts are typically crafted in code + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) + - Controls are automatically adjusted for OSX to match standard OSX text editing operations. + - Gamepad navigation: see suggested mappings in imgui.h ImGuiNavInput_ + + + PROGRAMMER GUIDE + ================ + + READ FIRST + + - Read the FAQ below this section! + - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention + on your side, no state duplication, less sync, less bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + + - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed from the public API. + If you have a problem with a missing function/symbols, search for its name in the code, there will likely be a comment about it. + Please report any issue to the GitHub page! + - Try to keep your copy of dear imgui reasonably up to date. + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + + - Add the Dear ImGui source files to your projects, using your preferred build system. + It is recommended you build the .cpp files as part of your project and not as a library. + - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types. + - See examples/ folder for standalone sample applications. + - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + + - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize + (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic + integration you don't need to worry about it all. + - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory. + - Every frame: + - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.) + - Call ImGui::NewFrame() to begin the frame + - You can use any ImGui function you want between NewFrame() and Render() + - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler. + (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break) + - All rendering information are stored into command-lists until ImGui::Render() is called. + - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. + - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases + of your own application. + - Refer to the examples applications in the examples/ folder for instruction on how to setup your code. + - A minimal application skeleton may be: + + // Application init + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = 1920.0f; + io.DisplaySize.y = 1280.0f; + // TODO: Fill others settings of the io structure later. + + // Load texture atlas (there is a default font so you don't need to care about choosing a font yet) + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height); + // TODO: At this points you've got the texture data and you need to upload that your your graphic system: + MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA) + // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer. + io.Fonts->TexID = (void*)texture; + + // Application main loop + while (true) + { + // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.) + ImGuiIO& io = ImGui::GetIO(); + io.DeltaTime = 1.0f/60.0f; + io.MousePos = mouse_pos; + io.MouseDown[0] = mouse_button_0; + io.MouseDown[1] = mouse_button_1; + + // Call NewFrame(), after this point you can use ImGui::* functions anytime + ImGui::NewFrame(); + + // Most of your application code here + MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); + MyGameRender(); // may use any ImGui functions as well! + + // Render & swap video buffers + ImGui::Render(); + MyImGuiRenderFunction(ImGui::GetDrawData()); + SwapBuffers(); + } + + // Shutdown + ImGui::DestroyContext(); + + + - A minimal render function skeleton may be: + + void void MyRenderFunction(ImDrawData* draw_data) + { + // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + // TODO: Setup viewport, orthographic projection matrix + // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // The texture for the draw call is specified by pcmd->TextureId. + // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization. + MyEngineBindTexture(pcmd->TextureId); + + // We are using scissoring to clip some objects. All low-level graphics API supports it. + // If your engine doesn't support scissoring yet, you will get some small glitches (some elements outside their bounds) which you can fix later. + MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices. + MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + } + + - The examples/ folders contains many functional implementation of the pseudo-code above. + - When calling NewFrame(), the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'io.WantTextInput' flags are updated. + They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide + mouse inputs from the rest of your application. Read the FAQ below for more information about those flags. + + USING GAMEPAD/KEYBOARD NAVIGATION [BETA] + + - Ask questions and report issues at https://github.com/ocornut/imgui/issues/787 + - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable. + - Keyboard: + - Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays. + - When keyboard navigation is active (io.NavActive + NavFlags_EnableKeyboard), the io.WantCaptureKeyboard flag will be set. + For more advanced uses, you may want to read from: + - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. + - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used). + - or query focus information with e.g. IsWindowFocused(), IsItemFocused() etc. functions. + Please reach out if you think the game vs navigation input sharing could be improved. + - Gamepad: + - Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). + - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: + 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. + - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. + Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, maybe a power curve, etc.). + - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. + Please reach out if you think the game vs navigation input sharing could be improved. + - Mouse: + - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback. + - Consoles/Tablet/Phone users: Consider using Synergy host (on your computer) + uSynergy.c (in your console/tablet/phone app) to use your PC mouse/keyboard. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiNavFlags_MoveMouse flag in io.NavFlags. + Enabling ImGuiNavFlags_MoveMouse instructs dear imgui to move your mouse cursor along with navigation movements. + When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantMoveMouse' to notify you that it wants the mouse cursor to be moved. + When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that. + (If you set the ImGuiNavFlags_MoveMouse flag but don't honor 'io.WantMoveMouse' properly, imgui will misbehave as it will see your mouse as moving back and forth.) + (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want + to set a boolean to ignore your other external mouse positions until the external source is moved again.) + + + API BREAKING CHANGES + ==================== + + Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. + Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. + Also read releases logs https://github.com/ocornut/imgui/releases for more details. + + - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display. + - 2018/02/07 (1.60) - reorganized context handling to be more explicit, + - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. + - removed Shutdown() function, as DestroyContext() serve this purpose. + - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwhise CreateContext() will create its own font atlas instance. + - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. + - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. + - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. + - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. + - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side. + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. + Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). + - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). + - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame. + - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. + - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. + However if your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color. + ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) + { + float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; + return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); + } + If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDraw::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. + you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. + this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + became: + ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + argument 'cmd_lists' -> 'draw_data->CmdLists' + argument 'cmd_lists_count' -> 'draw_data->CmdListsCount' + ImDrawList 'commands' -> 'CmdBuffer' + ImDrawList 'vtx_buffer' -> 'VtxBuffer' + ImDrawList n/a -> 'IdxBuffer' (new) + ImDrawCmd 'vtx_count' -> 'ElemCount' + ImDrawCmd 'clip_rect' -> 'ClipRect' + ImDrawCmd 'user_callback' -> 'UserCallback' + ImDrawCmd 'texture_id' -> 'TextureId' + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + this sequence: + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + // + became: + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + // + io.Fonts->TexID = (your_texture_identifier); + you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + it is now recommended that you sample the font texture with bilinear interpolation. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. + (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + + ISSUES & TODO-LIST + ================== + See TODO.txt + + + FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + ====================================== + + Q: How can I help? + A: - If you are experienced with Dear ImGui and C++, look at the github issues, or TODO.txt and see how you want/can help! + - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README. + - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. + You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers. + But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. + - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately). + + Q: How can I display an image? What is ImTextureID, how does it works? + A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function. + Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry! + It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc. + At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render. + Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing. + (c++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!) + To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions. + Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. + You may call ImGui::ShowMetricsWindow() to explore active draw lists and visualize/understand how the draw data is generated. + It is your responsibility to get textures uploaded to your GPU. + + Q: Can I have multiple widgets with the same label? Can I have widget without a label? + A: Yes. A primer on labels and the ID stack... + + - Elements that are typically not clickable, such as Text() items don't need an ID. + + - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui often needs to remember what is + the "active" widget). to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. + + Button("OK"); // Label = "OK", ID = hash of "OK" + Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" + + - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" + in two different windows or in two different locations of a tree. + + - If you have a same ID twice in the same location, you'll have a conflict: + + Button("OK"); + Button("OK"); // ID collision! Both buttons will be treated as the same. + + Fear not! this is easy to solve and there are many ways to solve it! + + - When passing a label you can optionally specify extra unique ID information within string itself. + Use "##" to pass a complement to the ID that won't be visible to the end-user. + This helps solving the simple collision cases when you know which items are going to be created. + + Button("Play"); // Label = "Play", ID = hash of "Play" + Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above) + Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above) + + - If you want to completely hide the label, but still need an ID: + + Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!) + + - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels. + For example you may want to include varying information in a window title bar, but windows are uniquely identified by their ID.. + Use "###" to pass a label that isn't part of ID: + + Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" + Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) + + sprintf(buf, "My game (%f FPS)###MyGame", fps); + Begin(buf); // Variable label, ID = hash of "MyGame" + + - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window. + This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements. + You can push a pointer, a string or an integer value. Remember that ID are formed from the concatenation of _everything_ in the ID stack! + + for (int i = 0; i < 100; i++) + { + PushID(i); + Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj); + Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj->Name); + Button("Click"); // Label = "Click", ID = hash of string + "label" (unique) + PopID(); + } + + - More example showing that you can stack multiple prefixes into the ID stack: + + Button("Click"); // Label = "Click", ID = hash of "Click" + PushID("node"); + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + PushID(my_ptr); + Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click" + PopID(); + PopID(); + + - Tree nodes implicitly creates a scope for you by calling PushID(). + + Button("Click"); // Label = "Click", ID = hash of "Click" + if (TreeNode("node")) + { + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + TreePop(); + } + + - When working with trees, ID are used to preserve the open/close state of each tree node. + Depending on your use cases you may want to use strings, indices or pointers as ID. + e.g. when displaying a single object that may change over time (dynamic 1-1 relationship), using a static string as ID will preserve your + node open/closed state when the targeted object change. + e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. + experiment and see what makes more sense! + + Q: How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure. + - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application. + - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is + also generally ok, as the bool toggles fairly rarely and you don't generally expect to interact with either Dear ImGui or your application during + the same frame when that transition occurs. Dear ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, + so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered. + (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically + have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs + were for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) + + Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13) + A: Use the font atlas to load the TTF/OTF file you want: + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + + New programmers: remember that in C/C++ and most programming languages if you want to use a backslash \ in a string literal you need to write a double backslash "\\": + io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG + io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT + io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT + + Q: How can I easily use icons in my application? + A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your + strings. Read 'How can I load multiple fonts?' and the file 'misc/fonts/README.txt' for instructions and useful header files. + + Q: How can I load multiple fonts? + A: Use the font atlas to pack them into a single texture: + (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.) + + ImGuiIO& io = ImGui::GetIO(); + ImFont* font0 = io.Fonts->AddFontDefault(); + ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + // the first loaded font gets used by default + // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime + + // Options + ImFontConfig config; + config.OversampleH = 3; + config.OversampleV = 1; + config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up + config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); + + // Combine multiple fonts into one (e.g. for icon fonts) + ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontDefault(); + io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs + + Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? + A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. + + // Add default Japanese ranges + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); + + // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need) + ImVector ranges; + ImFontAtlas::GlyphRangesBuilder builder; + builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters) + builder.AddChar(0x7262); // Add a specific character + builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges + builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); + + All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax. + Specifying literal in your source code using a local code page (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work! + Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. + + Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that. + For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. + The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly. + + Q: How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' + so you don't rely on the default globals. + + Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + A: - You can create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, + push a ImGuiCol_WindowBg with zero alpha, then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. + - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows. + - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData. + + Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. + A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). + Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. + + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + A: You are probably mishandling the clipping rectangles in your render function. + Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). + + + - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. + this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". + - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle + of a deep nested inner loop in your code. + - tip: you can call Render() multiple times (e.g for VR renders). + - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui! + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // toupper, isprint +#include // NULL, malloc, free, qsort, atoi +#include // vsnprintf, sscanf, printf +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#define IMGUI_DEBUG_NAV_SCORING 0 +#define IMGUI_DEBUG_NAV_RECTS 0 + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great! +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' // +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#endif + +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif + +//------------------------------------------------------------------------- +// Forward Declarations +//------------------------------------------------------------------------- + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); + +static ImFont* GetDefaultFont(); +static void SetCurrentWindow(ImGuiWindow* window); +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); +static ImGuiWindow* FindHoveredWindow(); +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); +static void CheckStacksSize(ImGuiWindow* window, bool write); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); +static void AddWindowToDrawData(ImVector* out_list, ImGuiWindow* window); +static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); + +static ImGuiWindowSettings* AddWindowSettings(const char* name); + +static void LoadIniSettingsFromDisk(const char* ini_filename); +static void LoadIniSettingsFromMemory(const char* buf); +static void SaveIniSettingsToDisk(const char* ini_filename); +static void SaveIniSettingsToMemory(ImVector& out_buf); +static void MarkIniSettingsDirty(ImGuiWindow* window); + +static ImRect GetViewportRect(); + +static void ClosePopupToLevel(int remaining); +static ImGuiWindow* GetFrontMostModalRootWindow(); + +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size); +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size); +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2); +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format); + +namespace ImGui +{ +static void NavUpdate(); +static void NavUpdateWindowing(); +static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id); + +static void UpdateMovingWindow(); +static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]); +static void FocusFrontMostActiveWindow(ImGuiWindow* ignore_window); +} + +//----------------------------------------------------------------------------- +// Platform dependent default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(void* user_data); +static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +// Current context pointer. Implicitely used by all ImGui functions. Always assumed to be != NULL. +// CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext(). +// If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file. +// ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can: +// - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) +#ifndef GImGui +ImGuiContext* GImGui = NULL; +#endif + +// Memory Allocator functions. Use SetAllocatorFunctions() to change them. +// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file. +// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); } +#else +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); } +#endif + +static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper; +static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper; +static void* GImAllocatorUserData = NULL; +static size_t GImAllocatorActiveAllocationsCount = 0; + +//----------------------------------------------------------------------------- +// User facing structures +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + Alpha = 1.0f; // Global alpha applies to everything in ImGui + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns + ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + DisplaySafeAreaPadding = ImVec2(4,4); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. + AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + ImGui::StyleColorsClassic(this); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); + MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); +} + +ImGuiIO::ImGuiIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + + // Settings + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f/60.0f; + NavFlags = 0x00; + IniSavingRate = 5.0f; + IniFilename = "imgui.ini"; + LogFilename = "imgui_log.txt"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; + KeyRepeatDelay = 0.250f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); + + // Advanced/subtle behaviors +#ifdef __APPLE__ + OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + OptMacOSXBehaviors = false; +#endif + OptCursorBlink = true; + + // Settings (User Functions) + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; + ImeWindowHandle = NULL; + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + RenderDrawListsFn = NULL; +#endif + + // Input (NB: we already have memset zero the entire structure) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; +} + +// Pass in translated ASCII characters for text input. +// - with glfw you can get those from the callback set in glfwSetCharCallback() +// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message +void ImGuiIO::AddInputCharacter(ImWchar c) +{ + const int n = ImStrlenW(InputCharacters); + if (n + 1 < IM_ARRAYSIZE(InputCharacters)) + { + InputCharacters[n] = c; + InputCharacters[n+1] = '\0'; + } +} + +void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +{ + // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more + const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); + ImWchar wchars[wchars_buf_len]; + ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); + for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) + AddInputCharacter(wchars[i]); +} + +//----------------------------------------------------------------------------- +// HELPERS +//----------------------------------------------------------------------------- + +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 + +// Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) +{ + ImVec2 ap = p - a; + ImVec2 ab_dir = b - a; + float ab_len = sqrtf(ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y); + ab_dir *= 1.0f / ab_len; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + if (dot > ab_len) + return b; + return a + ab_dir * dot; +} + +bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) +{ + ImVec2 v0 = b - a; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + out_w = (v0.x * v2.y - v2.x * v0.y) / denom; + out_u = 1.0f - out_v - out_w; +} + +ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + ImVec2 proj_ab = ImLineClosestPoint(a, b, p); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + return proj_bc; + return proj_ca; +} + +int ImStricmp(const char* str1, const char* str2) +{ + int d; + while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + return d; +} + +int ImStrnicmp(const char* str1, const char* str2, size_t count) +{ + int d = 0; + while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + return d; +} + +void ImStrncpy(char* dst, const char* src, size_t count) +{ + if (count < 1) return; + strncpy(dst, src, count); + dst[count-1] = 0; +} + +char* ImStrdup(const char *str) +{ + size_t len = strlen(str) + 1; + void* buf = ImGui::MemAlloc(len); + return (char*)memcpy(buf, (const void*)str, len); +} + +char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + for ( ; str < str_end; str++) + if (*str == c) + return (char*)str; + return NULL; +} + +int ImStrlenW(const ImWchar* str) +{ + int n = 0; + while (*str++) n++; + return n; +} + +const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) +{ + if (!needle_end) + needle_end = needle + strlen(needle); + + const char un0 = (char)toupper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) + { + if (toupper(*haystack) == un0) + { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + return NULL; +} + +static const char* ImAtoi(const char* src, int* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + int v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS +int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + int w = vsnprintf(buf, buf_size, fmt, args); + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) +{ + int w = vsnprintf(buf, buf_size, fmt, args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +// Pass data_size==0 for zero-terminated strings +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImU32 ImHash(const void* data, int data_size, ImU32 seed) +{ + static ImU32 crc32_lut[256] = { 0 }; + if (!crc32_lut[1]) + { + const ImU32 polynomial = 0xEDB88320; + for (ImU32 i = 0; i < 256; i++) + { + ImU32 crc = i; + for (ImU32 j = 0; j < 8; j++) + crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); + crc32_lut[i] = crc; + } + } + + seed = ~seed; + ImU32 crc = seed; + const unsigned char* current = (const unsigned char*)data; + + if (data_size > 0) + { + // Known size + while (data_size--) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; + } + else + { + // Zero-terminated string + while (unsigned char c = *current++) + { + // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. + // Because this syntax is rarely used we are optimizing for the common case. + // - If we reach ### in the string we discard the hash so far and reset to the seed. + // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. + if (c == '#' && current[0] == '#' && current[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + return ~crc; +} + +//----------------------------------------------------------------------------- +// ImText* helpers +//----------------------------------------------------------------------------- + +// Convert UTF-8 to 32-bits character, process single character input. +// Based on stb_from_utf8() from github.com/nothings/stb/ +// We handle UTF-8 decoding error by skipping forward. +int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) +{ + unsigned int c = (unsigned int)-1; + const unsigned char* str = (const unsigned char*)in_text; + if (!(*str & 0x80)) + { + c = (unsigned int)(*str++); + *out_char = c; + return 1; + } + if ((*str & 0xe0) == 0xc0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 2) return 1; + if (*str < 0xc2) return 2; + c = (unsigned int)((*str++ & 0x1f) << 6); + if ((*str & 0xc0) != 0x80) return 2; + c += (*str++ & 0x3f); + *out_char = c; + return 2; + } + if ((*str & 0xf0) == 0xe0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 3) return 1; + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; + if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x0f) << 12); + if ((*str & 0xc0) != 0x80) return 3; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 3; + c += (*str++ & 0x3f); + *out_char = c; + return 3; + } + if ((*str & 0xf8) == 0xf0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 4) return 1; + if (*str > 0xf4) return 4; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; + if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x07) << 18); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 12); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 4; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return 4; + *out_char = c; + return 4; + } + *out_char = 0; + return 0; +} + +int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + ImWchar* buf_out = buf; + ImWchar* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) + char_count++; + } + return char_count; +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c >= 0xdc00 && c < 0xe000) return 0; + if (c >= 0xd800 && c < 0xdc00) return 4; + return 3; +} + +int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +{ + char* buf_out = buf; + const char* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); + } + *buf_out = 0; + return (int)(buf_out - buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_count; +} + +ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) +{ + float s = 1.0f/255.0f; + return ImVec4( + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; + return out; +} + +ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4& col) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = col; + c.w *= style.Alpha; + return ColorConvertFloat4ToU32(c); +} + +const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) +{ + ImGuiStyle& style = GImGui->Style; + return style.Colors[idx]; +} + +ImU32 ImGui::GetColorU32(ImU32 col) +{ + float style_alpha = GImGui->Style.Alpha; + if (style_alpha >= 1.0f) + return col; + int a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (int)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 +// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) +{ + float K = 0.f; + if (g < b) + { + ImSwap(g, b); + K = -1.f; + } + if (r < g) + { + ImSwap(r, g); + K = -2.f / 6.f - K; + } + + const float chroma = r - (g < b ? g : b); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); + out_s = chroma / (r + 1e-20f); + out_v = r; +} + +// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 +// also http://en.wikipedia.org/wiki/HSL_and_HSV +void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) +{ + if (s == 0.0f) + { + // gray + out_r = out_g = out_b = v; + return; + } + + h = fmodf(h, 1.0f) / (60.0f/360.0f); + int i = (int)h; + float f = h - (float)i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (i) + { + case 0: out_r = v; out_g = t; out_b = p; break; + case 1: out_r = q; out_g = v; out_b = p; break; + case 2: out_r = p; out_g = v; out_b = t; break; + case 3: out_r = p; out_g = q; out_b = v; break; + case 4: out_r = t; out_g = p; out_b = v; break; + case 5: default: out_r = v; out_g = p; out_b = q; break; + } +} + +FILE* ImFileOpen(const char* filename, const char* mode) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can) + const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; + const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); + ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); + return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); +#else + return fopen(filename, mode); +#endif +} + +// Load file content into memory +// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() +void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) +{ + IM_ASSERT(filename && file_open_mode); + if (out_file_size) + *out_file_size = 0; + + FILE* f; + if ((f = ImFileOpen(filename, file_open_mode)) == NULL) + return NULL; + + long file_size_signed; + if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) + { + fclose(f); + return NULL; + } + + int file_size = (int)file_size_signed; + void* file_data = ImGui::MemAlloc(file_size + padding_bytes); + if (file_data == NULL) + { + fclose(f); + return NULL; + } + if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) + { + fclose(f); + ImGui::MemFree(file_data); + return NULL; + } + if (padding_bytes > 0) + memset((void *)(((char*)file_data) + file_size), 0, padding_bytes); + + fclose(f); + if (out_file_size) + *out_file_size = file_size; + + return file_data; +} + +//----------------------------------------------------------------------------- +// ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +// std::lower_bound but without the bullshit +static ImVector::iterator LowerBound(ImVector& data, ImGuiID key) +{ + ImVector::iterator first = data.begin(); + ImVector::iterator last = data.end(); + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + ImVector::iterator mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. +void ImGuiStorage::BuildSortByKey() +{ + struct StaticFunc + { + static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1; + if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. +int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_i; +} + +bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_f; +} + +void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) +void ImGuiStorage::SetInt(ImGuiID key, int val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_i = val; +} + +void ImGuiStorage::SetBool(ImGuiID key, bool val) +{ + SetInt(key, val ? 1 : 0); +} + +void ImGuiStorage::SetFloat(ImGuiID key, float val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) +{ + if (default_filter) + { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } + else + { + InputBuf[0] = 0; + CountGrep = 0; + } +} + +bool ImGuiTextFilter::Draw(const char* label, float width) +{ + if (width != 0.0f) + ImGui::PushItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (width != 0.0f) + ImGui::PopItemWidth(); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::TextRange::split(char separator, ImVector& out) +{ + out.resize(0); + const char* wb = b; + const char* we = wb; + while (we < e) + { + if (*we == separator) + { + out.push_back(TextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out.push_back(TextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); + input_range.split(',', Filters); + + CountGrep = 0; + for (int i = 0; i != Filters.Size; i++) + { + Filters[i].trim_blanks(); + if (Filters[i].empty()) + continue; + if (Filters[i].front() != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (int i = 0; i != Filters.Size; i++) + { + const TextRange& f = Filters[i]; + if (f.empty()) + continue; + if (f.front() == '-') + { + // Subtract + if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) + return false; + } + else + { + // Grep + if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) + return true; + } + } + + // Implicit * grep + if (CountGrep == 0) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiTextBuffer +//----------------------------------------------------------------------------- + +// On some platform vsnprintf() takes va_list by reference and modifies it. +// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. +#ifndef va_copy +#define va_copy(dest, src) (dest = src) +#endif + +// Helper: Text buffer for logging/accumulating text +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + if (len <= 0) + return; + + const int write_off = Buf.Size; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int double_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); + } + + Buf.resize(needed_sz); + ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy); +} + +void ImGuiTextBuffer::appendf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + appendfv(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// ImGuiSimpleColumns (internal use only) +//----------------------------------------------------------------------------- + +ImGuiMenuColumns::ImGuiMenuColumns() +{ + Count = 0; + Spacing = Width = NextWidth = 0.0f; + memset(Pos, 0, sizeof(Pos)); + memset(NextWidths, 0, sizeof(NextWidths)); +} + +void ImGuiMenuColumns::Update(int count, float spacing, bool clear) +{ + IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); + Count = count; + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < Count; i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = (float)(int)Width; + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } +} + +float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +{ + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < 3; i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); +} + +float ImGuiMenuColumns::CalcExtraSpace(float avail_w) +{ + return ImMax(0.0f, avail_w - Width); +} + +//----------------------------------------------------------------------------- +// ImGuiListClipper +//----------------------------------------------------------------------------- + +static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) +{ + // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. Consider moving within SetCursorXXX functions? + ImGui::SetCursorPosY(pos_y); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage. + window->DC.PrevLineHeight = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. + if (window->DC.ColumnsSet) + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly +} + +// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 +// Use case B: Begin() called from constructor with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int count, float items_height) +{ + StartPosY = ImGui::GetCursorPosY(); + ItemsHeight = items_height; + ItemsCount = count; + StepNo = 0; + DisplayEnd = DisplayStart = -1; + if (ItemsHeight > 0.0f) + { + ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display + if (DisplayStart > 0) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor + StepNo = 2; + } +} + +void ImGuiListClipper::End() +{ + if (ItemsCount < 0) + return; + // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; + StepNo = 3; +} + +bool ImGuiListClipper::Step() +{ + if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) + { + ItemsCount = -1; + return false; + } + if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height. + { + DisplayStart = 0; + DisplayEnd = 1; + StartPosY = ImGui::GetCursorPosY(); + StepNo = 1; + return true; + } + if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. + { + if (ItemsCount == 1) { ItemsCount = -1; return false; } + float items_height = ImGui::GetCursorPosY() - StartPosY; + IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically + Begin(ItemsCount-1, items_height); + DisplayStart++; + DisplayEnd++; + StepNo = 3; + return true; + } + if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3. + { + IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); + StepNo = 3; + return true; + } + if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. + End(); + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiWindow +//----------------------------------------------------------------------------- + +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) +{ + Name = ImStrdup(name); + ID = ImHash(name, 0); + IDStack.push_back(ID); + Flags = 0; + PosFloat = Pos = ImVec2(0.0f, 0.0f); + Size = SizeFull = ImVec2(0.0f, 0.0f); + SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); + WindowPadding = ImVec2(0.0f, 0.0f); + WindowRounding = 0.0f; + WindowBorderSize = 0.0f; + MoveId = GetID("#MOVE"); + ChildId = 0; + Scroll = ImVec2(0.0f, 0.0f); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + ScrollbarX = ScrollbarY = false; + ScrollbarSizes = ImVec2(0.0f, 0.0f); + Active = WasActive = false; + WriteAccessed = false; + Collapsed = false; + CollapseToggleWanted = false; + SkipItems = false; + Appearing = false; + CloseButton = false; + BeginOrderWithinParent = -1; + BeginOrderWithinContext = -1; + BeginCount = 0; + PopupId = 0; + AutoFitFramesX = AutoFitFramesY = -1; + AutoFitOnlyGrows = false; + AutoFitChildAxises = 0x00; + AutoPosLastDirection = ImGuiDir_None; + HiddenFrames = 0; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + + LastFrameActive = -1; + ItemWidthDefault = 0.0f; + FontWindowScale = 1.0f; + + DrawList = IM_NEW(ImDrawList)(&context->DrawListSharedData); + DrawList->_OwnerName = Name; + ParentWindow = NULL; + RootWindow = NULL; + RootWindowForTitleBarHighlight = NULL; + RootWindowForTabbing = NULL; + RootWindowForNav = NULL; + + NavLastIds[0] = NavLastIds[1] = 0; + NavRectRel[0] = NavRectRel[1] = ImRect(); + NavLastChildNavWindow = NULL; + + FocusIdxAllCounter = FocusIdxTabCounter = -1; + FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; + FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_DELETE(DrawList); + IM_DELETE(Name); + for (int i = 0; i != ColumnsStorage.Size; i++) + ColumnsStorage[i].~ImGuiColumnsSet(); +} + +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(&ptr, sizeof(void*), seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; + ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed); + ImGui::KeepAliveID(id); + return id; +} + +//----------------------------------------------------------------------------- +// Internal API exposed in imgui_internal.h +//----------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow = window; + if (window) + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +static void SetNavID(ImGuiID id, int nav_layer) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindow); + IM_ASSERT(nav_layer == 0 || nav_layer == 1); + g.NavId = id; + g.NavWindow->NavLastIds[nav_layer] = id; +} + +static void SetNavIDAndMoveMouse(ImGuiID id, int nav_layer, const ImRect& rect_rel) +{ + ImGuiContext& g = *GImGui; + SetNavID(id, nav_layer); + g.NavWindow->NavRectRel[nav_layer] = rect_rel; + g.NavMousePosDirty = true; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; +} + +void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) + g.ActiveIdTimer = 0.0f; + g.ActiveId = id; + g.ActiveIdAllowNavDirFlags = 0; + g.ActiveIdAllowOverlap = false; + g.ActiveIdWindow = window; + if (id) + { + g.ActiveIdIsAlive = true; + g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; + } +} + +ImGuiID ImGui::GetActiveID() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId; +} + +void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0); + + // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it. + const int nav_layer = window->DC.NavLayerCurrent; + if (g.NavWindow != window) + g.NavInitRequest = false; + g.NavId = id; + g.NavWindow = window; + g.NavLayer = nav_layer; + window->NavLastIds[nav_layer] = id; + if (window->DC.LastItemId == id) + window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + + if (g.ActiveIdSource == ImGuiInputSource_Nav) + g.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f; +} + +ImGuiID ImGui::GetHoveredID() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; +} + +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = true; +} + +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + + return true; +} + +// Advance cursor given item size for layout. +void ImGui::ItemSize(const ImVec2& size, float text_offset_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Always align ourselves on pixel boundaries + const float line_height = ImMax(window->DC.CurrentLineHeight, size.y); + const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); + window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineHeight = line_height; + window->DC.PrevLineTextBaseOffset = text_base_offset; + window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + +void ImGui::ItemSize(const ImRect& bb, float text_offset_y) +{ + ItemSize(bb.GetSize(), text_offset_y); +} + +static ImGuiDir NavScoreItemGetQuadrant(float dx, float dy) +{ + if (fabsf(dx) > fabsf(dy)) + return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; + return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; +} + +static float NavScoreItemDistInterval(float a0, float a1, float b0, float b1) +{ + if (a1 < b0) + return a1 - b0; + if (b1 < a0) + return a0 - b1; + return 0.0f; +} + +// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavLayer != window->DC.NavLayerCurrent) + return false; + + const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringCount++; + + // We perform scoring on items bounding box clipped by their parent window on the other axis (clipping on our movement axis would give us equal scores for all clipped items) + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + { + cand.Min.y = ImClamp(cand.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y); + cand.Max.y = ImClamp(cand.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y); + } + else + { + cand.Min.x = ImClamp(cand.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + cand.Max.x = ImClamp(cand.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + } + + // Compute distance between boxes + // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. + float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + if (dby != 0.0f && dbx != 0.0f) + dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); + float dist_box = fabsf(dbx) + fabsf(dby); + + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) + float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); + float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); + float dist_center = fabsf(dcx) + fabsf(dcy); // L1 metric (need this for our connectedness guarantee) + + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance + ImGuiDir quadrant; + float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; + if (dbx != 0.0f || dby != 0.0f) + { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = NavScoreItemGetQuadrant(dbx, dby); + } + else if (dcx != 0.0f || dcy != 0.0f) + { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = NavScoreItemGetQuadrant(dcx, dcy); + } + else + { + // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) + quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + g.OverlayDrawList.AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100)); + g.OverlayDrawList.AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); + g.OverlayDrawList.AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); + g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + } + else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + { + if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } + if (quadrant == g.NavMoveDir) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + g.OverlayDrawList.AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); + g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } + #endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + if (quadrant == g.NavMoveDir) + { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) + { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) + { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) + { + result->DistCenter = dist_center; + new_best = true; + } + else if (dist_center == result->DistCenter) + { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items + // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), + // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. + if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches + // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) + // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. + // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. + // Disabling it may however lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +static void NavSaveLastChildNavWindow(ImGuiWindow* child_window) +{ + ImGuiWindow* parent_window = child_window; + while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent_window = parent_window->ParentWindow; + if (parent_window && parent_window != child_window) + parent_window->NavLastChildNavWindow = child_window; +} + +// Call when we are expected to land on Layer 0 after FocusWindow() +static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window) +{ + return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; +} + +static void NavRestoreLayer(int layer) +{ + ImGuiContext& g = *GImGui; + g.NavLayer = layer; + if (layer == 0) + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); + if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) + SetNavIDAndMoveMouse(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); + else + ImGui::NavInitWindow(g.NavWindow, true); +} + +static inline void NavUpdateAnyRequestFlag() +{ + ImGuiContext& g = *GImGui; + g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || IMGUI_DEBUG_NAV_SCORING; +} + +static bool NavMoveRequestButNoResultYet() +{ + ImGuiContext& g = *GImGui; + return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +static void NavMoveRequestCancel() +{ + ImGuiContext& g = *GImGui; + g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); +} + +// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) +static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +{ + ImGuiContext& g = *GImGui; + //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. + // return; + + const ImGuiItemFlags item_flags = window->DC.ItemFlags; + const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + { + g.NavInitResultId = id; + g.NavInitResultRectRel = nav_bb_rel; + } + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + { + g.NavInitRequest = false; // Found a match, clear request + NavUpdateAnyRequestFlag(); + } + } + + // Scoring for navigation + if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav)) + { + ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; +#if IMGUI_DEBUG_NAV_SCORING + // [DEBUG] Score all items in NavWindow at all times + if (!g.NavMoveRequest) + g.NavMoveDir = g.NavMoveDirLast; + bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; +#else + bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); +#endif + if (new_best) + { + result->ID = id; + result->ParentID = window->IDStack.back(); + result->Window = window; + result->RectRel = nav_bb_rel; + } + } + + // Update window-relative bounding box of navigated item + if (g.NavId == id) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavIdIsAlive = true; + g.NavIdTabCounter = window->FocusIdxTabCounter; + window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + } +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (id != 0) + { + // Navigation processing runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window. + // it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick) + window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask; + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); + } + + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemStatusFlags = 0; + + // Clipping test + const bool is_clipped = IsClippedEx(bb, id, false); + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (IsMouseHoveringRect(bb.Min, bb.Max)) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavDisableMouseHover && !g.NavDisableHighlight) + return IsItemFocused(); + + // Test for bounding box overlap, as updated as ItemAdd() + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function + + // Test if we are hovering the right window (our window could be behind another window) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. + // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. + //if (g.HoveredWindow != window) + // return false; + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) + return false; + + // Test if another item is active (e.g. being dragged) + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + + // Test if interactions on this window are blocked by an active popup or modal + if (!IsWindowContentHoverable(window, flags)) + return false; + + // Test if the item is disabled + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. + if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_Default)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + SetHoveredID(id); + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || id != g.ActiveId) + if (clip_even_when_logged || !g.LogEnabled) + return true; + return false; +} + +bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) +{ + ImGuiContext& g = *GImGui; + + const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus; + window->FocusIdxAllCounter++; + if (allow_keyboard_focus) + window->FocusIdxTabCounter++; + + // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. + // Note that we can always TAB out of a widget that doesn't allow tabbing in. + if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) + window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + + if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) + return true; + if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) + { + g.NavJustTabbedId = id; + return true; + } + + return false; +} + +void ImGui::FocusableItemUnregister(ImGuiWindow* window) +{ + window->FocusIdxAllCounter--; + window->FocusIdxTabCounter--; +} + +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) +{ + ImGuiContext& g = *GImGui; + ImVec2 content_max; + if (size.x < 0.0f || size.y < 0.0f) + content_max = g.CurrentWindow->Pos + GetContentRegionMax(); + if (size.x <= 0.0f) + size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; + if (size.y <= 0.0f) + size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; + return size; +} + +float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) +{ + if (wrap_pos_x < 0.0f) + return 0.0f; + + ImGuiWindow* window = GetCurrentWindowRead(); + if (wrap_pos_x == 0.0f) + wrap_pos_x = GetContentRegionMax().x + window->Pos.x; + else if (wrap_pos_x > 0.0f) + wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space + + return ImMax(wrap_pos_x - pos.x, 1.0f); +} + +//----------------------------------------------------------------------------- + +void* ImGui::MemAlloc(size_t sz) +{ + GImAllocatorActiveAllocationsCount++; + return GImAllocatorAllocFunc(sz, GImAllocatorUserData); +} + +void ImGui::MemFree(void* ptr) +{ + if (ptr) GImAllocatorActiveAllocationsCount--; + return GImAllocatorFreeFunc(ptr, GImAllocatorUserData); +} + +const char* ImGui::GetClipboardText() +{ + return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char* text) +{ + if (GImGui->IO.SetClipboardTextFn) + GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); +} + +const char* ImGui::GetVersion() +{ + return IMGUI_VERSION; +} + +// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data) +{ + GImAllocatorAllocFunc = alloc_func; + GImAllocatorFreeFunc = free_func; + GImAllocatorUserData = user_data; +} + +ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) +{ + ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + if (GImGui == NULL) + SetCurrentContext(ctx); + Initialize(ctx); + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + if (ctx == NULL) + ctx = GImGui; + Shutdown(ctx); + if (GImGui == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +ImGuiIO& ImGui::GetIO() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->IO; +} + +ImGuiStyle& ImGui::GetStyle() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->Style; +} + +// Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame() +ImDrawData* ImGui::GetDrawData() +{ + ImGuiContext& g = *GImGui; + return g.DrawData.Valid ? &g.DrawData : NULL; +} + +float ImGui::GetTime() +{ + return GImGui->Time; +} + +int ImGui::GetFrameCount() +{ + return GImGui->FrameCount; +} + +ImDrawList* ImGui::GetOverlayDrawList() +{ + return &GImGui->OverlayDrawList; +} + +ImDrawListSharedData* ImGui::GetDrawListSharedData() +{ + return &GImGui->DrawListSharedData; +} + +// This needs to be called before we submit any widget (aka in or before Begin) +void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == g.NavWindow); + bool init_for_nav = false; + if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; + if (init_for_nav) + { + SetNavID(0, g.NavLayer); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } + else + { + g.NavId = window->NavLastIds[0]; + } +} + +static ImVec2 NavCalcPreferredMousePos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.NavWindow; + if (!window) + return g.IO.MousePos; + const ImRect& rect_rel = window->NavRectRel[g.NavLayer]; + ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImRect visible_rect = GetViewportRect(); + return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. +} + +static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size-1; i >= 0; i--) + if (g.Windows[i] == window) + return i; + return -1; +} + +static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.Windows[i])) + return g.Windows[i]; + return NULL; +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + ImGuiContext& g = *GImGui; + if (mode == ImGuiInputReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) + return 0.0f; + if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. + return (t == 0.0f) ? 1.0f : 0.0f; + if (mode == ImGuiInputReadMode_Repeat) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f); + if (mode == ImGuiInputReadMode_RepeatSlow) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f); + if (mode == ImGuiInputReadMode_RepeatFast) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f); + return 0.0f; +} + +// Equivalent of IsKeyDown() for NavInputs[] +static bool IsNavInputDown(ImGuiNavInput n) +{ + return GImGui->IO.NavInputs[n] > 0.0f; +} + +// Equivalent of IsKeyPressed() for NavInputs[] +static bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + return ImGui::GetNavInputAmount(n, mode) > 0.0f; +} + +static bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) +{ + return (ImGui::GetNavInputAmount(n1, mode) + ImGui::GetNavInputAmount(n2, mode)) > 0.0f; +} + +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +{ + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = FindWindowIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); + if (!window_target) + window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir); + g.NavWindowingTarget = window_target; + g.NavWindowingToggleLayer = false; +} + +// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) +static void ImGui::NavUpdateWindowing() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; + + bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard); + if (start_windowing_with_gamepad || start_windowing_with_keyboard) + if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1)) + { + g.NavWindowingTarget = window->RootWindowForTabbing; + g.NavWindowingHighlightTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavWindowingInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + } + + // Gamepad update + g.NavWindowingHighlightTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + { + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.20f) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); + if (focus_change_dir != 0) + { + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } + + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) + if (!IsNavInputDown(ImGuiNavInput_Menu)) + { + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.15f) / 0.04f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and Release ALT to toggle menu layer + // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) + if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) + apply_toggle_layer = true; + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + ImVec2 move_delta; + if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingTarget->PosFloat += move_delta * move_speed; + g.NavDisableMouseHover = true; + MarkIniSettingsDirty(g.NavWindowingTarget); + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowForTabbing)) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window); + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window only has a menu layer, select it directly + if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) + g.NavLayer = 1; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) + { + ImGuiWindow* new_nav_window = g.NavWindow; + while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) + { + ImGuiWindow* old_nav_window = g.NavWindow; + FocusWindow(new_nav_window); + new_nav_window->NavLastChildNavWindow = old_nav_window; + } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0); + } +} + +// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. +static void NavScrollToBringItemIntoView(ImGuiWindow* window, ImRect& item_rect_rel) +{ + // Scroll to keep newly navigated item fully into view + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); + //g.OverlayDrawList.AddRect(window->Pos + window_rect_rel.Min, window->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG] + if (window_rect_rel.Contains(item_rect_rel)) + return; + + ImGuiContext& g = *GImGui; + if (window->ScrollbarX && item_rect_rel.Min.x < window_rect_rel.Min.x) + { + window->ScrollTarget.x = item_rect_rel.Min.x + window->Scroll.x - g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + else if (window->ScrollbarX && item_rect_rel.Max.x >= window_rect_rel.Max.x) + { + window->ScrollTarget.x = item_rect_rel.Max.x + window->Scroll.x + g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 1.0f; + } + if (item_rect_rel.Min.y < window_rect_rel.Min.y) + { + window->ScrollTarget.y = item_rect_rel.Min.y + window->Scroll.y - g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + else if (item_rect_rel.Max.y >= window_rect_rel.Max.y) + { + window->ScrollTarget.y = item_rect_rel.Max.y + window->Scroll.y + g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 1.0f; + } + + // Estimate upcoming scroll so we can offset our relative mouse position so mouse position can be applied immediately (under this block) + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); + item_rect_rel.Translate(window->Scroll - next_scroll); +} + +static void ImGui::NavUpdate() +{ + ImGuiContext& g = *GImGui; + g.IO.WantMoveMouse = false; + +#if 0 + if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); +#endif + + // Update Keyboard->Nav inputs mapping + memset(g.IO.NavInputs + ImGuiNavInput_InternalStart_, 0, (ImGuiNavInput_COUNT - ImGuiNavInput_InternalStart_) * sizeof(g.IO.NavInputs[0])); + if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) + { + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (g.IO.KeyMap[_KEY] != -1 && IsKeyDown(g.IO.KeyMap[_KEY])) g.IO.NavInputs[_NAV_INPUT] = 1.0f; + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); + NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); + if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; + if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; +#undef NAV_MAP_KEY + } + + memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) + g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) + { + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + IM_ASSERT(g.NavWindow); + if (g.NavInitRequestFromMove) + SetNavIDAndMoveMouse(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); + else + SetNavID(g.NavInitResultId, g.NavLayer); + g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + } + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 0; + + // Process navigation move request + if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) + { + // Select which result to use + ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + if (g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) // Maybe entering a flattened child? In this case solve the tie using the regular scoring rules + if ((g.NavMoveResultOther.DistBox < g.NavMoveResultLocal.DistBox) || (g.NavMoveResultOther.DistBox == g.NavMoveResultLocal.DistBox && g.NavMoveResultOther.DistCenter < g.NavMoveResultLocal.DistCenter)) + result = &g.NavMoveResultOther; + + IM_ASSERT(g.NavWindow && result->Window); + + // Scroll to keep newly navigated item fully into view + if (g.NavLayer == 0) + NavScrollToBringItemIntoView(result->Window, result->RectRel); + + // Apply result from previous frame navigation directional move request + ClearActiveID(); + g.NavWindow = result->Window; + SetNavIDAndMoveMouse(result->ID, g.NavLayer, result->RectRel); + g.NavJustMovedToId = result->ID; + g.NavMoveFromClampedRefRect = false; + } + + // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame + if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + { + IM_ASSERT(g.NavMoveRequest); + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + g.NavDisableHighlight = false; + g.NavMoveRequestForward = ImGuiNavForward_None; + } + + // Apply application mouse position movement, after we had a chance to process move request result. + if (g.NavMousePosDirty && g.NavIdIsAlive) + { + // Set mouse position given our knowledge of the nav widget position from last frame + if (g.IO.NavFlags & ImGuiNavFlags_MoveMouse) + { + g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredMousePos(); + g.IO.WantMoveMouse = true; + } + g.NavMousePosDirty = false; + } + g.NavIdIsAlive = false; + g.NavJustTabbedId = 0; + IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); + + // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 + if (g.NavWindow) + NavSaveLastChildNavWindow(g.NavWindow); + if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) + g.NavWindow->NavLastChildNavWindow = NULL; + + NavUpdateWindowing(); + + // Set output flags for user application + g.IO.NavActive = (g.IO.NavFlags & (ImGuiNavFlags_EnableGamepad | ImGuiNavFlags_EnableKeyboard)) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); + g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; + + // Process NavCancel input (to close a popup, get back to parent, clear focus) + if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) + { + if (g.ActiveId != 0) + { + ClearActiveID(); + } + else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + { + // Exit child window + ImGuiWindow* child_window = g.NavWindow; + ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, 0); + g.NavIdIsAlive = false; + if (g.NavDisableMouseHover) + g.NavMousePosDirty = true; + } + else if (g.OpenPopupStack.Size > 0) + { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + } + else if (g.NavLayer != 0) + { + // Leave the "menu" layer + NavRestoreLayer(0); + } + else + { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = 0; + } + } + + // Process manual activation request + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + if (g.ActiveId == 0 && activate_pressed) + g.NavActivateId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) + g.NavActivatePressedId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) + g.NavInputId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + g.NavMoveRequest = false; + + // Process programmatic activation request + if (g.NavNextActivateId != 0) + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + g.NavNextActivateId = 0; + + // Initiate directional inputs request + const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; + if (g.NavMoveRequestForward == ImGuiNavForward_None) + { + g.NavMoveDir = ImGuiDir_None; + if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + if ((allowed_dir_flags & (1<Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) + { + // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item + ImGuiWindow* window = g.NavWindow; + const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + { + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) + SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + } + + // *Normal* Manual scroll with NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + { + SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + if (scroll_dir.y != 0.0f) + { + SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + } + + // Reset search results + g.NavMoveResultLocal.Clear(); + g.NavMoveResultOther.Clear(); + + // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items + if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1,1), window->InnerRect.Max - window->Pos + ImVec2(1,1)); + if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) + { + float pad = window->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item + window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); + g.NavId = 0; + } + g.NavMoveFromClampedRefRect = false; + } + + // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) + ImRect nav_rect_rel = (g.NavWindow && g.NavWindow->NavRectRel[g.NavLayer].IsFinite()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); + g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect(); + g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); + g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; + IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous fabsf() calls in NavScoreItem(). + //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] + g.NavScoringCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) g.OverlayDrawList.AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames <= 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredMousePos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } +#endif +} + +static void ImGui::UpdateMovingWindow() +{ + ImGuiContext& g = *GImGui; + if (g.MovingWindow && g.MovingWindow->MoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse) + { + // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). + // We track it to preserve Focus and so that ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. + KeepAliveID(g.ActiveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + ImGuiWindow* moving_window = g.MovingWindow->RootWindow; + if (g.IO.MouseDown[0]) + { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + if (moving_window->PosFloat.x != pos.x || moving_window->PosFloat.y != pos.y) + { + MarkIniSettingsDirty(moving_window); + moving_window->PosFloat = pos; + } + FocusWindow(g.MovingWindow); + } + else + { + ClearActiveID(); + g.MovingWindow = NULL; + } + } + else + { + // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. + if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) + { + KeepAliveID(g.ActiveId); + if (!g.IO.MouseDown[0]) + ClearActiveID(); + } + g.MovingWindow = NULL; + } +} + +void ImGui::NewFrame() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + ImGuiContext& g = *GImGui; + + // Check user data + // (We pass an error message in the assert expression as a trick to get it visible to programmers who are not using a debugger, as most assert handlers display their argument) + IM_ASSERT(g.Initialized); + IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)"); + IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value"); + IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting"); + IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)"); + IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); + for (int n = 0; n < ImGuiKey_COUNT; n++) + IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)"); + + // Do a simple check for required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was super recently added in 1.60 WIP) + if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) + IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); + + // Load settings on first frame + if (!g.SettingsLoaded) + { + IM_ASSERT(g.SettingsWindows.empty()); + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.SettingsLoaded = true; + } + + g.Time += g.IO.DeltaTime; + g.FrameCount += 1; + g.TooltipOverrideCount = 0; + g.WindowsActiveCount = 0; + + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + + g.OverlayDrawList.Clear(); + g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); + g.OverlayDrawList.PushClipRectFullScreen(); + g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it + g.DrawData.Clear(); + + // Clear reference to active widget if the widget isn't alive anymore + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + if (g.ActiveId) + g.ActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) + g.ScalarAsInputTextId = 0; + + // Elapse drag & drop payload + if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) + { + ClearDragDrop(); + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + } + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + + // Update keyboard input state + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Update gamepad/keyboard directional navigation + NavUpdate(); + + // Update mouse input state + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) + g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) + g.NavDisableMouseHover = false; + + g.IO.MousePosPrev = g.IO.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseDoubleClicked[i] = false; + if (g.IO.MouseClicked[i]) + { + if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime) + { + if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) + g.IO.MouseDoubleClicked[i] = true; + g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click + } + else + { + g.IO.MouseClickedTime[i] = g.Time; + } + g.IO.MouseClickedPos[i] = g.IO.MousePos; + g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (g.IO.MouseDown[i]) + { + ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i]; + g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x); + g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y); + g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta)); + } + if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + g.NavDisableMouseHover = false; + } + + // Calculate frame-rate for the user, as a purely luxurious feature + g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; + g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); + g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); + + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) + UpdateMovingWindow(); + + // Delay saving settings so we don't spam disk too much + if (g.SettingsDirtyTimer > 0.0f) + { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) + SaveIniSettingsToDisk(g.IO.IniFilename); + } + + // Find the window we are hovering + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow(); + g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + + ImGuiWindow* modal_window = GetFrontMostModalRootWindow(); + if (modal_window != NULL) + { + g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f); + if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) + g.HoveredRootWindow = g.HoveredWindow = NULL; + } + else + { + g.ModalWindowDarkeningRatio = 0.0f; + } + + // Update the WantCaptureMouse/WantCaptureKeyboard flags, so user can capture/discard the inputs away from the rest of their application. + // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership. + int mouse_earliest_button_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + if (g.IO.MouseClicked[i]) + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); + mouse_any_down |= g.IO.MouseDown[i]; + if (g.IO.MouseDown[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) + mouse_earliest_button_down = i; + } + bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); + else + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + + if (g.WantCaptureKeyboardNextFrame != -1) + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + if (g.IO.NavActive && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) && !(g.IO.NavFlags & ImGuiNavFlags_NoCaptureKeyboard)) + g.IO.WantCaptureKeyboard = true; + + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0; + g.MouseCursor = ImGuiMouseCursor_Arrow; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) + bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; + if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) + g.HoveredWindow = g.HoveredRootWindow = NULL; + + // Mouse wheel scrolling, scale + if (g.HoveredWindow && !g.HoveredWindow->Collapsed && (g.IO.MouseWheel != 0.0f || g.IO.MouseWheelH != 0.0f)) + { + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set). + ImGuiWindow* window = g.HoveredWindow; + ImGuiWindow* scroll_window = window; + while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow) + scroll_window = scroll_window->ParentWindow; + const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs); + + if (g.IO.MouseWheel != 0.0f) + { + if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) + { + // Zoom / Scale window + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + window->Pos += offset; + window->PosFloat += offset; + window->Size *= scale; + window->SizeFull *= scale; + } + else if (!g.IO.KeyCtrl && scroll_allowed) + { + // Mouse wheel vertical scrolling + float scroll_amount = 5 * scroll_window->CalcFontSize(); + scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f); + SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount); + } + } + if (g.IO.MouseWheelH != 0.0f && scroll_allowed) + { + // Mouse wheel horizontal scrolling (for hardware that supports it) + float scroll_amount = scroll_window->CalcFontSize(); + if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) + SetWindowScrollX(window, window->Scroll.x - g.IO.MouseWheelH * scroll_amount); + } + } + + // Pressing TAB activate widget focus + if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false)) + { + if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) + g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1); + else + g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0; + } + g.NavIdTabCounter = INT_MAX; + + // Mark all windows as not visible + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->Active = false; + window->WriteAccessed = false; + } + + // Closing the focused window restore focus to the first active root window in descending z-order + if (g.NavWindow && !g.NavWindow->WasActive) + FocusFrontMostActiveWindow(NULL); + + // No window should be open at the beginning of the frame. + // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. + g.CurrentWindowStack.resize(0); + g.CurrentPopupStack.resize(0); + ClosePopupsOverWindow(g.NavWindow); + + // Create implicit window - we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. + SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); +} + +static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); + if (!settings) + settings = AddWindowSettings(name); + return (void*)settings; +} + +static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; + float x, y; + int i; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); + else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); +} + +static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + ImGuiContext& g = *imgui_ctx; + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID); + if (!settings) + settings = AddWindowSettings(window->Name); + settings->Pos = window->Pos; + settings->Size = window->SizeFull; + settings->Collapsed = window->Collapsed; + } + + // Write a buffer + // If a window wasn't opened in this session we preserve its settings + buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) + { + const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; + if (settings->Pos.x == FLT_MAX) + continue; + const char* name = settings->Name; + if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + name = p; + buf->appendf("[%s][%s]\n", handler->TypeName, name); + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->appendf("\n"); + } +} + +void ImGui::Initialize(ImGuiContext* context) +{ + ImGuiContext& g = *context; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + g.LogClipboard = IM_NEW(ImGuiTextBuffer)(); + + // Add .ini handle for ImGuiWindow type + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHash("Window", 0, 0); + ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; + ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; + ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; + g.SettingsHandlers.push_front(ini_handler); + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown(ImGuiContext* context) +{ + ImGuiContext& g = *context; + + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + if (g.IO.Fonts && g.FontAtlasOwnedByContext) + IM_DELETE(g.IO.Fonts); + + // Cleanup of other data are conditional on actually having initialize ImGui. + if (!g.Initialized) + return; + + SaveIniSettingsToDisk(g.IO.IniFilename); + + // Clear everything else + for (int i = 0; i < g.Windows.Size; i++) + IM_DELETE(g.Windows[i]); + g.Windows.clear(); + g.WindowsSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = NULL; + g.HoveredRootWindow = NULL; + g.ActiveIdWindow = NULL; + g.MovingWindow = NULL; + for (int i = 0; i < g.SettingsWindows.Size; i++) + IM_DELETE(g.SettingsWindows[i].Name); + g.ColorModifiers.clear(); + g.StyleModifiers.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.CurrentPopupStack.clear(); + g.DrawDataBuilder.ClearFreeMemory(); + g.OverlayDrawList.ClearFreeMemory(); + g.PrivateClipboard.clear(); + g.InputTextState.Text.clear(); + g.InputTextState.InitialText.clear(); + g.InputTextState.TempTextBuffer.clear(); + + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile && g.LogFile != stdout) + { + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard) + IM_DELETE(g.LogClipboard); + + g.Initialized = false; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (int i = 0; i != g.SettingsWindows.Size; i++) + if (g.SettingsWindows[i].Id == id) + return &g.SettingsWindows[i]; + return NULL; +} + +static ImGuiWindowSettings* AddWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + g.SettingsWindows.push_back(ImGuiWindowSettings()); + ImGuiWindowSettings* settings = &g.SettingsWindows.back(); + settings->Name = ImStrdup(name); + settings->Id = ImHash(name, 0); + return settings; +} + +static void LoadIniSettingsFromDisk(const char* ini_filename) +{ + if (!ini_filename) + return; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1); + if (!file_data) + return; + LoadIniSettingsFromMemory(file_data); + ImGui::MemFree(file_data); +} + +ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) +{ + ImGuiContext& g = *GImGui; + const ImGuiID type_hash = ImHash(type_name, 0, 0); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +// Zero-tolerance, no error reporting, cheap .ini parsing +static void LoadIniSettingsFromMemory(const char* buf_readonly) +{ + // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy. + char* buf = ImStrdup(buf_readonly); + char* buf_end = buf + strlen(buf); + + ImGuiContext& g = *GImGui; + void* entry_data = NULL; + ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) + { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + + if (line[0] == '[' && line_end > line && line_end[-1] == ']') + { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; + char* type_end = ImStrchrRange(type_start, name_end, ']'); + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + { + name_start = type_start; // Import legacy entries that have no type + type_start = "Window"; + } + else + { + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + } + entry_handler = ImGui::FindSettingsHandler(type_start); + entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; + } + else if (entry_handler != NULL && entry_data != NULL) + { + // Let type handler parse the line + entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); + } + } + ImGui::MemFree(buf); + g.SettingsLoaded = true; +} + +static void SaveIniSettingsToDisk(const char* ini_filename) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + if (!ini_filename) + return; + + ImVector buf; + SaveIniSettingsToMemory(buf); + + FILE* f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f); + fclose(f); +} + +static void SaveIniSettingsToMemory(ImVector& out_buf) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + + ImGuiTextBuffer buf; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + { + ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; + handler->WriteAllFn(&g, handler, &buf); + } + + buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer + out_buf.swap(buf.Buf); +} + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +static void MarkIniSettingsDirty(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +// FIXME: Add a more explicit sort order in the window structure. +static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) +{ + const ImGuiWindow* a = *(const ImGuiWindow**)lhs; + const ImGuiWindow* b = *(const ImGuiWindow**)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) +{ + out_sorted_windows->push_back(window); + if (window->Active) + { + int count = window->DC.ChildWindows.Size; + if (count > 1) + qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); + for (int i = 0; i < count; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortedBuffer(out_sorted_windows, child); + } + } +} + +static void AddDrawListToDrawData(ImVector* out_render_list, ImDrawList* draw_list) +{ + if (draw_list->CmdBuffer.empty()) + return; + + // Remove trailing command if unused + ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); + if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) + { + draw_list->CmdBuffer.pop_back(); + if (draw_list->CmdBuffer.empty()) + return; + } + + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly. + IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); + IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); + IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + + // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) + // If this assert triggers because you are drawing lots of stuff manually: + // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents. + // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes. + // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API. + // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists. + if (sizeof(ImDrawIdx) == 2) + IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + + out_render_list->push_back(draw_list); +} + +static void AddWindowToDrawData(ImVector* out_render_list, ImGuiWindow* window) +{ + AddDrawListToDrawData(out_render_list, window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active && child->HiddenFrames <= 0) // clipped children may have been marked not active + AddWindowToDrawData(out_render_list, child); + } +} + +static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.IO.MetricsActiveWindows++; + if (window->Flags & ImGuiWindowFlags_Tooltip) + AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window); + else + AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window); +} + +void ImDrawDataBuilder::FlattenIntoSingleLayer() +{ + int n = Layers[0].Size; + int size = n; + for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) + size += Layers[i].Size; + Layers[0].resize(size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) + { + ImVector& layer = Layers[layer_n]; + if (layer.empty()) + continue; + memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + layer.resize(0); + } +} + +static void SetupDrawData(ImVector* draw_lists, ImDrawData* out_draw_data) +{ + out_draw_data->Valid = true; + out_draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; + out_draw_data->CmdListsCount = draw_lists->Size; + out_draw_data->TotalVtxCount = out_draw_data->TotalIdxCount = 0; + for (int n = 0; n < draw_lists->Size; n++) + { + out_draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; + out_draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size; + } +} + +// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. +void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +void ImGui::PopClipRect() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PopClipRect(); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. +void ImGui::EndFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times. + return; + + // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) + if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f) + { + g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y); + g.OsImePosSet = g.OsImePosRequest; + } + + // Hide implicit "Debug" window if it hasn't been used + IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls + if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + g.CurrentWindow->Active = false; + End(); + + if (g.ActiveId == 0 && g.HoveredId == 0) + { + if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear + { + // Click to focus window and start moving (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) + { + if (g.HoveredRootWindow != NULL) + { + // Set ActiveId even if the _NoMove flag is set, without it dragging away from a window with _NoMove would activate hover on other windows. + FocusWindow(g.HoveredWindow); + SetActiveID(g.HoveredWindow->MoveId, g.HoveredWindow); + g.NavDisableHighlight = true; + g.ActiveIdClickOffset = g.IO.MousePos - g.HoveredRootWindow->Pos; + if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove)) + g.MovingWindow = g.HoveredWindow; + } + else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) + { + // Clicking on void disable focus + FocusWindow(NULL); + } + } + + // With right mouse button we close popups without changing focus + // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger) + if (g.IO.MouseClicked[1]) + { + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostModalRootWindow(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal); + } + } + } + + // Sort the window list so that all child windows are after their parent + // We cannot do that on FocusWindow() because childs may not exist yet + g.WindowsSortBuffer.resize(0); + g.WindowsSortBuffer.reserve(g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + continue; + AddWindowToSortedBuffer(&g.WindowsSortBuffer, window); + } + + IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong + g.Windows.swap(g.WindowsSortBuffer); + + // Clear Input data for next frame + g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); + + g.FrameCountEnded = g.FrameCount; +} + +void ImGui::Render() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + + if (g.FrameCountEnded != g.FrameCount) + ImGui::EndFrame(); + g.FrameCountRendered = g.FrameCount; + + // Skip render altogether if alpha is 0.0 + // Note that vertex buffers have been created and are wasted, so it is best practice that you don't create windows in the first place, or consistently respond to Begin() returning false. + if (g.Style.Alpha > 0.0f) + { + // Gather windows to render + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0; + g.DrawDataBuilder.Clear(); + ImGuiWindow* window_to_render_front_most = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget : NULL; + for (int n = 0; n != g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0 && window != window_to_render_front_most) + AddWindowToDrawDataSelectLayer(window); + } + if (window_to_render_front_most && window_to_render_front_most->Active && window_to_render_front_most->HiddenFrames <= 0) // NavWindowingTarget is always temporarily displayed as the front-most window + AddWindowToDrawDataSelectLayer(window_to_render_front_most); + g.DrawDataBuilder.FlattenIntoSingleLayer(); + + // Draw software mouse cursor if requested + ImVec2 offset, size, uv[4]; + if (g.IO.MouseDrawCursor && g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &offset, &size, &uv[0], &uv[2])) + { + const ImVec2 pos = g.IO.MousePos - offset; + const ImTextureID tex_id = g.IO.Fonts->TexID; + const float sc = g.Style.MouseCursorScale; + g.OverlayDrawList.PushTextureID(tex_id); + g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(1,0)*sc, pos+ImVec2(1,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(2,0)*sc, pos+ImVec2(2,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[2], uv[3], IM_COL32(0,0,0,255)); // Black border + g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[0], uv[1], IM_COL32(255,255,255,255)); // White fill + g.OverlayDrawList.PopTextureID(); + } + if (!g.OverlayDrawList.VtxBuffer.empty()) + AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList); + + // Setup ImDrawData structure for end-user + SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData); + g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount; + g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount; + + // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) + g.IO.RenderDrawListsFn(&g.DrawData); +#endif + } +} + +const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) +{ + const char* text_display_end = text; + if (!text_end) + text_end = (const char*)-1; + + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) + text_display_end++; + return text_display_end; +} + +// Pass text data straight to log (without being displayed) +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + if (g.LogFile) + { + vfprintf(g.LogFile, fmt, args); + } + else + { + g.LogClipboard->appendfv(fmt, args); + } + va_end(args); +} + +// Internal version that takes a position to decide on newline placement and pad items according to their depth. +// We split text into individual lines to add current tree level padding +static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = ImGui::FindRenderedTextEnd(text, text_end); + + const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); + if (ref_pos) + window->DC.LogLinePosY = ref_pos->y; + + const char* text_remaining = text; + if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth + g.LogStartDepth = window->DC.TreeDepth; + const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); + for (;;) + { + // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. + const char* line_end = text_remaining; + while (line_end < text_end) + if (*line_end == '\n') + break; + else + line_end++; + if (line_end >= text_end) + line_end = NULL; + + const bool is_first_line = (text == text_remaining); + bool is_last_line = false; + if (line_end == NULL) + { + is_last_line = true; + line_end = text_end; + } + if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0)) + { + const int char_count = (int)(line_end - text_remaining); + if (log_new_line || !is_first_line) + ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining); + else + ImGui::LogText(" %.*s", char_count, text_remaining); + } + + if (is_last_line) + break; + text_remaining = line_end + 1; + } +} + +// Internal ImGui functions to render text +// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() +void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) + { + text_display_end = FindRenderedTextEnd(text, text_end); + } + else + { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + const int text_len = (int)(text_display_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); + } +} + +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + + const int text_len = (int)(text_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_end); + } +} + +// Default clip_rect uses (pos_min,pos_max) +// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Perform CPU side clipping for single clipped element to avoid using scissor state + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; + const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min + need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. + if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); + if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + + // Render + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); +} + +// Render a rectangle shaped with optional rounding and borders +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +// Render a triangle to denote expanded/collapsed state +void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const float h = g.FontSize * 1.00f; + float r = h * 0.40f * scale; + ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); + + ImVec2 a, b, c; + switch (dir) + { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) r = -r; + center.y -= r * 0.25f; + a = ImVec2(0,1) * r; + b = ImVec2(-0.866f,-0.5f) * r; + c = ImVec2(+0.866f,-0.5f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) r = -r; + center.x -= r * 0.25f; + a = ImVec2(1,0) * r; + b = ImVec2(-0.500f,+0.866f) * r; + c = ImVec2(-0.500f,-0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_Count_: + IM_ASSERT(0); + break; + } + + window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); +} + +void ImGui::RenderBullet(ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); +} + +void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness*0.5f; + pos += ImVec2(thickness*0.25f, thickness*0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third*0.5f; + window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); + window->DrawList->PathLineTo(ImVec2(bx, by)); + window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); + window->DrawList->PathStroke(col, false, thickness); +} + +void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +{ + ImGuiContext& g = *GImGui; + if (id != g.NavId) + return; + if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + return; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->DC.NavHideHighlightOneFrame) + return; + + float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + ImRect display_rect = bb; + display_rect.ClipWith(window->ClipRect); + if (flags & ImGuiNavHighlightFlags_TypeDefault) + { + const float THICKNESS = 2.0f; + const float DISTANCE = 3.0f + THICKNESS * 0.5f; + display_rect.Expand(ImVec2(DISTANCE,DISTANCE)); + bool fully_visible = window->ClipRect.Contains(display_rect); + if (!fully_visible) + window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS); + if (!fully_visible) + window->DrawList->PopClipRect(); + } + if (flags & ImGuiNavHighlightFlags_TypeThin) + { + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f); + } +} + +// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. +// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) +ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) +{ + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + ImFont* font = g.Font; + const float font_size = g.FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) + const float font_scale = font_size / font->FontSize; + const float character_spacing_x = 1.0f * font_scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + text_size.x = (float)(int)(text_size.x + 0.95f); + + return text_size; +} + +// Helper to calculate coarse clipping of large list of evenly sized items. +// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. +// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.LogEnabled) + { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (window->SkipItems) + { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((window->ClipRect.Min.y - pos.y) / items_height); + int end = (int)((window->ClipRect.Max.y - pos.y) / items_height); + if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Up) // When performing a navigation request, ensure we have one item extra in the direction we are moving to + start--; + if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) + end++; + + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have a lag here because WindowRectClipped is updated in Begin() so windows moved by user via SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is called, aka before the next Begin(). Moving window thankfully isn't affected. +static ImGuiWindow* FindHoveredWindow() +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (!window->Active) + continue; + if (window->Flags & ImGuiWindowFlags_NoInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding); + if (bb.Contains(g.IO.MousePos)) + return window; + } + return NULL; +} + +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(window->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + return rect_for_touch.Contains(g.IO.MousePos); +} + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat) +{ + const int key_index = GImGui->IO.KeyMap[key]; + return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false; +} + +int ImGui::GetKeyIndex(ImGuiKey imgui_key) +{ + IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); + return GImGui->IO.KeyMap[imgui_key]; +} + +// Note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your back-end/engine stored them into KeyDown[]! +bool ImGui::IsKeyDown(int user_key_index) +{ + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); + return GImGui->IO.KeysDown[user_key_index]; +} + +int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) +{ + if (t == 0.0f) + return 1; + if (t <= repeat_delay || repeat_rate <= 0.0f) + return 0; + const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); + return (count > 0) ? count : 0; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) return false; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); +} + +bool ImGui::IsKeyPressed(int user_key_index, bool repeat) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[user_key_index]; + if (t == 0.0f) + return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsKeyReleased(int user_key_index) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]; +} + +bool ImGui::IsMouseDown(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button]; +} + +bool ImGui::IsAnyMouseDown() +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + if (g.IO.MouseDown[n]) + return true; + return false; +} + +bool ImGui::IsMouseClicked(int button, bool repeat) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) + return true; + + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) + return true; + } + + return false; +} + +bool ImGui::IsMouseReleased(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + +bool ImGui::IsMouseDragging(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; +} + +ImVec2 ImGui::GetMousePos() +{ + return GImGui->IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext& g = *GImGui; + if (g.CurrentPopupStack.Size > 0) + return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos; + return g.IO.MousePos; +} + +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + if (mouse_pos == NULL) + mouse_pos = &GImGui->IO.MousePos; + const float MOUSE_INVALID = -256000.0f; + return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; +} + +// NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window. +ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr + g.IO.MouseClickedPos[button] = g.IO.MousePos; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + return GImGui->MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) +{ + GImGui->MouseCursor = cursor_type; +} + +void ImGui::CaptureKeyboardFromApp(bool capture) +{ + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; +} + +void ImGui::CaptureMouseFromApp(bool capture) +{ + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + return g.ActiveId == window->DC.LastItemId; + } + return false; +} + +bool ImGui::IsItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId; +} + +bool ImGui::IsItemClicked(int mouse_button) +{ + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default); +} + +bool ImGui::IsAnyItemHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; +} + +bool ImGui::IsAnyItemActive() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId != 0; +} + +bool ImGui::IsAnyItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId != 0 && !g.NavDisableHighlight; +} + +bool ImGui::IsItemVisible() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(window->DC.LastItemRect); +} + +// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId == g.CurrentWindow->DC.LastItemId) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == g.CurrentWindow->DC.LastItemId) + g.ActiveIdAllowOverlap = true; +} + +ImVec2 ImGui::GetItemRectMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Min; +} + +ImVec2 ImGui::GetItemRectMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Max; +} + +ImVec2 ImGui::GetItemRectSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.GetSize(); +} + +static ImRect GetViewportRect() +{ + ImGuiContext& g = *GImGui; + if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) + return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); + return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); +} + +// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) +{ + ImGuiContext& g = *GImGui; + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (override_previous_tooltip) + if (ImGuiWindow* window = FindWindowByName(window_name)) + if (window->Active) + { + // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one. + window->HiddenFrames = 1; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav; + Begin(window_name, NULL, flags | extra_flags); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) +{ + BeginTooltipEx(0, true); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +void ImGui::BeginTooltip() +{ + BeginTooltipEx(0, false); +} + +void ImGui::EndTooltip() +{ + IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls + End(); +} + +// Mark popup as open (toggle toward open state). +// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopupEx(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + int current_stack_size = g.CurrentPopupStack.Size; + ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. + popup_ref.PopupId = id; + popup_ref.Window = NULL; + popup_ref.ParentWindow = parent_window; + popup_ref.OpenFrameCount = g.FrameCount; + popup_ref.OpenParentId = parent_window->IDStack.back(); + popup_ref.OpenMousePos = g.IO.MousePos; + popup_ref.OpenPopupPos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos; + + if (g.OpenPopupStack.Size < current_stack_size + 1) + { + g.OpenPopupStack.push_back(popup_ref); + } + else + { + // Close child popups if any + g.OpenPopupStack.resize(current_stack_size + 1); + + // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui + // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing + // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. + if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) + g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; + else + g.OpenPopupStack[current_stack_size] = popup_ref; + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). + // This is equivalent to what ClosePopupToLevel() does. + //if (g.OpenPopupStack[current_stack_size].PopupId == id) + // FocusWindow(parent_window); + } +} + +void ImGui::OpenPopup(const char* str_id) +{ + ImGuiContext& g = *GImGui; + OpenPopupEx(g.CurrentWindow->GetID(str_id)); +} + +void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.empty()) + return; + + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. + // Don't close our own child popup windows. + int n = 0; + if (ref_window) + { + for (n = 0; n < g.OpenPopupStack.Size; n++) + { + ImGuiPopupRef& popup = g.OpenPopupStack[n]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) + bool has_focus = false; + for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) + has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); + if (!has_focus) + break; + } + } + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); +} + +static ImGuiWindow* GetFrontMostModalRootWindow() +{ + ImGuiContext& g = *GImGui; + for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +static void ClosePopupToLevel(int remaining) +{ + IM_ASSERT(remaining >= 0); + ImGuiContext& g = *GImGui; + ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow; + if (g.NavLayer == 0) + focus_window = NavRestoreLastChildNavWindow(focus_window); + ImGui::FocusWindow(focus_window); + focus_window->DC.NavHideHighlightOneFrame = true; + g.OpenPopupStack.resize(remaining); +} + +void ImGui::ClosePopup(ImGuiID id) +{ + if (!IsPopupOpen(id)) + return; + ImGuiContext& g = *GImGui; + ClosePopupToLevel(g.OpenPopupStack.Size - 1); +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext& g = *GImGui; + int popup_idx = g.CurrentPopupStack.Size - 1; + if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) + return; + while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) + popup_idx--; + ClosePopupToLevel(popup_idx); +} + +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (extra_flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::IsPopupOpen(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; +} + +bool ImGui::IsPopupOpen(const char* str_id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); +} + +bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + // Center modal windows by default + // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. + if (g.NextWindowData.PosCond == 0) + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); + if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + { + EndPopup(); + if (is_open) + ClosePopup(id); + return false; + } + + return is_open; +} + +static void NavProcessMoveRequestWrapAround(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.NavWindow == window && NavMoveRequestButNoResultYet()) + if ((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == 0) + { + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + g.NavWindow->NavRectRel[0].Min.y = g.NavWindow->NavRectRel[0].Max.y = ((g.NavMoveDir == ImGuiDir_Up) ? ImMax(window->SizeFull.y, window->SizeContents.y) : 0.0f) - window->Scroll.y; + } +} + +void ImGui::EndPopup() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls + IM_ASSERT(g.CurrentPopupStack.Size > 0); + + // Make all menus and popups wrap around for now, may need to expose that policy. + NavProcessMoveRequestWrapAround(g.CurrentWindow); + + End(); +} + +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id); + return true; + } + return false; +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. +bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) +{ + if (!str_id) + str_id = "window_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (also_over_items || !IsAnyItemHovered()) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) +{ + if (!str_id) + str_id = "void_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + const ImVec2 content_avail = ImGui::GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + flags |= extra_flags; + + char title[256]; + if (name) + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); + else + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); + + ImGui::SetNextWindowSize(size); + bool ret = ImGui::Begin(title, NULL, flags); + ImGuiWindow* child_window = ImGui::GetCurrentWindow(); + child_window->ChildId = id; + child_window->AutoFitChildAxises = auto_fit_axises; + g.Style.ChildBorderSize = backup_border_size; + + // Process navigation-in immediately so NavInit can run on first frame + if (!(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && g.NavActivateId == id) + { + ImGui::FocusWindow(child_window); + ImGui::NavInitWindow(child_window, false); + ImGui::SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item + g.ActiveIdSource = ImGuiInputSource_Nav; + } + + return ret; +} + +bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + IM_ASSERT(id != 0); + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss + if (window->BeginCount > 1) + { + End(); + } + else + { + // When using auto-filling child window, we don't provide full width/height to ItemSize so that it doesn't feed back into automatic size-fitting. + ImVec2 sz = GetWindowSize(); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + End(); + + ImGuiWindow* parent_window = g.CurrentWindow; + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) + { + ItemAdd(bb, window->ChildId); + RenderNavHighlight(bb, window->ChildId); + + // When browsing a window that has no activable items (scroll only) we keep a highlight on the child + if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow) + RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + } + else + { + // Not navigable into + ItemAdd(bb, 0); + } + } +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + return BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); +} + +void ImGui::EndChildFrame() +{ + EndChild(); + PopStyleVar(3); + PopStyleColor(); +} + +// Save and compare stack sizes on Begin()/End() to detect usage errors +static void CheckStacksSize(ImGuiWindow* window, bool write) +{ + // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + ImGuiContext& g = *GImGui; + int* p_backup = &window->DC.StackSizesBackup[0]; + { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop() + { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup() + { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup() + { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor() + { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar() + { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont() + IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); +} + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox +}; + +static ImVec2 FindBestWindowPosForPopup(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default) +{ + const ImGuiStyle& style = GImGui->Style; + + // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) + // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. + ImVec2 safe_padding = style.DisplaySafeAreaPadding; + ImRect r_outer(GetViewportRect()); + r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x*2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y*2) ? -safe_padding.y : 0.0f)); + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) + { + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Default popup policy + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + if (avail_w < size.x || avail_h < size.y) + continue; + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + *last_dir = dir; + return pos; + } + + // Fallback, try to keep within display + *last_dir = ImGuiDir_None; + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); +} + +ImGuiWindow* ImGui::FindWindowByName(const char* name) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = ImHash(name, 0); + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + { + // Retrieve settings from .ini file + // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + window->Pos = window->PosFloat = ImVec2(60, 60); + + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) + { + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + window->PosFloat = settings->Pos; + window->Pos = ImFloor(window->PosFloat); + window->Collapsed = settings->Collapsed; + if (ImLengthSqr(settings->Size) > 0.00001f) + size = settings->Size; + } + } + window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; + + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } + + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + return window; +} + +static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) +{ + ImGuiContext& g = *GImGui; + if (g.NextWindowData.SizeConstraintCond != 0) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.NextWindowData.SizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.NextWindowData.SizeCallback) + { + ImGuiSizeCallbackData data; + data.UserData = g.NextWindowData.SizeCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.NextWindowData.SizeCallback(&data); + new_size = data.DesiredSize; + } + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + { + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + } + return new_size; +} + +static ImVec2 CalcSizeContents(ImGuiWindow* window) +{ + ImVec2 sz; + sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); + sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); + return sz + window->WindowPadding; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + ImVec2 size_auto_fit; + if ((flags & ImGuiWindowFlags_Tooltip) != 0) + { + // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. + size_auto_fit = size_contents; + } + else + { + // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); + ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) + size_auto_fit.y += style.ScrollbarSize; + if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) + size_auto_fit.x += style.ScrollbarSize; + } + return size_auto_fit; +} + +static float GetScrollMaxX(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x)); +} + +static float GetScrollMaxY(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y)); +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) +{ + ImVec2 scroll = window->Scroll; + float cr_x = window->ScrollTargetCenterRatio.x; + float cr_y = window->ScrollTargetCenterRatio.y; + if (window->ScrollTarget.x < FLT_MAX) + scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); + if (window->ScrollTarget.y < FLT_MAX) + scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); + scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, GetScrollMaxX(window)); + scroll.y = ImMin(scroll.y, GetScrollMaxY(window)); + } + return scroll; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildBg; + return ImGuiCol_WindowBg; +} + +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +struct ImGuiResizeGripDef +{ + ImVec2 CornerPos; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; + +const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right + { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left + { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left + { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right +}; + +static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) rect.Max -= ImVec2(1,1); + if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness); + if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding); + if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y); + if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + IM_ASSERT(0); + return ImRect(); +} + +// Handle resize for: Resize Grips, Borders, Gamepad +static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return; + + const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0; + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f); + + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); + + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size); + resize_rect.FixInverted(); + bool hovered, held; + ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcSizeAfterConstraint(window, size_auto_fit); + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip + CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); + } + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) + { + const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check. + const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise + bool hovered, held; + ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE); + ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held) + { + g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) *border_held = border_n; + } + if (held) + { + ImVec2 border_target = window->Pos; + ImVec2 border_posn; + if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); } + if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); } + if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); } + if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); } + CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + } + } + PopID(); + + // Navigation/gamepad resize + if (g.NavWindowingTarget == window) + { + ImVec2 nav_resize_delta; + if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); + if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) + { + const float NAV_RESIZE_SPEED = 600.0f; + nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck. + size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta); + } + } + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) + { + window->Pos = window->PosFloat = ImFloor(pos_target); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; +} + +// Push a new ImGui window to add widgets to. +// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. +// - Begin/End can be called multiple times during the frame with the same window name to append content. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + IM_ASSERT(name != NULL); // Window name required + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + + // Find or create + ImGuiWindow* window = FindWindowByName(name); + if (!window) + { + ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. + window = CreateNewWindow(name, size_on_first_use, flags); + } + + // Automatically disable manual moving/resizing when NoInputs is set + if (flags & ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + if (flags & ImGuiWindowFlags_NavFlattened) + IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); + + const int current_frame = g.FrameCount; + const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); + if (first_begin_of_the_frame) + window->Flags = (ImGuiWindowFlags)flags; + else + flags = window->Flags; + + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); + window->CloseButton = (p_open != NULL); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack + ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + + // Add to stack + g.CurrentWindowStack.push_back(window); + SetCurrentWindow(window); + CheckStacksSize(window, true); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + popup_ref.Window = window; + g.CurrentPopupStack.push_back(popup_ref); + window->PopupId = popup_ref.PopupId; + } + + if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) + window->NavLastIds[0] = 0; + + // Process SetNextWindow***() calls + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.NextWindowData.PosCond) + { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) + { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.NextWindowData.PosVal; + window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } + else + { + SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); + } + g.NextWindowData.PosCond = 0; + } + if (g.NextWindowData.SizeCond) + { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); + SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); + g.NextWindowData.SizeCond = 0; + } + if (g.NextWindowData.ContentSizeCond) + { + // Adjust passed "client size" to become a "window size" + window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal; + if (window->SizeContentsExplicit.y != 0.0f) + window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); + g.NextWindowData.ContentSizeCond = 0; + } + else if (first_begin_of_the_frame) + { + window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); + } + if (g.NextWindowData.CollapsedCond) + { + SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); + g.NextWindowData.CollapsedCond = 0; + } + if (g.NextWindowData.FocusCond) + { + SetWindowFocus(); + g.NextWindowData.FocusCond = 0; + } + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) + { + const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) + + // Initialize + window->ParentWindow = parent_window; + window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = window->RootWindowForNav = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !window_is_child_tooltip) + window->RootWindow = parent_window->RootWindow; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = parent_window->RootWindowForTitleBarHighlight; // Same value in master branch, will differ for docking + while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + window->RootWindowForNav = window->RootWindowForNav->ParentWindow; + + window->Active = true; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = g.WindowsActiveCount++; + window->BeginCount = 0; + window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); + window->LastFrameActive = current_frame; + window->IDStack.resize(1); + + // Lock window rounding, border size and rounding so that altering the border sizes for children doesn't have side-effects. + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + + // Collapse window by double-clicking on title bar + // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + { + ImRect title_bar_rect = window->TitleBarRect(); + if (window->CollapseToggleWanted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])) + { + window->Collapsed = !window->Collapsed; + MarkIniSettingsDirty(window); + FocusWindow(window); + } + } + else + { + window->Collapsed = false; + } + window->CollapseToggleWanted = false; + + // SIZE + + // Update contents size from last frame for auto-fitting (unless explicitly specified) + window->SizeContents = CalcSizeContents(window); + + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + if (window->HiddenFrames > 0) + window->HiddenFrames--; + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user) + { + window->HiddenFrames = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) + { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->SizeContents = ImVec2(0.f, 0.f); + } + } + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); + ImVec2 size_full_modified(FLT_MAX, FLT_MAX); + if (flags & ImGuiWindowFlags_AlwaysAutoResize && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + window->SizeFull.x = size_full_modified.x = size_auto_fit.x; + if (!window_size_y_set_by_api) + window->SizeFull.y = size_full_modified.y = size_auto_fit.y; + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit only grows during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (!window->Collapsed) + MarkIniSettingsDirty(window); + } + + // Apply minimum/maximum window size constraints and final size + window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); + window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; + + // SCROLLBAR STATUS + + // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) + { + // When reading the current size we need to read it after size constraints have been applied + float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; + float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + } + + // POSITION + + // Popup latch its initial position, will position itself when it appears next frame + if (window_just_activated_by_user) + { + window->AutoPosLastDirection = ImGuiDir_None; + if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) + window->Pos = window->PosFloat = g.CurrentPopupStack.back().OpenPopupPos; + } + + // Position child window + if (flags & ImGuiWindowFlags_ChildWindow) + { + window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = window->PosFloat = parent_window->DC.CursorPos; + } + + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0); + if (window_pos_with_pivot) + { + // Position given a pivot (e.g. for centering) + SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); + } + else if (flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then our FindBestPopupWindowPos() function will move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(window_pos_set_by_api); + float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value). + ImGuiWindow* parent_menu = parent_window_in_stack; + ImRect rect_to_avoid; + if (parent_menu->DC.MenuBarAppending) + rect_to_avoid = ImRect(-FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight(), FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight() + parent_menu->MenuBarHeight()); + else + rect_to_avoid = ImRect(parent_menu->Pos.x + horizontal_overlap, -FLT_MAX, parent_menu->Pos.x + parent_menu->Size.x - horizontal_overlap - parent_menu->ScrollbarSizes.x, FLT_MAX); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + { + ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + + // Position tooltip (always follows mouse) + if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) + { + float sc = g.Style.MouseCursorScale; + ImVec2 ref_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos; + ImRect rect_to_avoid; + if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.NavFlags & ImGuiNavFlags_MoveMouse)) + rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + else + rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. + window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + if (window->AutoPosLastDirection == ImGuiDir_None) + window->PosFloat = ref_pos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + } + + // Clamp position so it stays visible + if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + { + if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + { + ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size; + window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding); + } + } + window->Pos = ImFloor(window->PosFloat); + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); + else + window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); + + // Prepare for focus requests + window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); + window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); + window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; + window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; + + // Apply scrolling + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + + // Apply focus, new windows appears in front + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + want_focus = true; + + // Handle manual resize: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = { 0 }; + const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4 + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + if (!window->Collapsed) + UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]); + + // DRAWING + + // Setup draw list and outer clipping rectangle + window->DrawList->Clear(); + window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + ImRect viewport_rect(GetViewportRect()); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) + PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); + else + PushClipRect(viewport_rect.Min, viewport_rect.Max, true); + + // Draw modal window background (darkens what is behind them) + if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) + window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio)); + + // Draw navigation selection/windowing rectangle background + if (g.NavWindowingTarget == window) + { + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); + } + + // Draw window + handle manual resize + const float window_rounding = window->WindowRounding; + const float window_border_size = window->WindowBorderSize; + const bool title_bar_is_highlight = want_focus || (g.NavWindow && window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); + const ImRect title_bar_rect = window->TitleBarRect(); + if (window->Collapsed) + { + // Title bar only + float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } + else + { + // Window background + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + if (g.NextWindowData.BgAlphaCond != 0) + { + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT); + g.NextWindowData.BgAlphaCond = 0; + } + window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); + + // Title bar + ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Scrollbars + if (window->ScrollbarX) + Scrollbar(ImGuiLayoutType_Horizontal); + if (window->ScrollbarY) + Scrollbar(ImGuiLayoutType_Vertical); + + // Render resize grips (after their input handling so we don't have a frame of latency) + if (!(flags & ImGuiWindowFlags_NoResize)) + { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } + } + + // Borders + if (window_border_size > 0.0f) + window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); + if (border_held != -1) + { + ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f); + window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); + } + if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Draw navigation selection/windowing rectangle border + if (g.NavWindowingTarget == window) + { + float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward + { + bb.Expand(-g.FontSize - 1.0f); + rounding = window->WindowRounding; + } + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + } + + // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. + window->SizeFullAtLastBegin = window->SizeFull; + + // Update ContentsRegionMax. All the variable it depends on are set above in this function. + window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; + window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); + window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); + window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); + + // Setup drawing context + // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) + window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffsetX = 0.0f; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.NavHideHighlightOneFrame = false; + window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f); + window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; + window->DC.NavLayerActiveMaskNext = 0x00; + window->DC.MenuBarAppending = false; + window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x); + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + window->DC.ChildWindows.resize(0); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + window->DC.ItemFlags = ImGuiItemFlags_Default_; + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemFlagsStack.resize(0); + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + window->DC.ColumnsSet = NULL; + window->DC.TreeDepth = 0; + window->DC.TreeDepthMayCloseOnPop = 0x00; + window->DC.StateStorage = &window->StateStorage; + window->DC.GroupStack.resize(0); + window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + + if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) + { + window->DC.ItemFlags = parent_window->DC.ItemFlags; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + } + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + { + FocusWindow(window); + NavInitWindow(window, false); + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + { + // Close & collapse button are on layer 1 (same as menus) and don't default focus + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + + // Collapse button + if (!(flags & ImGuiWindowFlags_NoCollapse)) + { + ImGuiID id = window->GetID("#COLLAPSE"); + ImRect bb(window->Pos + style.FramePadding + ImVec2(1,1), window->Pos + style.FramePadding + ImVec2(g.FontSize,g.FontSize) - ImVec2(1,1)); + ItemAdd(bb, id); // To allow navigation + if (ButtonBehavior(bb, id, NULL, NULL)) + window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function + RenderNavHighlight(bb, id); + RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + } + + // Close button + if (p_open != NULL) + { + const float PAD = 2.0f; + const float rad = (window->TitleBarHeight() - PAD*2.0f) * 0.5f; + if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-PAD - rad, PAD + rad), rad)) + *p_open = false; + } + + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.ItemFlags = item_flags_backup; + + // Title text (FIXME: refactor text alignment facilities along with RenderText helpers) + ImVec2 text_size = CalcTextSize(name, NULL, true); + ImRect text_r = title_bar_rect; + float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); + text_r.Min.x += pad_left; + text_r.Max.x -= pad_right; + ImRect clip_rect = text_r; + clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton() + RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); + } + + // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() + window->WindowRectClipped = window->Rect(); + window->WindowRectClipped.ClipWith(window->ClipRect); + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. + // Maybe we can support CTRL+C on every element? + /* + if (g.ActiveId == move_id) + if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + ImGui::LogToClipboard(); + */ + + // Inner rectangle + // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame + // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. + window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; + window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; + //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); + + // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.). + window->DC.LastItemId = window->MoveId; + window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; + window->DC.LastItemRect = title_bar_rect; + } + + // Inner clipping rectangle + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + const float border_size = window->WindowBorderSize; + ImRect clip_rect; + clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y); + clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y); + PushClipRect(clip_rect.Min, clip_rect.Max, true); + + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) + if (first_begin_of_the_frame) + window->WriteAccessed = false; + + window->BeginCount++; + g.NextWindowData.SizeConstraintCond = 0; + + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar). + if (flags & ImGuiWindowFlags_ChildWindow) + { + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + window->Collapsed = parent_window && parent_window->Collapsed; + + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y); + + // We also hide the window from rendering because we've already added its border to the command list. + // (we could perform the check earlier in the function but it is simpler at this point) + if (window->Collapsed) + window->Active = false; + } + if (style.Alpha <= 0.0f) + window->Active = false; + + // Return false if we don't intend to display anything to allow user to perform an early out optimization + window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0; + return !window->SkipItems; +} + +// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags) +{ + // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file. + if (size_first_use.x != 0.0f || size_first_use.y != 0.0f) + ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver); + + // Old API feature: override the window background alpha with a parameter. + if (bg_alpha_override >= 0.0f) + ImGui::SetNextWindowBgAlpha(bg_alpha_override); + + return ImGui::Begin(name, p_open, flags); +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +void ImGui::End() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + PopClipRect(); // Inner window clip rectangle + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + // Pop from window stack + g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.CurrentPopupStack.pop_back(); + CheckStacksSize(window, false); + SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); +} + +// Vertical scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +void ImGui::Scrollbar(ImGuiLayoutType direction) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const bool horizontal = (direction == ImGuiLayoutType_Horizontal); + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); + + // Render background + bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); + float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; + const ImRect window_rect = window->Rect(); + const float border_size = window->WindowBorderSize; + ImRect bb = horizontal + ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) + : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); + if (!horizontal) + bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); + if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) + return; + + int window_rounding_corners; + if (horizontal) + window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + else + window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); + bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); + float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; + float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; + float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); + const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + const bool previously_held = (g.ActiveId == id); + ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + + float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); + float scroll_ratio = ImSaturate(scroll_v / scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + if (held && grab_h_norm < 1.0f) + { + float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; + float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + SetHoveredID(id); + + bool seek_absolute = false; + if (!previously_held) + { + // On initial click calculate the distance between mouse and the center of the grab + if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) + { + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + else + { + seek_absolute = true; + *click_delta_to_grab_center_v = 0.0f; + } + } + + // Apply scroll + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position + const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); + scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); + if (horizontal) + window->Scroll.x = scroll_v; + else + window->Scroll.y = scroll_v; + + // Update values for rendering + scroll_ratio = ImSaturate(scroll_v / scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seeked and saturated + if (seek_absolute) + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + + // Render + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); + ImRect grab_rect; + if (horizontal) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); +} + +void ImGui::BringWindowToFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window + if (g.Windows[i] == window) + { + g.Windows.erase(g.Windows.Data + i); + g.Windows.push_back(window); + break; + } +} + +void ImGui::BringWindowToBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + if (g.NavWindow != window) + { + g.NavWindow = window; + if (window && g.NavDisableMouseHover) + g.NavMousePosDirty = true; + g.NavInitRequest = false; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavIdIsAlive = false; + g.NavLayer = 0; + } + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Move the root window to the top of the pile + if (window->RootWindow) + window = window->RootWindow; + + // Steal focus on active widgets + if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) + ClearActiveID(); + + // Bring to front + if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) + BringWindowToFront(window); +} + +void ImGui::FocusFrontMostActiveWindow(ImGuiWindow* ignore_window) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) + { + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]); + FocusWindow(focus_window); + return; + } +} + +void ImGui::PushItemWidth(float item_width) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = GImGui->Style; + if (w_full <= 0.0f) + w_full = CalcItemWidth(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components-1; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidthStack.pop_back(); + window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); +} + +float ImGui::CalcItemWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + float w = window->DC.ItemWidth; + if (w < 0.0f) + { + // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. + float width_to_right_edge = GetContentRegionAvail().x; + w = ImMax(1.0f, width_to_right_edge + w); + } + w = (float)(int)w; + return w; +} + +static ImFont* GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; +} + +void ImGui::SetCurrentFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; + g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + + ImFontAtlas* atlas = g.Font->ContainerAtlas; + g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (enabled) + window->DC.ItemFlags |= option; + else + window->DC.ItemFlags &= ~option; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); +} + +void ImGui::PopItemFlag() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); +} + +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPosStack.push_back(wrap_pos_x); +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPosStack.pop_back(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); +} + +// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 +void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiColMod& backup = g.ColorModifiers.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorModifiers.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo +{ + ImGuiDataType Type; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImGuiStyleVarInfo GStyleVarInfo[] = +{ + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_); + IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_Count_); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float) + { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float2) + { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiStyleMod& backup = g.StyleModifiers.back(); + const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); + if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr(&g.Style)) = backup.BackupFloat[0]; + else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr(&g.Style)) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); + else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr(&g.Style)) = backup.BackupInt[0]; + g.StyleModifiers.pop_back(); + count--; + } +} + +const char* ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) + { + case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; + case ImGuiCol_WindowBg: return "WindowBg"; + case ImGuiCol_ChildBg: return "ChildBg"; + case ImGuiCol_PopupBg: return "PopupBg"; + case ImGuiCol_Border: return "Border"; + case ImGuiCol_BorderShadow: return "BorderShadow"; + case ImGuiCol_FrameBg: return "FrameBg"; + case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: return "FrameBgActive"; + case ImGuiCol_TitleBg: return "TitleBg"; + case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_SliderGrab: return "SliderGrab"; + case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; + case ImGuiCol_Button: return "Button"; + case ImGuiCol_ButtonHovered: return "ButtonHovered"; + case ImGuiCol_ButtonActive: return "ButtonActive"; + case ImGuiCol_Header: return "Header"; + case ImGuiCol_HeaderHovered: return "HeaderHovered"; + case ImGuiCol_HeaderActive: return "HeaderActive"; + case ImGuiCol_Separator: return "Separator"; + case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: return "SeparatorActive"; + case ImGuiCol_ResizeGrip: return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_CloseButton: return "CloseButton"; + case ImGuiCol_CloseButtonHovered: return "CloseButtonHovered"; + case ImGuiCol_CloseButtonActive: return "CloseButtonActive"; + case ImGuiCol_PlotLines: return "PlotLines"; + case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening"; + case ImGuiCol_DragDropTarget: return "DragDropTarget"; + case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; + } + IM_ASSERT(0); + return "Unknown"; +} + +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) + { + if (window == potential_parent) + return true; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function + ImGuiContext& g = *GImGui; + + if (flags & ImGuiHoveredFlags_AnyWindow) + { + if (g.HoveredWindow == NULL) + return false; + } + else + { + switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) + { + case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: + if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_RootWindow: + if (g.HoveredWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_ChildWindows: + if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) + return false; + break; + default: + if (g.HoveredWindow != g.CurrentWindow) + return false; + break; + } + } + + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) + return false; + return true; +} + +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() + + if (flags & ImGuiFocusedFlags_AnyWindow) + return g.NavWindow != NULL; + + switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) + { + case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_RootWindow: + return g.NavWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); + default: + return g.NavWindow == g.CurrentWindow; + } +} + +// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + return window->Active && window == window->RootWindowForTabbing && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow); +} + +float ImGui::GetWindowWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.x; +} + +float ImGui::GetWindowHeight() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.y; +} + +ImVec2 ImGui::GetWindowPos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return window->Pos; +} + +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x) +{ + window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.x = new_scroll_x; + window->DC.CursorMaxPos.x -= window->Scroll.x; +} + +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) +{ + window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.y = new_scroll_y; + window->DC.CursorMaxPos.y -= window->Scroll.y; +} + +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowPosAllowFlags & cond) == 0) + return; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + + // Set + const ImVec2 old_pos = window->Pos; + window->PosFloat = pos; + window->Pos = ImFloor(pos); + window->DC.CursorPos += (window->Pos - old_pos); // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. +} + +void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + SetWindowPos(window, pos, cond); +} + +void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowPos(window, pos, cond); +} + +ImVec2 ImGui::GetWindowSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Size; +} + +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) + return; + window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + if (size.x > 0.0f) + { + window->AutoFitFramesX = 0; + window->SizeFull.x = size.x; + } + else + { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) + { + window->AutoFitFramesY = 0; + window->SizeFull.y = size.y; + } + else + { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) +{ + SetWindowSize(GImGui->CurrentWindow, size, cond); +} + +void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowSize(window, size, cond); +} + +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) + return; + window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + window->Collapsed = collapsed; +} + +void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); +} + +bool ImGui::IsWindowCollapsed() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; +} + +void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowCollapsed(window, collapsed, cond); +} + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.PosVal = pos; + g.NextWindowData.PosPivotVal = pivot; + g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeVal = size; + g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeConstraintCond = ImGuiCond_Always; + g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); + g.NextWindowData.SizeCallback = custom_callback; + g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; +} + +void ImGui::SetNextWindowContentSize(const ImVec2& size) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.ContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. + g.NextWindowData.ContentSizeCond = ImGuiCond_Always; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.CollapsedVal = collapsed; + g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +void ImGui::SetNextWindowBgAlpha(float alpha) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.BgAlphaVal = alpha; + g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +// In window space (not screen space!) +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImVec2 mx = window->ContentsRegionRect.Max; + if (window->DC.ColumnsSet) + mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); +} + +float ImGui::GetContentRegionAvailWidth() +{ + return GetContentRegionAvail().x; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetWindowContentRegionMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Min; +} + +ImVec2 ImGui::GetWindowContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max; +} + +float ImGui::GetWindowContentRegionWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x; +} + +float ImGui::GetTextLineHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize; +} + +float ImGui::GetTextLineHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.ItemSpacing.y; +} + +float ImGui::GetFrameHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + +float ImGui::GetFrameHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; +} + +ImDrawList* ImGui::GetWindowDrawList() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont* ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +void ImGui::SetWindowFontScale(float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->FontWindowScale = scale; + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. +// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. +ImVec2 ImGui::GetCursorPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos - window->Pos + window->Scroll; +} + +float ImGui::GetCursorPosX() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; +} + +float ImGui::GetCursorPosY() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; +} + +void ImGui::SetCursorPos(const ImVec2& local_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); +} + +void ImGui::SetCursorPosY(float y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); +} + +ImVec2 ImGui::GetCursorStartPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorStartPos - window->Pos; +} + +ImVec2 ImGui::GetCursorScreenPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos; +} + +void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = screen_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +float ImGui::GetScrollX() +{ + return GImGui->CurrentWindow->Scroll.x; +} + +float ImGui::GetScrollY() +{ + return GImGui->CurrentWindow->Scroll.y; +} + +float ImGui::GetScrollMaxX() +{ + return GetScrollMaxX(GImGui->CurrentWindow); +} + +float ImGui::GetScrollMaxY() +{ + return GetScrollMaxY(GImGui->CurrentWindow); +} + +void ImGui::SetScrollX(float scroll_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.x = scroll_x; + window->ScrollTargetCenterRatio.x = 0.0f; +} + +void ImGui::SetScrollY(float scroll_y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY + window->ScrollTargetCenterRatio.y = 0.0f; +} + +void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) +{ + // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); + window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); + window->ScrollTargetCenterRatio.y = center_y_ratio; + + // Minor hack to to make scrolling to top/bottom of window take account of WindowPadding, it looks more right to the user this way + if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y) + window->ScrollTarget.y = 0.0f; + else if (center_y_ratio >= 1.0f && window->ScrollTarget.y >= window->SizeContents.y - window->WindowPadding.y + GImGui->Style.ItemSpacing.y) + window->ScrollTarget.y = window->SizeContents.y; +} + +// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. +void ImGui::SetScrollHere(float center_y_ratio) +{ + ImGuiWindow* window = GetCurrentWindow(); + float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space + target_y += (window->DC.PrevLineHeight * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line. + SetScrollFromPosY(target_y, center_y_ratio); +} + +void ImGui::ActivateItem(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavNextActivateId = id; +} + +void ImGui::SetKeyboardFocusHere(int offset) +{ + IM_ASSERT(offset >= -1); // -1 is allowed but not below + ImGuiWindow* window = GetCurrentWindow(); + window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; + window->FocusIdxTabRequestNext = INT_MAX; +} + +void ImGui::SetItemDefaultFocus() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!window->Appearing) + return; + if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent) + { + g.NavInitRequest = false; + g.NavInitResultId = g.NavWindow->DC.LastItemId; + g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); + NavUpdateAnyRequestFlag(); + if (!IsItemVisible()) + SetScrollHere(); + } +} + +void ImGui::SetStateStorage(ImGuiStorage* tree) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.StateStorage = tree ? tree : &window->StateStorage; +} + +ImGuiStorage* ImGui::GetStateStorage() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.StateStorage; +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + TextUnformatted(g.TempBuffer, text_end); +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set + if (need_wrap) PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_wrap) PopTextWrapPos(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(text != NULL); + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = wrap_pos_x >= 0.0f; + if (text_end - text > 2000 && !wrap_enabled) + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + const char* line = text; + const float line_height = GetTextLineHeight(); + const ImRect clip_rect = window->ClipRect; + ImVec2 text_size(0,0); + + if (text_pos.y <= clip_rect.Max.y) + { + ImVec2 pos = text_pos; + + // Lines to skip (can't skip when logging text) + if (!g.LogEnabled) + { + int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (IsClippedEx(line_rect, 0, false)) + break; + + const ImVec2 line_size = CalcTextSize(line, line_end, false); + text_size.x = ImMax(text_size.x, line_size.x); + RenderText(pos, line, line_end, false); + if (!line_end) + line_end = text_end; + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + + text_size.y += (pos - text_pos).y; + } + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(bb); + ItemAdd(bb, 0); + } + else + { + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + // Account of baseline offset + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); + const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) ClearActiveID(); + return false; + } + + // Default behavior requires click+release on same spot + if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) + flags |= ImGuiButtonFlags_PressedOnClickRelease; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = window; + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && g.DragDropActive && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy + { + pressed = true; + FocusWindow(window); + } + } + + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = backup_hovered_window; + + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + // Mouse + if (hovered) + { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + { + // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat + // PressedOnClickRelease | * | .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds + // PressedOnClick | | .. + // PressedOnRelease | | .. (NOT on release) + // PressedOnDoubleClick | | .. + // FIXME-NAV: We don't honor those different behaviors. + if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) + { + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + FocusWindow(window); + } + if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveID) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) + { + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + pressed = true; + ClearActiveID(); + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) + pressed = true; + } + + if (pressed) + g.NavDisableHighlight = true; + } + + // Gamepad/Keyboard navigation + // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + hovered = true; + + if (g.NavActivateDownId == id) + { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); + if (nav_activated_by_code || nav_activated_by_inputs) + pressed = true; + if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + g.NavActivateId = id; // This is so SetActiveId assign a Nav source + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } + } + + bool held = false; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + if (g.IO.MouseDown[0]) + { + held = true; + } + else + { + if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + if (!g.DragDropActive) + pressed = true; + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + g.NavDisableHighlight = true; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + if (g.NavActivateDownId != id) + ClearActiveID(); + } + } + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, 0); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + return pressed; +} + +// Button to close a window +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. + // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); + bool is_clipped = !ItemAdd(bb, id); + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (is_clipped) + return pressed; + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton); + ImVec2 center = bb.GetCenter(); + window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12); + + const float cross_extent = (radius * 0.7071f) - 1.0f; + if (hovered) + { + center -= ImVec2(0.5f, 0.5f); + window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), GetColorU32(ImGuiCol_Text)); + window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), GetColorU32(ImGuiCol_Text)); + } + return pressed; +} + +// [Internal] +bool ImGui::ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + padding.x * 2.0f, g.FontSize + padding.y * 2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTriangle(bb.Min + padding, dir, 1.0f); + + return pressed; +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2,2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) + { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col)); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +// The color used are the button colors. +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. + PushID((void *)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2); + const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +// Start logging ImGui output to TTY +void ImGui::LogToTTY(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = stdout; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to given file +void ImGui::LogToFile(int max_depth, const char* filename) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + if (!filename) + { + filename = g.IO.LogFilename; + if (!filename) + return; + } + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = ImFileOpen(filename, "ab"); + if (!g.LogFile) + { + IM_ASSERT(g.LogFile != NULL); // Consider this an error + return; + } + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to clipboard +void ImGui::LogToClipboard(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = NULL; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +void ImGui::LogFinish() +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + if (g.LogFile != NULL) + { + if (g.LogFile == stdout) + fflush(g.LogFile); + else + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard->size() > 1) + { + SetClipboardText(g.LogClipboard->begin()); + g.LogClipboard->clear(); + } + g.LogEnabled = false; +} + +// Helper to display logging buttons +void ImGui::LogButtons() +{ + ImGuiContext& g = *GImGui; + + PushID("LogButtons"); + const bool log_to_tty = Button("Log To TTY"); SameLine(); + const bool log_to_file = Button("Log To File"); SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); + PushItemWidth(80.0f); + PushAllowKeyboardFocus(false); + SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); + PopAllowKeyboardFocus(); + PopItemWidth(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(g.LogAutoExpandMaxDepth); + if (log_to_file) + LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); + if (log_to_clipboard) + LogToClipboard(g.LogAutoExpandMaxDepth); +} + +bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions) + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.NextTreeNodeOpenCond != 0) + { + if (g.NextTreeNodeOpenCond & ImGuiCond_Always) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + g.NextTreeNodeOpenCond = 0; + } + else + { + is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) + is_open = true; + + return is_open; +} + +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + // We vertically grow up to current line height up the typical widget height. + const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); + ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); + if (display_frame) + { + // Framed header expand a little outside the default padding + frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; + frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; + } + + const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing + const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser + ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) + const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y); + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + + // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child. + // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). + // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero. + if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavCloseFromChild) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + window->DC.TreeDepthMayCloseOnPop |= (1 << window->DC.TreeDepth); + + bool item_add = ItemAdd(interact_bb, id); + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + window->DC.LastItemDisplayRect = frame_bb; + + if (!item_add) + { + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; + } + + // Flags that affects opening behavior: + // - 0(default) ..................... single-click anywhere to open + // - OpenOnDoubleClick .............. double-click anywhere to open + // - OpenOnArrow .................... single-click on arrow to open + // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open + ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); + + bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + { + bool toggled = false; + if (pressed) + { + toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id); + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover); + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + toggled |= g.IO.MouseDoubleClicked[0]; + if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = false; + } + + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) + { + toggled = true; + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavMoveRequestCancel(); + } + + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y); + if (display_frame) + { + // Framed type + RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + RenderTriangle(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + if (g.LogEnabled) + { + // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. + const char log_prefix[] = "\n##"; + const char log_suffix[] = "##"; + LogRenderedText(&text_pos, log_prefix, log_prefix+3); + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); + } + else + { + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + } + } + else + { + // Unframed typed for tree nodes + if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) + { + RenderFrame(frame_bb.Min, frame_bb.Max, col, false); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + } + + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); + else if (!(flags & ImGuiTreeNodeFlags_Leaf)) + RenderTriangle(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogRenderedText(&text_pos, ">"); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label); +} + +bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_open && !*p_open) + return false; + + ImGuiID id = window->GetID(label); + bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label); + if (p_open) + { + // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + ImGuiContext& g = *GImGui; + float button_sz = g.FontSize * 0.5f; + ImGuiItemHoveredDataBackup last_item_backup; + if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_sz, window->DC.LastItemRect.Min.y + g.Style.FramePadding.y + button_sz), button_sz)) + *p_open = false; + last_item_backup.Restore(); + } + + return is_open; +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, label, NULL); +} + +void ImGui::TreeAdvanceToLabelPos() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextTreeNodeOpenVal = is_open; + g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::PushID(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id)); +} + +void ImGui::PushID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id_begin, str_id_end)); +} + +void ImGui::PushID(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PushID(int int_id) +{ + const void* ptr_id = (void*)(intptr_t)int_id; + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PopID() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.pop_back(); +} + +ImGuiID ImGui::GetID(const char* str_id) +{ + return GImGui->CurrentWindow->GetID(str_id); +} + +ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) +{ + return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); +} + +ImGuiID ImGui::GetID(const void* ptr_id) +{ + return GImGui->CurrentWindow->GetID(ptr_id); +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x*2); + return; + } + + // Render and stay on same line + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + SameLine(0, style.FramePadding.x*2); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin = g.TempBuffer; + const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + // Render + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + ImFormatString(buf, buf_size, display_format, *(int*)data_ptr); + else if (data_type == ImGuiDataType_Float) + ImFormatString(buf, buf_size, display_format, *(float*)data_ptr); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%d", *(int*)data_ptr); + else + ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr); + } + else if (data_type == ImGuiDataType_Float) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%f", *(float*)data_ptr); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + else + ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr); + } +} + +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2)// Store into value1 +{ + if (data_type == ImGuiDataType_Int) + { + if (op == '+') + *(int*)value1 = *(int*)value1 + *(const int*)value2; + else if (op == '-') + *(int*)value1 = *(int*)value1 - *(const int*)value2; + } + else if (data_type == ImGuiDataType_Float) + { + if (op == '+') + *(float*)value1 = *(float*)value1 + *(const float*)value2; + else if (op == '-') + *(float*)value1 = *(float*)value1 - *(const float*)value2; + } +} + +// User can input math operators (e.g. +100) to edit a numerical values. +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format) +{ + while (ImCharIsSpace(*buf)) + buf++; + + // We don't support '-' op because it would conflict with inputing negative value. + // Instead you can use +-100 to subtract from an existing value + char op = buf[0]; + if (op == '+' || op == '*' || op == '/') + { + buf++; + while (ImCharIsSpace(*buf)) + buf++; + } + else + { + op = 0; + } + if (!buf[0]) + return false; + + if (data_type == ImGuiDataType_Int) + { + if (!scalar_format) + scalar_format = "%d"; + int* v = (int*)data_ptr; + const int old_v = *v; + int arg0i = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1) + return false; + + // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision + float arg1f = 0.0f; + if (op == '+') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i + arg1f); } // Add (use "+-" to subtract) + else if (op == '*') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i * arg1f); } // Multiply + else if (op == '/') { if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }// Divide + else { if (sscanf(buf, scalar_format, &arg0i) == 1) *v = arg0i; } // Assign constant (read as integer so big values are not lossy) + return (old_v != *v); + } + else if (data_type == ImGuiDataType_Float) + { + // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in + scalar_format = "%f"; + float* v = (float*)data_ptr; + const float old_v = *v; + float arg0f = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1) + return false; + + float arg1f = 0.0f; + if (sscanf(buf, scalar_format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + return (old_v != *v); + } + + return false; +} + +// Create text input in place of a slider (when CTRL+Clicking on slider) +// FIXME: Logic is messy and confusing. +bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) + // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id + SetActiveID(g.ScalarAsInputTextId, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetHoveredID(0); + FocusableItemUnregister(window); + + char buf[32]; + DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf)); + bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); + if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget + { + IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID (else we'd need to store them both, which is also possible) + g.ScalarAsInputTextId = g.ActiveId; + SetHoveredID(id); + } + if (text_value_changed) + return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL); + return false; +} + +// Parse display precision back from the display format string +int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) +{ + int precision = default_precision; + while ((fmt = strchr(fmt, '%')) != NULL) + { + fmt++; + if (fmt[0] == '%') { fmt++; continue; } // Ignore "%%" + while (*fmt >= '0' && *fmt <= '9') + fmt++; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 10) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + break; + } + return precision; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision); +} + +float ImGui::RoundScalar(float value, int decimal_precision) +{ + // Round past decimal precision + // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0 + // FIXME: Investigate better rounding methods + if (decimal_precision < 0) + return value; + const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision); + bool negative = value < 0.0f; + value = fabsf(value); + float remainder = fmodf(value, min_step); + if (remainder <= min_step*0.5f) + value -= remainder; + else + value += (min_step - remainder); + return negative ? -value : value; +} + +static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) +{ + if (v_min == v_max) + return 0.0f; + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_non_linear) + { + if (v_clamped < 0.0f) + { + const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f,v_max) - v_min); + return (1.0f - powf(f, 1.0f/power)) * linear_zero_pos; + } + else + { + const float f = (v_clamped - ImMax(0.0f,v_min)) / (v_max - ImMax(0.0f,v_min)); + return linear_zero_pos + powf(f, 1.0f/power) * (1.0f - linear_zero_pos); + } + } + + // Linear slider + return (v_clamped - v_min) / (v_max - v_min); +} + +bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32((g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Nav) ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; + + const float grab_padding = 2.0f; + const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); + float grab_sz; + if (decimal_precision != 0) + grab_sz = ImMin(style.GrabMinSize, slider_sz); + else + grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); // Integer sliders, if possible have the grab size represent 1 unit + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz*0.5f; + const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz*0.5f; + + // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f + float linear_zero_pos = 0.0f; // 0.0->1.0f + if (v_min * v_max < 0.0f) + { + // Different sign + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); + linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); + } + else + { + // Same sign + linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; + } + + // Process interacting with the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (!is_horizontal) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); + float delta = is_horizontal ? delta2.x : -delta2.y; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + { + ClearActiveID(); + } + else if (delta != 0.0f) + { + clicked_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (decimal_precision == 0 && !is_non_linear) + { + if (fabsf(v_max - v_min) <= 100.0f || IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (v_max - v_min); // Gamepad/keyboard tweak speeds in integer steps + else + delta /= 100.0f; + } + else + { + delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + if (IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta /= 10.0f; + } + if (IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= 10.0f; + set_new_value = true; + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits + set_new_value = false; + else + clicked_t = ImSaturate(clicked_t + delta); + } + } + + if (set_new_value) + { + float new_value; + if (is_non_linear) + { + // Account for logarithmic scale on both sides of the zero + if (clicked_t < linear_zero_pos) + { + // Negative: rescale to the negative range before powering + float a = 1.0f - (clicked_t / linear_zero_pos); + a = powf(a, power); + new_value = ImLerp(ImMin(v_max,0.0f), v_min, a); + } + else + { + // Positive: rescale to the positive range before powering + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f) + a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = clicked_t; + a = powf(a, power); + new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); + } + } + else + { + // Linear slider + new_value = ImLerp(v_min, v_max, clicked_t); + } + + // Round past decimal precision + new_value = RoundScalar(new_value, decimal_precision); + if (*v != new_value) + { + *v = new_value; + value_changed = true; + } + } + } + + // Draw + float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (!is_horizontal) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + ImRect grab_bb; + if (is_horizontal) + grab_bb = ImRect(ImVec2(grab_pos - grab_sz*0.5f, frame_bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz*0.5f, frame_bb.Max.y - grab_padding)); + else + grab_bb = ImRect(ImVec2(frame_bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f), ImVec2(frame_bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f)); + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + return value_changed; +} + +// Use power!=1.0 for logarithmic sliders. +// Adjust display_format to decorate the value with a prefix or a suffix. +// "%.3f" 1.234 +// "%5.2f secs" 01.23 secs +// "Gold: %.0f" Gold: 1 +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual slider behavior + render grab + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + + // Actual slider behavior + render grab + bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) +{ + float v_deg = (*v_rad) * 360.0f / (2*IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); + *v_rad = v_deg * (2*IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 2, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 3, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 4, v_min, v_max, display_format, power); +} + +bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 2, v_min, v_max, display_format); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 3, v_min, v_max, display_format); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 4, v_min, v_max, display_format); +} + +bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + bool value_changed = false; + + // Process interacting with the drag + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId == id) + { + if (g.ActiveIdIsJustActivated) + { + // Lock current value on click + g.DragCurrentValue = *v; + g.DragLastMouseDelta = ImVec2(0.f, 0.f); + } + + if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX) + v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio; + + float v_cur = g.DragCurrentValue; + const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f); + float adjust_delta = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid()) + { + adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x; + if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f) + adjust_delta *= g.DragSpeedScaleFast; + if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f) + adjust_delta *= g.DragSpeedScaleSlow; + g.DragLastMouseDelta.x = mouse_drag_delta.x; + } + if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; + if (v_min < v_max && ((v_cur >= v_max && adjust_delta > 0.0f) || (v_cur <= v_min && adjust_delta < 0.0f))) // This is to avoid applying the saturation when already past the limits + adjust_delta = 0.0f; + v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); + } + adjust_delta *= v_speed; + + if (fabsf(adjust_delta) > 0.0f) + { + if (fabsf(power - 1.0f) > 0.001f) + { + // Logarithmic curve on both side of 0.0 + float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur; + float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f; + float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign); + float v1_abs = v1 >= 0.0f ? v1 : -v1; + float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line + v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign + } + else + { + v_cur += adjust_delta; + } + + // Clamp + if (v_min < v_max) + v_cur = ImClamp(v_cur, v_min, v_max); + g.DragCurrentValue = v_cur; + } + + // Round to user desired precision, then apply + v_cur = RoundScalar(v_cur, decimal_precision); + if (*v != v_cur) + { + *v = v_cur; + value_changed = true; + } + } + + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Drag turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual drag behavior + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + return value_changed; +} + +bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* display_format, const char* display_format_max, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, display_format_max ? display_format_max : display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* display_format, const char* display_format_max) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, display_format_max ? display_format_max : display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + if (graph_size.x == 0.0f) + graph_size.x = CalcItemWidth(); + if (graph_size.y == 0.0f) + graph_size.y = label_size.y + (style.FramePadding.y * 2); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0, &frame_bb)) + return; + const bool hovered = ItemHoverable(inner_bb, 0); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + if (values_count > 0) + { + int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + int v_hovered = -1; + if (hovered) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + v_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); +} + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); + } + + if (!ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + *v = !(*v); + + RenderNavHighlight(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + if (*v) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + bool v = ((*flags & flags_value) == flags_value); + bool pressed = Checkbox(label, &v); + if (pressed) + { + if (v) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + + return pressed; +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb.Add(text_bb); + } + + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = (float)(int)center.x + 0.5f; + center.y = (float)(int)center.y + 0.5f; + const float radius = check_bb.GetHeight() * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + + RenderNavHighlight(total_bb, id); + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); + if (active) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + { + *v = v_button; + } + return pressed; +} + +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImFont* font = GImGui->Font; + const float line_height = GImGui->FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((unsigned short)c) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +namespace ImGuiStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) +{ + const ImWchar* text = obj->Text.Data; + const ImWchar* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool is_separator(unsigned int c) { return ImCharIsSpace(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->Text[idx-1] ) && !is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +#ifdef __APPLE__ // FIXME: Move setting to IO structure +static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +#else +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +#endif +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) +{ + ImWchar* dst = obj->Text.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text + const ImWchar* src = obj->Text.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) +{ + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + if (new_text_len + text_len + 1 > obj->Text.Size) + return false; + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA) + return false; + + ImWchar* text = obj->Text.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->Text[obj->CurLenW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_SHIFT 0x20000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + +} + +void ImGuiTextEditState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &StbState, key); + CursorFollow = true; + CursorAnimReset(); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos + bytes_count >= pos) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen + 1 >= BufSize) + return; + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + unsigned int c = *p_char; + + if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } + + if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. + return false; + + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank)) + { + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + *p_char = (c += (unsigned int)('A'-'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsSpace(c)) + return false; + } + + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Edit a string of text +// NB: when active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while active has no effect. +// FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188 +bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + + ImGuiContext& g = *GImGui; + const ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + + if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); + + ImGuiWindow* draw_window = window; + if (is_multiline) + { + ItemAdd(total_bb, id, &frame_bb); + if (!BeginChildFrame(id, frame_bb.GetSize())) + { + EndChildFrame(); + EndGroup(); + return false; + } + draw_window = GetCurrentWindow(); + size.x -= draw_window->ScrollbarSizes.x; + } + else + { + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // Password pushes a temporary font with only a fallback glyph + if (is_password) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->DisplayOffset = g.Font->DisplayOffset; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // NB: we are only allowed to access 'edit_state' if we are the active widget. + ImGuiTextEditState& edit_state = g.InputTextState; + + const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); + const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + + bool clear_active_id = false; + + bool select_all = (g.ActiveId != id) && (((flags & ImGuiInputTextFlags_AutoSelectAll) != 0) || (g.NavInputId == id)) && (!is_multiline); + if (focus_requested || user_clicked || user_scrolled || g.NavInputId == id) + { + if (g.ActiveId != id) + { + // Start edition + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int prev_len_w = edit_state.CurLenW; + edit_state.Text.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + edit_state.InitialText.resize(buf_size+1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + edit_state.CursorAnimReset(); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). + const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW); + if (recycle_state) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + edit_state.CursorClamp(); + } + else + { + edit_state.Id = id; + edit_state.ScrollX = 0.0f; + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); + if (!is_multiline && focus_requested_by_code) + select_all = true; + } + if (flags & ImGuiInputTextFlags_AlwaysInsertMode) + edit_state.StbState.insert_mode = true; + if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + select_all = true; + } + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)); + } + else if (io.MouseClicked[0]) + { + // Release focus when we click outside + clear_active_id = true; + } + + bool value_changed = false; + bool enter_pressed = false; + + if (g.ActiveId == id) + { + if (!is_editable && !g.ActiveIdIsJustActivated) + { + // When read-only we always use the live data passed to the function + edit_state.Text.resize(buf_size+1); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); + edit_state.CursorClamp(); + } + + edit_state.BufSizeA = buf_size; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); + + const bool osx_double_click_selects_words = io.OptMacOSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text + if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0])) + { + edit_state.SelectAll(); + edit_state.SelectedAllMouseLock = true; + } + else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0]) + { + // Select a word only, OS X style (by simulating keystrokes) + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) + { + if (hovered) + { + stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + } + } + else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + edit_state.CursorFollow = true; + } + if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) + edit_state.SelectedAllMouseLock = false; + + if (io.InputCharacters[0]) + { + // Process text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow CTRL+ALT as some keyboards (e.g. German) use AltGR - which is Alt+Ctrl - to input certain characters. + if (!(io.KeyCtrl && !io.KeyAlt) && is_editable) + { + for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) + if (unsigned int c = (unsigned int)io.InputCharacters[n]) + { + // Insert character if they pass filtering + if (!InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + edit_state.OnKeyPressed((int)c); + } + } + + // Consume characters + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + } + } + + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + // Handle key-presses + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_shortcut_key_only = (io.OptMacOSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl + const bool is_wordmove_key_down = io.OptMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = io.OptMacOSXBehaviors && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; + const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; + + const bool is_cut = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_copy = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_paste = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; + + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) + { + if (!edit_state.HasSelection()) + { + if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); + else if (io.OptMacOSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); + } + edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (IsKeyPressedMap(ImGuiKey_Enter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + enter_pressed = clear_active_id = true; + } + else if (is_editable) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } + else if (is_cut || is_copy) + { + // Cut, Copy + if (io.SetClipboardTextFn) + { + const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; + const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; + edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie); + SetClipboardText(edit_state.TempTextBuffer.Data); + } + + if (is_cut) + { + if (!edit_state.HasSelection()) + edit_state.SelectAll(); + edit_state.CursorFollow = true; + stb_textedit_cut(&edit_state, &edit_state.StbState); + } + } + else if (is_paste) + { + // Paste + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s; ) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) + break; + if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + edit_state.CursorFollow = true; + } + ImGui::MemFree(clipboard_filtered); + } + } + } + + if (g.ActiveId == id) + { + if (cancel_edit) + { + // Restore initial value + if (is_editable) + { + ImStrncpy(buf, edit_state.InitialText.Data, buf_size); + value_changed = true; + } + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + if (is_editable) + { + edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL); + } + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_COUNT; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + event_flag = ImGuiInputTextFlags_CallbackAlways; + + if (event_flag) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = user_data; + callback_data.ReadOnly = !is_editable; + + callback_data.EventKey = event_key; + callback_data.Buf = edit_state.TempTextBuffer.Data; + callback_data.BufTextLen = edit_state.CurLenA; + callback_data.BufSize = edit_state.BufSizeA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) + ImWchar* text = edit_state.Text.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA); + IM_ASSERT(callback_data.Flags == flags); + if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); + if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); + if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); + if (callback_data.BufDirty) + { + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL); + edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + edit_state.CursorAnimReset(); + } + } + } + + // Copy back to user buffer + if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0) + { + ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size); + value_changed = true; + } + } + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + + // Render + // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL; + + RenderNavHighlight(frame_bb, id); + if (!is_multiline) + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.f, 0.f); + const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); + if (g.ActiveId == id || is_currently_scrolling) + { + edit_state.CursorAnim += io.DeltaTime; + + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar* text_begin = edit_state.Text.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. + const ImWchar* searches_input_ptr[2]; + searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; + searches_input_ptr[1] = NULL; + int searches_remaining = 1; + int searches_result_line_number[2] = { -1, -999 }; + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + searches_result_line_number[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + for (const ImWchar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; + if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_number[0] * g.FontSize; + if (searches_result_line_number[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_number[1] * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(size.x, line_count * g.FontSize); + } + + // Scroll + if (edit_state.CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = size.x * 0.25f; + if (cursor_offset.x < edit_state.ScrollX) + edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= edit_state.ScrollX) + edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); + } + else + { + edit_state.ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + float scroll_y = draw_window->Scroll.y; + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - size.y >= scroll_y) + scroll_y = cursor_offset.y - size.y; + draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag + draw_window->Scroll.y = scroll_y; + render_pos.y = draw_window->DC.CursorPos.y; + } + } + edit_state.CursorFollow = false; + const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); + + // Draw selection + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); + + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); + ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = render_pos.x - render_scroll.x; + rect_pos.y += g.FontSize; + } + } + + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect); + + // Draw blinking cursor + bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (is_editable) + g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + } + else + { + // Render text only + const char* buf_end = NULL; + if (is_multiline) + text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + if (is_multiline) + { + Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line + EndChildFrame(); + EndGroup(); + } + + if (is_password) + PopFont(); + + // Log as text + if (g.LogEnabled && !is_password) + LogRenderedText(&render_pos, buf_display, NULL); + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +// NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument) +bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + BeginGroup(); + PushID(label); + const ImVec2 button_sz = ImVec2(GetFrameHeight(), GetFrameHeight()); + if (step_ptr) + PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2)); + + char buf[64]; + DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf)); + + bool value_changed = false; + if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal)) + extra_flags |= ImGuiInputTextFlags_CharsDecimal; + extra_flags |= ImGuiInputTextFlags_AutoSelectAll; + if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format); + + // Step buttons + if (step_ptr) + { + PopItemWidth(); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + } + PopID(); + + if (label_size.x > 0) + { + SameLine(0, style.ItemInnerSpacing.x); + RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); + ItemSize(label_size, style.FramePadding.y); + } + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char display_format[16]; + if (decimal_precision < 0) + strcpy(display_format, "%f"); // Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1 + else + ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision); + return InputScalarEx(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), display_format, extra_flags); +} + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), scalar_format, extra_flags); +} + +bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 2, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 3, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 4, decimal_precision, extra_flags); +} + +bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 2, extra_flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 3, extra_flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 4, extra_flags); +} + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + // Always consume the SetNextWindowSizeConstraint() call in our early return paths + ImGuiContext& g = *GImGui; + ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond; + g.NextWindowData.SizeConstraintCond = 0; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + bool popup_open = IsPopupOpen(id); + + const float arrow_size = GetFrameHeight(); + const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING + RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); + if (preview_value != NULL) + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((pressed || g.NavActivateId == id) && !popup_open) + { + if (window->DC.NavLayerCurrent == 0) + window->NavLastIds[0] = id; + OpenPopupEx(id); + popup_open = true; + } + + if (!popup_open) + return false; + + if (backup_next_window_size_constraint) + { + g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint; + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + } + + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + + // Peak into expected window size so we can position it + if (ImGuiWindow* popup_window = FindWindowByName(name)) + if (popup_window->WasActive) + { + ImVec2 size_contents = CalcSizeContents(popup_window); + ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents)); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; + ImVec2 pos = FindBestWindowPosForPopup(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + if (!Begin(name, NULL, window_flags)) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + + // Horizontally align ourselves with the framed text + if (style.FramePadding.x != style.WindowPadding.x) + Indent(style.FramePadding.x - style.WindowPadding.x); + + return true; +} + +void ImGui::EndCombo() +{ + const ImGuiStyle& style = GImGui->Style; + if (style.FramePadding.x != style.WindowPadding.x) + Unindent(style.FramePadding.x - style.WindowPadding.x); + EndPopup(); +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + const char* preview_text = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_text); + + // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here. + if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond) + { + float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, popup_max_height)); + } + + if (!BeginCombo(label, preview_text, 0)) + return false; + + // Display items + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + return value_changed; +} + +static bool Items_ArrayGetter(void* data, int idx, const char** out_text) +{ + const char* const* items = (const char* const*)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string. +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped. + PopClipRect(); + + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrentLineTextBaseOffset; + ImRect bb(pos, pos + size); + ItemSize(bb); + + // Fill horizontal space. + ImVec2 window_padding = window->WindowPadding; + float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; + float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); + ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y); + ImRect bb_with_spacing(pos, pos + size_draw); + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) + bb_with_spacing.Max.x += window_padding.x; + + // Selectables are tightly packed together, we extend the box to cover spacing between selectable. + float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); + float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); + float spacing_R = style.ItemSpacing.x - spacing_L; + float spacing_D = style.ItemSpacing.y - spacing_U; + bb_with_spacing.Min.x -= spacing_L; + bb_with_spacing.Min.y -= spacing_U; + bb_with_spacing.Max.x += spacing_R; + bb_with_spacing.Max.y += spacing_D; + if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id)) + { + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + PushColumnClipRect(); + return false; + } + + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID; + if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease; + if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; + if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + bool hovered, held; + bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags); + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; + + // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets) + if (pressed || hovered)// && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)) + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerActiveMask) + { + g.NavDisableHighlight = true; + SetNavID(id, window->DC.NavLayerCurrent); + } + + // Render + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f); + RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + { + PushColumnClipRect(); + bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x); + } + + if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); + if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + return pressed; +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + +// Helper to calculate the size of a listbox and display a label on the right. +// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" +bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = GetStyle(); + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. + + BeginGroup(); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) +{ + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + // However we don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. + // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); + + // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; + return ListBoxHeader(label, size); +} + +void ImGui::ListBoxFooter() +{ + ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; + const ImRect bb = parent_window->DC.LastItemRect; + const ImGuiStyle& style = GetStyle(); + + EndChildFrame(); + + // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) + // We call SameLine() to restore DC.CurrentLine* data + SameLine(); + parent_window->DC.CursorPos = bb.Min; + ItemSize(bb, style.FramePadding.y); + EndGroup(); +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + if (!ListBoxHeader(label, items_count, height_in_items)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. + bool value_changed = false; + ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + ListBoxFooter(); + return value_changed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation we render neither the shortcut neither the selected tick mark + float w = label_size.x; + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); + if (shortcut_size.x > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + SetNextWindowPos(ImVec2(0.0f, 0.0f)); + SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f)); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); + if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar) + || !BeginMenuBar()) + { + End(); + PopStyleVar(2); + return false; + } + g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x; + return true; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + ImGuiContext& g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) + FocusFrontMostActiveWindow(g.NavWindow); + + End(); + PopStyleVar(2); +} + +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Save position + PushID("##menubar"); + + // We don't clip with regular window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); + clip_rect.ClipWith(window->WindowRectClipped); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y);// + g.Style.FramePadding.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. + if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + { + ImGuiWindow* nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) + { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) + IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check + FocusWindow(window); + SetNavIDAndMoveMouse(window->NavLastIds[1], 1, window->NavRectRel[1]); + g.NavLayer = 1; + g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + } + } + + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x; + window->DC.GroupStack.back().AdvanceCursor = false; + EndGroup(); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.MenuBarAppending = false; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + + bool pressed; + bool menu_is_open = IsPopupOpen(id); + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) + g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos). + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestPopupWindowPos() in Begin() + popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + float w = label_size.x; + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a menu + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); + if (!enabled) PopStyleColor(); + } + + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) + g.NavWindow = backed_nav_window; + + bool want_open = false, want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_within_opened_triangle = false; + if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar)) + { + if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) + { + ImRect next_window_rect = next_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug + } + } + + want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); + want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); + + if (g.NavActivateId == id) + { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id)) + ClosePopupToLevel(g.CurrentPopupStack.Size); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) + OpenPopup(label); + + if (menu_is_open) + { + SetNextWindowPos(popup_pos, ImGuiCond_Always); + ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + + return menu_is_open; +} + +void ImGui::EndMenu() +{ + // Nav: When a left move request _within our child menu_ failed, close the menu. + // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. + // However it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + NavMoveRequestCancel(); + } + + EndPopup(); +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + BeginTooltipEx(0, true); + + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextUnformatted(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + EndTooltip(); +} + +static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) +{ + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); + return IM_COL32(r, g, b, 0xFF); +} + +// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. +// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether. +void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) + { + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col)); + window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); + + int yi = 0; + for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) + { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + int rounding_corners_flags_cell = 0; + if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } + rounding_corners_flags_cell &= rounding_corners_flags; + window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); + } + } + } + else + { + window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); + } +} + +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags__InputsMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask; + if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; + if ((flags & ImGuiColorEditFlags__PickerMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// A little colored square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + float default_size = GetFrameHeight(); + if (size.x == 0.0f) + size.x = default_size; + if (size.y == 0.0f) + size.y = default_size; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) + { + float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); + RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); + } + RenderNavHighlight(bb, id); + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + + // Drag and Drop Source + if (g.ActiveId == id && BeginDragDropSource()) // NB: The ActiveId test is merely an optional micro-optimization + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextUnformatted("Color"); + EndDragDropSource(); + hovered = false; + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; + if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1,0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + if (flags & ImGuiColorEditFlags_NoAlpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); +} + +static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + ImGui::PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) ImGui::Separator(); + ImGui::PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); + if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + ImGui::SetCursorScreenPos(backup_pos); + ImVec4 dummy_ref_col; + memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4)); + ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) ImGui::Separator(); + ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + ImGui::EndPopup(); +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_items_all = CalcItemWidth() - w_extra; + const char* label_display_end = FindRenderedTextEnd(label); + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + const ImGuiColorEditFlags flags_untouched = flags; + + BeginGroup(); + PushID(label); + + // If we're not showing any slider there's no point in doing any HSV conversions + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__InputsMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask); + if (!(flags & ImGuiColorEditFlags__DataTypeMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask)); + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + + const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + const char* fmt_table_int[3][4] = + { + { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, // Short display + { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, // Long display for RGBA + { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } // Long display for HSVA + }; + const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1; + + PushItemWidth(w_item_one); + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + if (n + 1 == components) + PushItemWidth(w_item_last); + if (flags & ImGuiColorEditFlags_Float) + value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + else + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + PopItemWidth(); + PopItemWidth(); + } + else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255)); + PushItemWidth(w_items_all); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsSpace(*p)) + p++; + i[0] = i[1] = i[2] = i[3] = 0; + if (alpha) + sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + PopItemWidth(); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + if (!(flags & ImGuiColorEditFlags_NoInputs)) + SameLine(0, style.ItemInnerSpacing.x); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + if (BeginPopup("picker")) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextUnformatted(label, label_display_end); + Separator(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemWidth(); + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + + // Convert back + if (picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + if (value_changed) + { + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + } + + PopID(); + EndGroup(); + + // Drag and Drop Target + if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && BeginDragDropTarget()) // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); + value_changed = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = true; + } + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +// 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. +static void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) +{ + switch (direction) + { + case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_None: case ImGuiDir_Count_: break; // Fix warnings + } +} + +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) +{ + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); +} + +// ColorPicker +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImDrawList* draw_list = window->DrawList; + + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + PushID(label); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(flags, col); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H,S,V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V); + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + PushItemFlag(ImGuiItemFlags_NoNav, true); + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1)) + { + // Interactive with Hue wheel + H = atan2f(current_off.y, current_off.x) / IM_PI*0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = cosf(-H * 2.0f * IM_PI); + float sin_hue_angle = sinf(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); + + // R,G,B and H,S,V slider color editor + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB); + if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV); + if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit), if any + if (value_changed) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f)); + + const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) }; + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list->VtxBuffer.Data + vert_start_idx, draw_list->VtxBuffer.Data + vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = cosf(H * 2.0f * IM_PI); + float sin_hue_angle = sinf(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE); + draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS); + draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK); + draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS); + draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]); + float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + EndGroup(); + PopID(); + + return value_changed && memcmp(backup_initial_col, col, components * sizeof(float)); +} + +// Horizontal separating line. +void ImGui::Separator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + ImGuiWindowFlags flags = 0; + if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0) + flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected + if (flags & ImGuiSeparatorFlags_Vertical) + { + VerticalSeparator(); + return; + } + + // Horizontal Separator + if (window->DC.ColumnsSet) + PopClipRect(); + + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + if (!window->DC.GroupStack.empty()) + x1 += window->DC.IndentX; + + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); + ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. + if (!ItemAdd(bb, 0)) + { + if (window->DC.ColumnsSet) + PushColumnClipRect(); + return; + } + + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); + + if (g.LogEnabled) + LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); + + if (window->DC.ColumnsSet) + { + PushColumnClipRect(); + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; + } +} + +void ImGui::VerticalSeparator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); + ItemSize(ImVec2(bb.GetWidth(), 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); +} + +bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool item_add = ItemAdd(bb, id); + window->DC.ItemFlags = item_flags_backup; + if (!item_add) + return false; + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + + if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // Minimum pane size + if (mouse_delta < min_size1 - *size1) + mouse_delta = min_size1 - *size1; + if (mouse_delta > *size2 - min_size2) + mouse_delta = *size2 - min_size2; + + // Apply resize + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + } + + // Render + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding); + + return held; +} + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0,0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + ItemAdd(bb, 0); +} + +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) +void ImGui::BeginGroup() +{ + ImGuiWindow* window = GetCurrentWindow(); + + window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndentX = window->DC.IndentX; + group_data.BackupGroupOffsetX = window->DC.GroupOffsetX; + group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight; + group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; + group_data.BackupLogLinePosY = window->DC.LogLinePosY; + group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive; + group_data.AdvanceCursor = true; + + window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX; + window->DC.IndentX = window->DC.GroupOffsetX; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.CurrentLineHeight = 0.0f; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; +} + +void ImGui::EndGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls + + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + + ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); + group_bb.Max = ImMax(group_bb.Min, group_bb.Max); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight; + window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; + window->DC.IndentX = group_data.BackupIndentX; + window->DC.GroupOffsetX = group_data.BackupGroupOffsetX; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + + if (group_data.AdvanceCursor) + { + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); + ItemAdd(group_bb, 0); + } + + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context. + const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); + if (active_id_within_group) + window->DC.LastItemId = g.ActiveId; + window->DC.LastItemRect = group_bb; + + window->DC.GroupStack.pop_back(); + + //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] +} + +// Gets back to previous line and continue with horizontal layout +// pos_x == 0 : follow right after previous item +// pos_x != 0 : align to specified x position (relative to window/group left) +// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float pos_x, float spacing_w) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + if (pos_x != 0.0f) + { + if (spacing_w < 0.0f) spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + else + { + if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrentLineHeight = window->DC.PrevLineHeight; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + if (window->DC.CurrentLineHeight > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0,0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.ColumnsSet == NULL) + return; + + ImGuiContext& g = *GImGui; + PopItemWidth(); + PopClipRect(); + + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + if (++columns->Current < columns->Count) + { + // Columns 1+ cancel out IndentX + window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(columns->Current); + } + else + { + window->DC.ColumnsOffsetX = 0.0f; + window->DrawList->ChannelsSetCurrent(0); + columns->Current = 0; + columns->CellMinY = columns->CellMaxY; + } + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + window->DC.CursorPos.y = columns->CellMinY; + window->DC.CurrentLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = 0.0f; + + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; +} + +static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) +{ + return offset_norm * (columns->MaxX - columns->MinX); +} + +static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) +{ + return offset / (columns->MaxX - columns->MinX); +} + +static inline float GetColumnsRectHalfWidth() { return 4.0f; } + +static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + /* + if (g.ActiveId) + { + ImGuiContext& g = *GImGui; + const ImGuiID column_id = columns->ColumnsSetId + ImGuiID(column_index); + if (g.ActiveId == column_id) + return GetDraggedColumnOffset(columns, column_index); + } + */ + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return OffsetNormToPixels(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + if (column_index < 0) + column_index = columns->Current; + + PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); +} + +static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +{ + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiColumnsSet()); + ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count > 1); + IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + // Acquire storage for the columns set + ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.ColumnsSet = columns; + + // Set state for first column + const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->Size.x -window->ScrollbarSizes.x); + columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range + //column->MaxX = content_region_width - window->Scroll.x - ((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x; + columns->MaxX = content_region_width - window->Scroll.x; + columns->StartPosY = window->DC.CursorPos.y; + columns->StartMaxPosX = window->DC.CursorMaxPos.x; + columns->CellMinY = columns->CellMaxY = window->DC.CursorPos.y; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize defaults + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count + 1; n++) + { + // Clamp position + ImGuiColumnData* column = &columns->Columns[n]; + float t = column->OffsetNorm; + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + t = ImMin(t, PixelsToOffsetNorm(columns, (columns->MaxX - columns->MinX) - g.Style.ColumnsMinSpacing * (columns->Count - n))); + column->OffsetNorm = t; + + if (n == columns_count) + continue; + + // Compute clipping rectangle + float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); + float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWith(window->ClipRect); + } + + window->DrawList->ChannelsSplit(columns->Count); + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + PopClipRect(); + window->DrawList->ChannelsMerge(); + + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->CellMaxY; + if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = ImMax(columns->StartMaxPosX, columns->MaxX); // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + bool is_being_resized = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) + { + const float y1 = columns->StartPosY; + const float y2 = window->DC.CursorPos.y; + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction + const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) + { + ButtonBehavior(column_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) + dragging_column = n; + } + + // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->DC.ColumnsSet = NULL; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); +} + +// [2017/12: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count != columns_count) + EndColumns(); + + ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); + //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +void ImGui::Indent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::Unindent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id ? str_id : "#TreePush"); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); +} + +void ImGui::TreePushRawID(ImGuiID id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + window->IDStack.push_back(id); +} + +void ImGui::TreePop() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Unindent(); + + window->DC.TreeDepth--; + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + if (g.NavIdIsAlive && (window->DC.TreeDepthMayCloseOnPop & (1 << window->DC.TreeDepth))) + { + SetNavID(window->IDStack.back(), g.NavLayer); + NavMoveRequestCancel(); + } + window->DC.TreeDepthMayCloseOnPop &= (1 << window->DC.TreeDepth) - 1; + + PopID(); +} + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//----------------------------------------------------------------------------- +// DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; +} + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags, int mouse_button) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case + return false; + if (g.IO.MouseDown[mouse_button] == false) + return false; + + if (source_id == 0) + { + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; + if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + if (is_hovered) + SetHoveredID(source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + } + else + { + window = NULL; + source_id = ImHash("#SourceExtern", 0); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // FIXME-DRAG + //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :( + SetNextWindowPos(g.IO.MousePos); + PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); + BeginTooltip(); + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + EndTooltip(); + PopStyleColor(); + //PopStyleVar(); + } + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 12 characters long"); + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy((void*)payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy((void*)payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + + const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(display_rect); + if (g.DragDropPayload.SourceId == id) + return false; + + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface < g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAG: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.DragDropActive); +} + +//----------------------------------------------------------------------------- +// PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#ifndef __MINGW32__ +#include +#else +#include +#endif +#endif + +// Win32 API clipboard implementation +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#endif + +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + static ImVector buf_local; + buf_local.clear(); + if (!OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return NULL; + } + if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle)) + { + int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; + buf_local.resize(buf_len); + ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); + } + GlobalUnlock(wbuf_handle); + CloseClipboard(); + return buf_local.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!OpenClipboard(NULL)) + return; + const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; + HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return; + } + ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle); + ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); + GlobalUnlock(wbuf_handle); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, wbuf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin(); +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + ImGuiContext& g = *GImGui; + g.PrivateClipboard.clear(); + const char* text_end = text + strlen(text); + g.PrivateClipboard.resize((int)(text_end - text) + 1); + memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); + g.PrivateClipboard[(int)(text_end - text)] = 0; +} + +#endif + +// Win32 API IME support (for Asian languages, etc.) +#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + +#include +#ifdef _MSC_VER +#pragma comment(lib, "imm32") +#endif + +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +{ + // Notify OS Input Method Editor of text input position + if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) + if (HIMC himc = ImmGetContext(hwnd)) + { + COMPOSITIONFORM cf; + cf.ptCurrentPos.x = x; + cf.ptCurrentPos.y = y; + cf.dwStyle = CFS_FORCE_POSITION; + ImmSetCompositionWindow(himc, &cf); + } +} + +#else + +static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + +#endif + +//----------------------------------------------------------------------------- +// HELP +//----------------------------------------------------------------------------- + +void ImGui::ShowMetricsWindow(bool* p_open) +{ + if (ImGui::Begin("ImGui Metrics", p_open)) + { + ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3); + ImGui::Text("%d allocations", (int)GImAllocatorActiveAllocationsCount); + static bool show_clip_rects = true; + ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_clip_rects); + ImGui::Separator(); + + struct Funcs + { + static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label) + { + bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); + if (draw_list == ImGui::GetWindowDrawList()) + { + ImGui::SameLine(); + ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) ImGui::TreePop(); + return; + } + + ImDrawList* overlay_draw_list = ImGui::GetOverlayDrawList(); // Render additional visuals into the top-most draw list + if (window && ImGui::IsItemHovered()) + overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!node_open) + return; + + int elem_offset = 0; + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; + if (pcmd->UserCallback) + { + ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + if (show_clip_rects && ImGui::IsItemHovered()) + { + ImRect clip_rect = pcmd->ClipRect; + ImRect vtxs_rect; + for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) + vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); + clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); + vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); + } + if (!pcmd_node_open) + continue; + + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + { + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangles_pos[3]; + for (int n = 0; n < 3; n++, vtx_i++) + { + ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; + triangles_pos[n] = v.pos; + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + } + ImGui::Selectable(buf, false); + if (ImGui::IsItemHovered()) + { + ImDrawListFlags backup_flags = overlay_draw_list->Flags; + overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. + overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); + overlay_draw_list->Flags = backup_flags; + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + static void NodeWindows(ImVector& windows, const char* label) + { + if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) + return; + for (int i = 0; i < windows.Size; i++) + Funcs::NodeWindow(windows[i], "Window"); + ImGui::TreePop(); + } + + static void NodeWindow(ImGuiWindow* window, const char* label) + { + if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) + return; + ImGuiWindowFlags flags = window->Flags; + NodeDrawList(window, window->DrawList, "DrawList"); + ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s..)", flags, + (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", + (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : ""); + ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window)); + ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed); + ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask); + ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + if (window->NavRectRel[0].IsFinite()) + ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); + else + ImGui::BulletText("NavRectRel[0]: "); + if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); + if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); + ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); + ImGui::TreePop(); + } + }; + + // Access private state, we are going to display the draw lists from last frame + ImGuiContext& g = *GImGui; + Funcs::NodeWindows(g.Windows, "Windows"); + if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size)) + { + for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++) + Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) + { + for (int i = 0; i < g.OpenPopupStack.Size; i++) + { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Internal state")) + { + const char* input_source_names[] = { "None", "Mouse", "Nav", "NavGamepad", "NavKeyboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_Count_); + ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec)", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, input_source_names[g.ActiveIdSource]); + ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); + ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + ImGui::TreePop(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- + +// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. +// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. +#ifdef IMGUI_INCLUDE_IMGUI_USER_INL +#include "imgui_user.inl" +#endif + +//----------------------------------------------------------------------------- diff --git a/apps/bench_shared/imgui/imgui.h b/apps/bench_shared/imgui/imgui.h new file mode 100644 index 00000000..7629e06c --- /dev/null +++ b/apps/bench_shared/imgui/imgui.h @@ -0,0 +1,1787 @@ +// dear imgui, v1.60 WIP +// (headers) + +// See imgui.cpp file for documentation. +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui + +#pragma once + +// User-editable configuration files (edit stock imconfig.h or define IMGUI_USER_CONFIG to your own filename) +#ifdef IMGUI_USER_CONFIG +#include IMGUI_USER_CONFIG +#endif +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" +#endif + +#include // FLT_MAX +#include // va_list +#include // ptrdiff_t, NULL +#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + +#define IMGUI_VERSION "1.60 WIP" + +// Define attributes of all API symbols declarations, e.g. for DLL under Windows. +#ifndef IMGUI_API +#define IMGUI_API +#endif + +// Define assertion handler. +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) +#endif + +// Helpers +// Some compilers support applying printf-style warnings to user functions. +#if defined(__clang__) || defined(__GNUC__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) +#define IM_OFFSETOF(_TYPE,_MEMBER) ((size_t)&(((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in modern C++. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +// Forward declarations +struct ImDrawChannel; // Temporary storage for outputting drawing commands out of order, used by ImDrawList::ChannelsSplit() +struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call) +struct ImDrawData; // All draw command lists required to render the frame +struct ImDrawList; // A single draw command list (generally one per window) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawVert; // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) +struct ImFont; // Runtime data for a single font within a parent ImFontAtlas +struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader +struct ImFontConfig; // Configuration data when adding a font or merging fonts +struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiOnceUponAFrame; // Simple helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro +struct ImGuiStorage; // Simple custom key value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTextFilter; // Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextBuffer; // Text buffer for logging/accumulating text +struct ImGuiTextEditCallbackData; // Shared state of ImGui::InputText() when using custom ImGuiTextEditCallback (rare/advanced use) +struct ImGuiSizeCallbackData; // Structure used to constraint window size in custom ways when using custom ImGuiSizeCallback (rare/advanced use) +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiContext; // ImGui context (opaque) + +// Typedefs and Enumerations (declared as int for compatibility and to not pollute the top of this file) +typedef unsigned int ImU32; // 32-bit unsigned integer (typically used to store packed colors) +typedef unsigned int ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) +typedef unsigned short ImWchar; // character for keyboard input/display +typedef void* ImTextureID; // user data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) +typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_ +typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ +typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_ +typedef int ImGuiNavInput; // enum: an input identifier for navigation // enum ImGuiNavInput_ +typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_ +typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImDrawCornerFlags; // flags: for ImDrawList::AddRect*() etc. // enum ImDrawCornerFlags_ +typedef int ImDrawListFlags; // flags: for ImDrawList // enum ImDrawListFlags_ +typedef int ImFontAtlasFlags; // flags: for ImFontAtlas // enum ImFontAtlasFlags_ +typedef int ImGuiColorEditFlags; // flags: for ColorEdit*(), ColorPicker*() // enum ImGuiColorEditFlags_ +typedef int ImGuiColumnsFlags; // flags: for *Columns*() // enum ImGuiColumnsFlags_ +typedef int ImGuiDragDropFlags; // flags: for *DragDrop*() // enum ImGuiDragDropFlags_ +typedef int ImGuiComboFlags; // flags: for BeginCombo() // enum ImGuiComboFlags_ +typedef int ImGuiFocusedFlags; // flags: for IsWindowFocused() // enum ImGuiFocusedFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() etc. // enum ImGuiHoveredFlags_ +typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ +typedef int ImGuiNavFlags; // flags: for io.NavFlags // enum ImGuiNavFlags_ +typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ +typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(),CollapsingHeader()// enum ImGuiTreeNodeFlags_ +typedef int ImGuiWindowFlags; // flags: for Begin*() // enum ImGuiWindowFlags_ +typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); +typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); +#if defined(_MSC_VER) && !defined(__clang__) +typedef unsigned __int64 ImU64; // 64-bit unsigned integer +#else +typedef unsigned long long ImU64; // 64-bit unsigned integer +#endif + +// Others helpers at bottom of the file: +// class ImVector<> // Lightweight std::vector like class. +// IMGUI_ONCE_UPON_A_FRAME // Execute a block of code once per frame only (convenient for creating UI within deep-nested code that runs multiple times) + +struct ImVec2 +{ + float x, y; + ImVec2() { x = y = 0.0f; } + ImVec2(float _x, float _y) { x = _x; y = _y; } + float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, thus an assert is fine. +#ifdef IM_VEC2_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec2. + IM_VEC2_CLASS_EXTRA +#endif +}; + +struct ImVec4 +{ + float x, y, z, w; + ImVec4() { x = y = z = w = 0.0f; } + ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } +#ifdef IM_VEC4_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec4. + IM_VEC4_CLASS_EXTRA +#endif +}; + +// ImGui end-user API +// In a namespace so that user can add extra functions in a separate file (e.g. Value() helpers for your vector or common types) +namespace ImGui +{ + // Context creation and access, if you want to use multiple context, share context between modules (e.g. DLL). + // All contexts share a same ImFontAtlas by default. If you want different font atlas, you can new() them and overwrite the GetIO().Fonts variable of an ImGui context. + // All those functions are not reliant on the current context. + IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); + IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = Destroy current context + IMGUI_API ImGuiContext* GetCurrentContext(); + IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + + // Main + IMGUI_API ImGuiIO& GetIO(); + IMGUI_API ImGuiStyle& GetStyle(); + IMGUI_API void NewFrame(); // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame(). + IMGUI_API void Render(); // ends the ImGui frame, finalize the draw data. (Obsolete: optionally call io.RenderDrawListsFn if set. Nowadays, prefer calling your render function yourself.) + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. (Obsolete: this used to be passed to your io.RenderDrawListsFn() function.) + IMGUI_API void EndFrame(); // ends the ImGui frame. automatically called by Render(), so most likely don't need to ever call that yourself directly. If you don't need to render you may call EndFrame() but you'll have wasted CPU already. If you don't need to render, better to not create any imgui windows instead! + + // Demo, Debug, Informations + IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create metrics window. display ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc. + IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) + IMGUI_API bool ShowStyleSelector(const char* label); + IMGUI_API void ShowFontSelector(const char* label); + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + IMGUI_API const char* GetVersion(); + + // Styles + IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // New, recommended style + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // Classic imgui style (default) + IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // Best used with borders and a custom, thicker font + + // Window + IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // push window to the stack and start appending to it. see .cpp for details. return false when window is collapsed (so you can early out in your code) but you always need to call End() regardless. 'bool* p_open' creates a widget on the upper-right to close the window (which sets your bool to false). + IMGUI_API void End(); // always call even if Begin() return false (which indicates a collapsed window)! finish appending to current window, pop it off the window stack. + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). + IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // " + IMGUI_API void EndChild(); // always call even if BeginChild() return false (which indicates a collapsed or clipping child window) + IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates + IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() + IMGUI_API float GetContentRegionAvailWidth(); // + IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API float GetWindowContentRegionWidth(); // + IMGUI_API ImDrawList* GetWindowDrawList(); // get rendering command-list if you want to append your own draw primitives + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList api) + IMGUI_API ImVec2 GetWindowSize(); // get current window size + IMGUI_API float GetWindowWidth(); + IMGUI_API float GetWindowHeight(); + IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowAppearing(); + IMGUI_API void SetWindowFontScale(float scale); // per-window font scale. Adjust IO.FontGlobalScale if you want to scale all windows + + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. + IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() + IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() + IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() + IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg. + IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. + IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. + IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). + IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / front-most. prefer using SetNextWindowFocus(). + IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. + IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. + IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state + IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / front-most. use NULL to remove focus. + + IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()] + IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()] + IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X + IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y + IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] + IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] + IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. + IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it) + IMGUI_API ImGuiStorage* GetStateStorage(); + + // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); + IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); + IMGUI_API void PopStyleColor(int count = 1); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); + IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwhise use GetColorU32() to get style color + style alpha. + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier + IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied + IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align width to the right side) + IMGUI_API void PopItemWidth(); + IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position + IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space + IMGUI_API void PopTextWrapPos(); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); + + // Cursor / Layout + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. + IMGUI_API void SameLine(float pos_x = 0.0f, float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally + IMGUI_API void NewLine(); // undo a SameLine() + IMGUI_API void Spacing(); // add vertical spacing + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) + IMGUI_API void EndGroup(); + IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position + IMGUI_API float GetCursorPosX(); // " + IMGUI_API float GetCursorPosY(); // " + IMGUI_API void SetCursorPos(const ImVec2& local_pos); // " + IMGUI_API void SetCursorPosX(float x); // " + IMGUI_API void SetCursorPosY(float y); // " + IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] + IMGUI_API void AlignTextToFramePadding(); // vertically align/lower upcoming text to FramePadding.y so that it will aligns to upcoming widgets (call if you have text on a line before regular widgets) + IMGUI_API float GetTextLineHeight(); // ~ FontSize + IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) + IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 + IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + + // Columns + // You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather lacking. + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API int GetColumnsCount(); + + // ID scopes + // If you are creating widgets in a loop you most likely want to push a unique identifier (e.g. object pointer, loop index) so ImGui can differentiate them. + // You can also use the "##foobar" syntax within widget label to distinguish them from each others. Read "A primer on the use of labels/IDs" in the FAQ for more details. + IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); + IMGUI_API void PushID(const void* ptr_id); + IMGUI_API void PushID(int int_id); + IMGUI_API void PopID(); + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Main + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); + IMGUI_API bool RadioButton(const char* label, bool active); + IMGUI_API bool RadioButton(const char* label, int* v, int v_button); + IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL); + + // Widgets: Combo Box + // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it. + // The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + + // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds) + // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x + // Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound + IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", const char* display_format_max = NULL, float power = 1.0f); + IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); // If v_min >= v_max we have no bound + IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f", const char* display_format_max = NULL); + + // Widgets: Input with Keyboard + IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0); + + // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds) + IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); // adjust display_format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for logarithmic sliders + IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f); + IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be left-clicked to open a picker, and right-clicked to open an option menu.) + // Note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x + IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); + IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0)); // display a colored square/button, hover for details, return true when pressed. + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + + // Widgets: Trees + IMGUI_API bool TreeNode(const char* label); // if returning 'true' the node is open and the tree id is pushed into the id stack. user is responsible for calling TreePop(). + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call Push/Pop yourself for layout purpose + IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePop(); // ~ Unindent()+PopId() + IMGUI_API void TreeAdvanceToLabelPos(); // advance cursor x position by GetTreeNodeToLabelSpacing() + IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode + IMGUI_API void SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). + IMGUI_API bool CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small close button on upper right of the header + + // Widgets: Selectable / Lists + IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height + IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + IMGUI_API bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. make sure to call ListBoxFooter() afterwards. + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " + IMGUI_API void ListBoxFooter(); // terminate the scrolling region + + // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) + IMGUI_API void Value(const char* prefix, bool b); + IMGUI_API void Value(const char* prefix, int v); + IMGUI_API void Value(const char* prefix, unsigned int v); + IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + + // Tooltips + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set text tooltip under mouse-cursor, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of contents). + IMGUI_API void EndTooltip(); + + // Menus + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. + IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). + IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); // only call EndBegin() if BeginMenu() returns true! + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + + // Popups + IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returns true! + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). + IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // modal dialog (regular window with title bar, block interactions behind the modal window, can't close the modal window by clicking outside) + IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. + IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open + IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. + + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. + IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty + IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file + IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard + IMGUI_API void LogFinish(); // stop logging (close file, etc.) + IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + + // Drag and Drop + // [BETA API] Missing Demo code. API may evolve. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0, int mouse_button = 0); // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond = 0);// type is a user defined string of maximum 12 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! + IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! + + // Clipping + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); + IMGUI_API void PopClipRect(); + + // Focus, Activation + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged) + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. Please use instead of "if (IsWindowAppearing()) SetScrollHere()" to signify "default item". + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + + // Utilities + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) + IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? + IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) + IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) + IMGUI_API bool IsAnyItemHovered(); + IMGUI_API bool IsAnyItemActive(); + IMGUI_API bool IsAnyItemFocused(); + IMGUI_API ImVec2 GetItemRectMin(); // get bounding rectangle of last item, in screen space + IMGUI_API ImVec2 GetItemRectMax(); // " + IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space + IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. + IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags = 0); // is current window focused? or its root/child, depending on flags. see flags for options. + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. + IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. + IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. + IMGUI_API float GetTime(); + IMGUI_API int GetFrameCount(); + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text + IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); + IMGUI_API const char* GetStyleColorName(ImGuiCol idx); + IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. + + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) + + IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); + IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); + IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); + IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + + // Inputs + IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] + IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]! + IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate + IMGUI_API bool IsMouseDown(int button); // is mouse button held + IMGUI_API bool IsAnyMouseDown(); // is any mouse button held + IMGUI_API bool IsMouseClicked(int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) + IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime. + IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) + IMGUI_API bool IsMouseDragging(int button = 0, float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings. disregarding of consideration of focus/window ordering/blocked by a popup. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // + IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls + IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse positioning at the time of opening popup we have BeginPopup() into + IMGUI_API ImVec2 GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API void ResetMouseDragDelta(int button = 0); // + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor type); // set desired cursor type + IMGUI_API void CaptureKeyboardFromApp(bool capture = true); // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application handle). e.g. force capture keyboard when your widget is being hovered. + IMGUI_API void CaptureMouseFromApp(bool capture = true); // manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application handle). + + // Clipboard Utilities (also see the LogToClipboard() function to capture or output text data to the clipboard) + IMGUI_API const char* GetClipboardText(); + IMGUI_API void SetClipboardText(const char* text); + + // Memory Utilities + // All those functions are not reliant on the current context. + // If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again. + IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data = NULL); + IMGUI_API void* MemAlloc(size_t size); + IMGUI_API void MemFree(void* ptr); + +} // namespace ImGui + +// Flags for ImGui::Begin() +enum ImGuiWindowFlags_ +{ + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + //ImGuiWindowFlags_ShowBorders = 1 << 7, // Show borders around windows and items (OBSOLETE! Use e.g. style.FrameBorderSize=1.0f to enable borders). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // (WIP) Enable resize from any corners and borders. Your back-end needs to honor the different values of io.MouseCursor set by imgui. + ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + + // [Internal] + ImGuiWindowFlags_NavFlattened = 1 << 23, // (WIP) Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!) + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() +}; + +// Flags for ImGui::InputText() +enum ImGuiInputTextFlags_ +{ + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to when the value was modified) + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ +{ + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Full colored frame (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_NavCloseFromChild = 1 << 13, // (WIP) Nav: left direction may close this TreeNode() when focusing on any child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoAutoOpenOnLog + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap +#endif +}; + +// Flags for ImGui::Selectable() +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ +{ + ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_AnyWindow = 1 << 2, // IsWindowFocused(): Return true if any window is focused + ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows +}; + +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_Default = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 3, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 4, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 5, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 6, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return true, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + +// Standard Drag and Drop payload types. You can define you own payload types using 12-characters long strings. Types starting with '_' are defined by Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3] // Standard type for colors, without alpha. User code may use this type. +#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4] // Standard type for colors. User code may use this type. + +// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +enum ImGuiKey_ +{ + ImGuiKey_Tab, + ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, + ImGuiKey_UpArrow, + ImGuiKey_DownArrow, + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, + ImGuiKey_End, + ImGuiKey_Insert, + ImGuiKey_Delete, + ImGuiKey_Backspace, + ImGuiKey_Space, + ImGuiKey_Enter, + ImGuiKey_Escape, + ImGuiKey_A, // for text edit CTRL+A: select all + ImGuiKey_C, // for text edit CTRL+C: copy + ImGuiKey_V, // for text edit CTRL+V: paste + ImGuiKey_X, // for text edit CTRL+X: cut + ImGuiKey_Y, // for text edit CTRL+Y: redo + ImGuiKey_Z, // for text edit CTRL+Z: undo + ImGuiKey_COUNT +}; + +// [BETA] Gamepad/Keyboard directional navigation +// Keyboard: Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays. +// Gamepad: Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). +// Read instructions in imgui.cpp for more details. +enum ImGuiNavInput_ +{ + // Gamepad Mapping + ImGuiNavInput_Activate, // activate / open / toggle / tweak value // e.g. Circle (PS4), A (Xbox), B (Switch), Space (Keyboard) + ImGuiNavInput_Cancel, // cancel / close / exit // e.g. Cross (PS4), B (Xbox), A (Switch), Escape (Keyboard) + ImGuiNavInput_Input, // text input / on-screen keyboard // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard) + ImGuiNavInput_Menu, // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard) + ImGuiNavInput_DpadLeft, // move / tweak / resize window (w/ PadMenu) // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard) + ImGuiNavInput_DpadRight, // + ImGuiNavInput_DpadUp, // + ImGuiNavInput_DpadDown, // + ImGuiNavInput_LStickLeft, // scroll / move window (w/ PadMenu) // e.g. Left Analog Stick Left/Right/Up/Down + ImGuiNavInput_LStickRight, // + ImGuiNavInput_LStickUp, // + ImGuiNavInput_LStickDown, // + ImGuiNavInput_FocusPrev, // next window (w/ PadMenu) // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_FocusNext, // prev window (w/ PadMenu) // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + ImGuiNavInput_TweakSlow, // slower tweaks // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_TweakFast, // faster tweaks // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + + // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them. + // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) may be directly reading from io.KeyDown[] instead of io.NavInputs[]. + ImGuiNavInput_KeyMenu_, // toggle menu // = io.KeyAlt + ImGuiNavInput_KeyLeft_, // move left // = Arrow keys + ImGuiNavInput_KeyRight_, // move right + ImGuiNavInput_KeyUp_, // move up + ImGuiNavInput_KeyDown_, // move down + ImGuiNavInput_COUNT, + ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_ +}; + +// [BETA] Gamepad/Keyboard directional navigation flags, stored in io.NavFlags +enum ImGuiNavFlags_ +{ + ImGuiNavFlags_EnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. NewFrame() will automatically fill io.NavInputs[] based on io.KeyDown[]. + ImGuiNavFlags_EnableGamepad = 1 << 1, // Master gamepad navigation enable flag. This is mostly to instruct your imgui back-end to fill io.NavInputs[]. + ImGuiNavFlags_MoveMouse = 1 << 2, // Request navigation to allow moving the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantMoveMouse=true. If enabled you MUST honor io.WantMoveMouse requests in your binding, otherwise ImGui will react as if the mouse is jumping around back and forth. + ImGuiNavFlags_NoCaptureKeyboard = 1 << 3 // Do not set the io.WantCaptureKeyboard flag with io.NavActive is set. +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ +{ + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_CloseButton, + ImGuiCol_CloseButtonHovered, + ImGuiCol_CloseButtonActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TextSelectedBg, + ImGuiCol_ModalWindowDarkening, // darken entire screen when a modal window is active + ImGuiCol_DragDropTarget, + ImGuiCol_NavHighlight, // gamepad/keyboard: current highlighted item + ImGuiCol_NavWindowingHighlight, // gamepad/keyboard: when holding NavMenu to focus/move/resize windows + ImGuiCol_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //, ImGuiCol_ComboBg = ImGuiCol_PopupBg // ComboBg has been merged with PopupBg, so a redirect isn't accurate. + , ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, ImGuiCol_Column = ImGuiCol_Separator, ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive +#endif +}; + +// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. +// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During initialization, feel free to just poke into ImGuiStyle directly. +// NB: if changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. +enum ImGuiStyleVar_ +{ + // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_Count_ + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding +#endif +}; + +// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ +{ + ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on colored square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable colored square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview colored square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small colored square preview instead. + // User Options (right-click on widget to change some of them). You can set application defaults using SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the user choose and/or call SetColorEditOptions() during startup. + ImGuiColorEditFlags_AlphaBar = 1 << 9, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaPreview = 1 << 10, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 11, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_HDR = 1 << 12, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). + ImGuiColorEditFlags_RGB = 1 << 13, // [Inputs] // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any combination using RGB/HSV/HEX. + ImGuiColorEditFlags_HSV = 1 << 14, // [Inputs] // " + ImGuiColorEditFlags_HEX = 1 << 15, // [Inputs] // " + ImGuiColorEditFlags_Uint8 = 1 << 16, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 17, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 18, // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value. + ImGuiColorEditFlags_PickerHueWheel = 1 << 19, // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value. + // Internals/Masks + ImGuiColorEditFlags__InputsMask = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX, + ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_Float, + ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel|ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_PickerHueBar // Change application default using SetColorEditOptions() +}; + +// Enumeration for GetMouseCursor() +enum ImGuiMouseCursor_ +{ + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_ResizeAll, // Unused + ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Count_ +}; + +// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +// All those functions treat 0 as a shortcut to ImGuiCond_Always. From the point of view of the user use this as an enum (don't combine multiple values into flags). +enum ImGuiCond_ +{ + ImGuiCond_Always = 1 << 0, // Set the variable + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the window has no saved data (if doesn't exist in the .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the window is appearing after being hidden/inactive (or the first time) + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiSetCond_Always = ImGuiCond_Always, ImGuiSetCond_Once = ImGuiCond_Once, ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing = ImGuiCond_Appearing +#endif +}; + +// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). +// During the frame, prefer using ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. +struct ImGuiStyle +{ + float Alpha; // Global alpha applies to everything in ImGui. + ImVec2 WindowPadding; // Padding within a window. + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float PopupRounding; // Radius of popup window corners rounding. + float PopupBorderSize; // Thickness of border around popup windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. + float ScrollbarRounding; // Radius of grab corners for scrollbar. + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. + ImVec2 DisplayWindowPadding; // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. + bool AntiAliasedFill; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + ImVec4 Colors[ImGuiCol_COUNT]; + + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); +}; + +// This is where your app communicate with ImGui. Access via ImGui::GetIO(). +// Read 'Programmer guide' section in .cpp file for general usage. +struct ImGuiIO +{ + //------------------------------------------------------------------ + // Settings (fill once) // Default value: + //------------------------------------------------------------------ + + ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + ImGuiNavFlags NavFlags; // = 0x00 // See ImGuiNavFlags_. Gamepad/keyboard navigation options. + float IniSavingRate; // = 5.0f // Maximum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. + const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + void* UserData; // = NULL // Store your own data for retrieval by callbacks. + + ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui. + ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area. + ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize + + // Advanced/subtle behaviors + bool OptMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl + bool OptCursorBlink; // = true // Enable blinking cursor, for users who consider it annoying. + + //------------------------------------------------------------------ + // Settings (User Functions) + //------------------------------------------------------------------ + + // Optional: access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; + + // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) + // (default to use native imm32 api on Windows) + void (*ImeSetInputScreenPosFn)(int x, int y); + void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // [OBSOLETE] Rendering function, will be automatically called in Render(). Please call your rendering function yourself now! You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). + // See example applications if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawData* data); +#endif + + //------------------------------------------------------------------ + // Input - Fill before calling NewFrame() + //------------------------------------------------------------------ + + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: left, right, middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel: 1 unit scrolls about 5 lines text. + float MouseWheelH; // Mouse wheel (Horizontal). Most users don't have a mouse with an horizontal wheel, may not be filled by all back-ends. + bool MouseDrawCursor; // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). + bool KeyCtrl; // Keyboard modifier pressed: Control + bool KeyShift; // Keyboard modifier pressed: Shift + bool KeyAlt; // Keyboard modifier pressed: Alt + bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows + bool KeysDown[512]; // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). + ImWchar InputCharacters[16+1]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. + float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs (keyboard keys will be auto-mapped and be written here by ImGui::NewFrame) + + // Functions + IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[] + IMGUI_API void AddInputCharactersUTF8(const char* utf8_chars); // Add new characters into InputCharacters[] from an UTF-8 string + inline void ClearInputCharacters() { InputCharacters[0] = 0; } // Clear the text input buffer manually + + //------------------------------------------------------------------ + // Output - Retrieve after calling NewFrame() + //------------------------------------------------------------------ + + bool WantCaptureMouse; // When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. This is set by ImGui when it wants to use your mouse (e.g. unclicked mouse is hovering a window, or a widget is active). + bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. This is set by ImGui when it wants to use your keyboard inputs. + bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). + bool WantMoveMouse; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when ImGuiNavFlags_MoveMouse flag is enabled in io.NavFlags. + bool NavActive; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. + bool NavVisible; // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsActiveWindows; // Number of visible root windows (exclude child windows) + ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + + //------------------------------------------------------------------ + // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! + //------------------------------------------------------------------ + + ImVec2 MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) + ImVec2 MouseClickedPos[5]; // Position at time of clicking + float MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down + float NavInputsDownDuration[ImGuiNavInput_COUNT]; + float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + // OBSOLETED in 1.60 (from Dec 2017) + static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } + static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = 0.f) { (void)on_edge; (void)outward; IM_ASSERT(0); return pos; } + // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + static inline void ShowTestWindow() { return ShowDemoWindow(); } + static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } + static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } + static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } + static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } + // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead. + static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } + static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } + static inline void SetNextWindowPosCenter(ImGuiCond c=0) { ImGuiIO& io = GetIO(); SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); } + // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } + static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead. + static inline bool IsMouseHoveringAnyWindow() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } + // OBSOLETED IN 1.49 (between Apr 2016 and May 2016) + static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } +} +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +// Lightweight std::vector<> like class to avoid dragging dependencies (also: windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). +// Our implementation does NOT call C++ constructors/destructors. This is intentional and we do not require it. Do not use this class as a straight std::vector replacement in your code! +template +class ImVector +{ +public: + int Size; + int Capacity; + T* Data; + + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + inline ImVector() { Size = Capacity = 0; Data = NULL; } + inline ~ImVector() { if (Data) ImGui::MemFree(Data); } + + inline bool empty() const { return Size == 0; } + inline int size() const { return Size; } + inline int capacity() const { return Capacity; } + + inline value_type& operator[](int i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& operator[](int i) const { IM_ASSERT(i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } + inline iterator begin() { return Data; } + inline const_iterator begin() const { return Data; } + inline iterator end() { return Data + Size; } + inline const_iterator end() const { return Data + Size; } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > sz ? new_capacity : sz; } + + inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size, const T& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) Data[n] = v; Size = new_size; } + inline void reserve(int new_capacity) + { + if (new_capacity <= Capacity) + return; + T* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(T)); + if (Data) + memcpy(new_data, Data, (size_t)Size * sizeof(T)); + ImGui::MemFree(Data); + Data = new_data; + Capacity = new_capacity; + } + + // NB: &v cannot be pointing inside the ImVector Data itself! e.g. v.push_back(v[10]) is forbidden. + inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); Data[Size++] = v; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const value_type& v) { if (Size == 0) push_back(v); else insert(Data, v); } + + inline iterator erase(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } + inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } + inline bool contains(const value_type& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } +}; + +// Helper: execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Usage: +// static ImGuiOnceUponAFrame oaf; +// if (oaf) +// ImGui::Text("This will be called only once per frame"); +struct ImGuiOnceUponAFrame +{ + ImGuiOnceUponAFrame() { RefFrame = -1; } + mutable int RefFrame; + operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } +}; + +// Helper macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it within e.g. an if() statement without curly braces. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Will obsolete +#define IMGUI_ONCE_UPON_A_FRAME static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf) +#endif + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextFilter +{ + struct TextRange + { + const char* b; + const char* e; + + TextRange() { b = e = NULL; } + TextRange(const char* _b, const char* _e) { b = _b; e = _e; } + const char* begin() const { return b; } + const char* end() const { return e; } + bool empty() const { return b == e; } + char front() const { return *b; } + static bool is_blank(char c) { return c == ' ' || c == '\t'; } + void trim_blanks() { while (b < e && is_blank(*b)) b++; while (e > b && is_blank(*(e-1))) e--; } + IMGUI_API void split(char separator, ImVector& out); + }; + + char InputBuf[256]; + ImVector Filters; + int CountGrep; + + IMGUI_API ImGuiTextFilter(const char* default_filter = ""); + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; + IMGUI_API void Build(); + void Clear() { InputBuf[0] = 0; Build(); } + bool IsActive() const { return !Filters.empty(); } +}; + +// Helper: Text buffer for logging/accumulating text +struct ImGuiTextBuffer +{ + ImVector Buf; + + ImGuiTextBuffer() { Buf.push_back(0); } + inline char operator[](int i) { return Buf.Data[i]; } + const char* begin() const { return &Buf.front(); } + const char* end() const { return &Buf.back(); } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const { return Buf.Size - 1; } + bool empty() { return Buf.Size <= 1; } + void clear() { Buf.clear(); Buf.push_back(0); } + void reserve(int capacity) { Buf.reserve(capacity); } + const char* c_str() const { return Buf.Data; } + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); +}; + +// Helper: Simple Key->value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1), store color edit options. +// This is optimized for efficient reading (dichotomy into a contiguous buffer), rare writing (typically tied to user interactions) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage +{ + struct Pair + { + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + Pair(ImGuiID _key, int _val_i) { key = _key; val_i = _val_i; } + Pair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; } + Pair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; } + }; + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ImGuiID key, float val); + IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +// Shared state of InputText(), passed to callback when a ImGuiInputTextFlags_Callback* flag is used and the corresponding callback is triggered. +struct ImGuiTextEditCallbackData +{ + ImGuiInputTextFlags EventFlag; // One of ImGuiInputTextFlags_Callback* // Read-only + ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only + void* UserData; // What user passed to InputText() // Read-only + bool ReadOnly; // Read-only mode // Read-only + + // CharFilter event: + ImWchar EventChar; // Character input // Read-write (replace character or set to zero) + + // Completion,History,Always events: + // If you modify the buffer contents make sure you update 'BufTextLen' and set 'BufDirty' to true. + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only + char* Buf; // Current text buffer // Read-write (pointed data only, can't replace the actual pointer) + int BufTextLen; // Current text length in bytes // Read-write + int BufSize; // Maximum text length in bytes // Read-only + bool BufDirty; // Set if you modify Buf/BufTextLen!! // Write + int CursorPos; // // Read-write + int SelectionStart; // // Read-write (== to SelectionEnd when no selection) + int SelectionEnd; // // Read-write + + // NB: Helper functions for text manipulation. Calling those function loses selection. + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); + bool HasSelection() const { return SelectionStart != SelectionEnd; } +}; + +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. +struct ImGuiSizeCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + +// Data payload for Drag and Drop operations +struct ImGuiPayload +{ + // Members + const void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + int DataFrameCount; // Data timestamp + char DataType[12 + 1]; // Data type tag (short user-supplied string, 12 characters max) + bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) + bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsPreview() const { return Preview; } + bool IsDelivery() const { return Delivery; } +}; + +// Helpers macros to generate 32-bits encoded colors +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#define IM_COL32(R,G,B,A) (((ImU32)(A)<>IM_COL32_R_SHIFT)&0xFF) * sc; Value.y = (float)((rgba>>IM_COL32_G_SHIFT)&0xFF) * sc; Value.z = (float)((rgba>>IM_COL32_B_SHIFT)&0xFF) * sc; Value.w = (float)((rgba>>IM_COL32_A_SHIFT)&0xFF) * sc; } + ImColor(float r, float g, float b, float a = 1.0f) { Value.x = r; Value.y = g; Value.z = b; Value.w = a; } + ImColor(const ImVec4& col) { Value = col; } + inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } + inline operator ImVec4() const { return Value; } + + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. + inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } + static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r,g,b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r,g,b,a); } +}; + +// Helper: Manually clip large list of items. +// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse clipping based on visibility to save yourself from processing those items at all. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// ImGui already clip items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this cost and your own data fetching/submission cost null. +// Usage: +// ImGuiListClipper clipper(1000); // we have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height (step skipped if we passed a known height as second arg to constructor). +// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. +// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call Step(). Does nothing and switch to Step 3.) +// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. +struct ImGuiListClipper +{ + float StartPosY; + float ItemsHeight; + int ItemsCount, StepNo, DisplayStart, DisplayEnd; + + // items_count: Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step). + // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). + // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step(). + ImGuiListClipper(int items_count = -1, float items_height = -1.0f) { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want). + ~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. + + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. +}; + +//----------------------------------------------------------------------------- +// Draw List +// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. +//----------------------------------------------------------------------------- + +// Draw callbacks for advanced uses. +// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that) +// Draw callback may be useful for example, A) Change your GPU render state, B) render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else RenderTriangles()' +typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); + +// Typically, 1 command = 1 GPU draw call (unless command is a callback) +struct ImDrawCmd +{ + unsigned int ElemCount; // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. + ImVec4 ClipRect; // Clipping rectangle (x1, y1, x2, y2) + ImTextureID TextureId; // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImDrawCallback UserCallback; // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void* UserCallbackData; // The draw callback code can access this. + + ImDrawCmd() { ElemCount = 0; ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; TextureId = NULL; UserCallback = NULL; UserCallbackData = NULL; } +}; + +// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h) +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; +#endif + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert +{ + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. +// The type has to be described within the macro (you can either declare the struct or use a typedef) +// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. +IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; +#endif + +// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together. +// You can also use them to simulate drawing layers and submit primitives in a different order than how they will be rendered. +struct ImDrawChannel +{ + ImVector CmdBuffer; + ImVector IdxBuffer; +}; + +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 + ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 + ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 + ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA + ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience +}; + +enum ImDrawListFlags_ +{ + ImDrawListFlags_AntiAliasedLines = 1 << 0, + ImDrawListFlags_AntiAliasedFill = 1 << 1 +}; + +// Draw command list +// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. +// Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), however you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) +// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. +struct ImDrawList +{ + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those + ImVector VtxBuffer; // Vertex buffer. + + // [Internal, used while building lists] + ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + const char* _OwnerName; // Pointer to owner window's name for debugging + unsigned int _VtxCurrentIdx; // [Internal] == VtxBuffer.Size + ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + int _ChannelsCurrent; // [Internal] current channel number (0) + int _ChannelsCount; // [Internal] number of active channels (1+) + ImVector _Channels; // [Internal] draw channels for columns API (not resized down so _ChannelsCount may be smaller than _Channels.Size) + + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) + ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; _OwnerName = NULL; Clear(); } + ~ImDrawList() { ClearFreeMemory(); } + IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) + IMGUI_API void PushClipRectFullScreen(); + IMGUI_API void PopClipRect(); + IMGUI_API void PushTextureID(const ImTextureID& texture_id); + IMGUI_API void PopTextureID(); + inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } + inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + + // Primitives + IMGUI_API void AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f); // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round + IMGUI_API void AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); // a: upper-left, b: lower-right + IMGUI_API void AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); + IMGUI_API void AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col); + IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); + IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,0), const ImVec2& uv_c = ImVec2(1,1), const ImVec2& uv_d = ImVec2(0,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, const int num_points, ImU32 col); + IMGUI_API void AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 0); + + // Stateful path API, add points then finish with PathFill() or PathStroke() + inline void PathClear() { _Path.resize(0); } + inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } + inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path[_Path.Size-1], &pos, 8) != 0) _Path.push_back(pos); } + inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); PathClear(); } + inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); PathClear(); } + IMGUI_API void PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10); + IMGUI_API void PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + IMGUI_API void PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0); + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); + + // Channels + // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives before background primitives) + // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, prefer to append into separate channels then merge at the end) + IMGUI_API void ChannelsSplit(int channels_count); + IMGUI_API void ChannelsMerge(); + IMGUI_API void ChannelsSetCurrent(int channel_index); + + // Advanced + IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible + + // Internal helpers + // NB: all primitives needs to be reserved via PrimReserve() beforehand! + IMGUI_API void Clear(); + IMGUI_API void ClearFreeMemory(); + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); + IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); + inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col){ _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } + inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } + inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } + IMGUI_API void UpdateClipRect(); + IMGUI_API void UpdateTextureID(); +}; + +// All draw data to render an ImGui frame +struct ImDrawData +{ + bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + ImDrawList** CmdLists; + int CmdListsCount; + int TotalVtxCount; // For convenience, sum of all cmd_lists vtx_buffer.Size + int TotalIdxCount; // For convenience, sum of all cmd_lists idx_buffer.Size + + // Functions + ImDrawData() { Clear(); } + void Clear() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; } // Draw lists are owned by the ImGuiContext and only pointed to here. + IMGUI_API void DeIndexAllBuffers(); // For backward compatibility or convenience: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! + IMGUI_API void ScaleClipRects(const ImVec2& sc); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +}; + +struct ImFontConfig +{ + void* FontData; // // TTF/OTF data + int FontDataSize; // // TTF/OTF data size + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer. + int OversampleH, OversampleV; // 3, 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. + + // [Internal] + char Name[32]; // Name (strictly to ease debugging) + ImFont* DstFont; + + IMGUI_API ImFontConfig(); +}; + +struct ImFontGlyph +{ + ImWchar Codepoint; // 0x0000..0xFFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +enum ImFontAtlasFlags_ +{ + ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two + ImFontAtlasFlags_NoMouseCursors = 1 << 1 // Don't build software mouse cursors into the atlas +}; + +// Load and rasterize multiple TTF/OTF fonts into a same texture. +// Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. +// We also add custom graphic data into the texture that serves for ImGui. +// 1. (Optional) Call AddFont*** functions. If you don't call any, the default font will be loaded for you. +// 2. Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// 3. Upload the pixels data into a texture within your graphics system. +// 4. Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture. This value will be passed back to you during rendering to identify the texture. +// IMPORTANT: If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the ImFont is build (when calling GetTextData*** or Build()). We only copy the pointer, not the data. +struct ImFontAtlas +{ + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after Build(). Set font_cfg->FontDataOwnedByAtlas to false to keep ownership. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void ClearTexData(); // Clear the CPU-side texture data. Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearInputData(); // Clear the input TTF data (inc sizes, glyph ranges) + IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates) + IMGUI_API void Clear(); // Clear all + + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // RGBA32 format is provided for convenience and compatibility, but note that unless you use CustomRect to draw color data, the RGB pixels emitted from Fonts will all be white (~75% of waste). + // Pitch = Width * BytesPerPixels + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Default + Japanese + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters + IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters + + // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges(). + struct GlyphRangesBuilder + { + ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + GlyphRangesBuilder() { UsedChars.resize(0x10000 / 8); memset(UsedChars.Data, 0, 0x10000 / 8); } + bool GetBit(int n) { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; } + void SetBit(int n) { UsedChars[n >> 3] |= 1 << (n & 7); } // Set bit 'c' in the array + void AddChar(ImWchar c) { SetBit(c); } // Add character + IMGUI_API void AddText(const char* text, const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) + IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault) to force add all of ASCII/Latin+Ext + IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges + }; + + //------------------------------------------- + // Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels. + // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. + struct CustomRect + { + unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data. + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset + ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font + CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } + }; + + IMGUI_API int AddCustomRectRegular(unsigned int id, int width, int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. + const CustomRect* GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; } + + // Internals + IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + + //------------------------------------------- + // Members + //------------------------------------------- + + ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; // Texture width calculated during Build(). + int TexHeight; // Texture height calculated during Build(). + ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. + ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. + ImVector ConfigData; // Internal data + int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList +}; + +// Font runtime data and rendering +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +struct ImFont +{ + // Members: Hot ~62/78 bytes + float FontSize; // // Height of characters, set during loading (don't change after loading) + float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() + ImVec2 DisplayOffset; // = (0.f,1.f) // Offset font rendering by xx pixels + ImVector Glyphs; // // All glyphs. + ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). + ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. + const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + float FallbackAdvanceX; // == FallbackGlyph->AdvanceX + ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() + + // Members: Cold ~18/26 bytes + short ConfigDataCount; // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. + ImFontConfig* ConfigData; // // Pointer within ContainerAtlas->ConfigData + ImFontAtlas* ContainerAtlas; // // What we has been loaded into + float Ascent, Descent; // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + int MetricsTotalSurface;// // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API void ClearOutputData(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; + IMGUI_API void SetFallbackChar(ImWchar c); + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + + // [Internal] + IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); + IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ +#endif +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif diff --git a/apps/bench_shared/imgui/imgui_demo.cpp b/apps/bench_shared/imgui/imgui_demo.cpp new file mode 100644 index 00000000..2a197278 --- /dev/null +++ b/apps/bench_shared/imgui/imgui_demo.cpp @@ -0,0 +1,3150 @@ +// dear imgui, v1.60 WIP +// (demo code) + +// Message to the person tempted to delete this file when integrating ImGui into their code base: +// Don't do it! Do NOT remove this file from your project! It is useful reference code that you and other users will want to refer to. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// During development, you can call ImGui::ShowDemoWindow() in your code to learn about various features of ImGui. Have it wired in a debug menu! +// Removing this file from your project is hindering access to documentation for everyone in your team, likely leading you to poorer usage of the library. +// Note that you can #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h for the same effect. +// If you want to link core ImGui in your final builds but not those demo windows, #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h and those functions will be empty. +// In other situation, when you have ImGui available you probably want this to be available for reference and execution. +// Thank you, +// -Your beloved friend, imgui_demo.cpp (that you won't delete) + +// Message to beginner C/C++ programmers. About the meaning of 'static': in this demo code, we frequently we use 'static' variables inside functions. +// We do this as a way to gather code and data in the same place, just to make the demo code faster to read, faster to write, and use less code. +// A static variable persist across calls, so it is essentially like a global variable but declared inside the scope of the function. +// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant or used in threads. +// This might be a pattern you occasionally want to use in your code, but most of the real data you would be editing is likely to be stored outside your function. + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include // toupper, isprint +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored "-Wformat-security" // warning : warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning : format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#if (__GNUC__ >= 6) +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#endif +#endif + +// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) + +//----------------------------------------------------------------------------- +// DEMO CODE +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Obsolete name since 1.53, TEST->DEMO +#define IMGUI_DISABLE_DEMO_WINDOWS +#endif + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +static void ShowExampleAppConsole(bool* p_open); +static void ShowExampleAppLog(bool* p_open); +static void ShowExampleAppLayout(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppLongText(bool* p_open); +static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); +static void ShowExampleAppFixedOverlay(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); +static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleAppMainMenuBar(); +static void ShowExampleMenuFile(); + +static void ShowHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +void ImGui::ShowUserGuide() +{ + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents)."); + ImGui::BulletText("Click and drag on any empty space to move window."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + if (ImGui::GetIO().FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("Mouse Wheel to scroll."); + ImGui::BulletText("While editing text:\n"); + ImGui::Indent(); + ImGui::BulletText("Hold SHIFT or use mouse to select text."); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); +} + +// Demonstrate most ImGui features (big function!) +void ImGui::ShowDemoWindow(bool* p_open) +{ + // Examples apps + static bool show_app_main_menu_bar = false; + static bool show_app_console = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_long_text = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_fixed_overlay = false; + static bool show_app_window_titles = false; + static bool show_app_custom_rendering = false; + static bool show_app_style_editor = false; + + static bool show_app_metrics = false; + static bool show_app_about = false; + + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } + if (show_app_about) + { + ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Text("Dear ImGui, %s", ImGui::GetVersion()); + ImGui::Separator(); + ImGui::Text("By Omar Cornut and all dear imgui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); + ImGui::End(); + } + + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + static bool no_nav = false; + + // Demonstrate the various window flags. Typically you would just use the default. + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + + ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); // 2/3 of the space for widget and 1/3 for labels + ImGui::PushItemWidth(-140); // Right align, keep 140 pixels for labels + + ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + ImGui::MenuItem("Metrics", NULL, &show_app_metrics); + ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + if (ImGui::CollapsingHeader("Help")) + { + ImGui::TextWrapped("This window is being created by the ShowDemoWindow() function. Please refer to the code in imgui_demo.cpp for reference.\n\n"); + ImGui::Text("USER GUIDE:"); + ImGui::ShowUserGuide(); + } + + if (ImGui::CollapsingHeader("Window options")) + { + ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); + ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); + ImGui::Checkbox("No menu", &no_menu); + ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); + ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); + ImGui::Checkbox("No collapse", &no_collapse); + ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); + ImGui::Checkbox("No nav", &no_nav); + + if (ImGui::TreeNode("Style")) + { + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Capture/Logging")) + { + ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. You can also call ImGui::LogText() to output directly to the log without a visual output."); + ImGui::LogButtons(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Widgets")) + { + if (ImGui::TreeNode("Basic")) + { + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + ImGui::Text("Hover over me"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::Text("- or me"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + // Testing ImGuiOnceUponAFrame helper. + //static ImGuiOnceUponAFrame once; + //for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + ImGui::Separator(); + + ImGui::LabelText("label", "Value"); + + { + // Simplified one-liner Combo() API, using values packed in a single constant string + static int current_item_1 = 1; + ImGui::Combo("combo", ¤t_item_1, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + //ImGui::Combo("combo w/ array of char*", ¤t_item_2_idx, items, IM_ARRAYSIZE(items)); // Combo using proper array. You can also pass a callback to retrieve array value, no need to create/copy an array just for that. + + // General BeginCombo() API, you have full control over your selection data and display type + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO", "PPPP", "QQQQQQQQQQ", "RRR", "SSSS" }; + static const char* current_item_2 = NULL; + if (ImGui::BeginCombo("combo 2", current_item_2)) // The second parameter is the label previewed before opening the combo. + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + bool is_selected = (current_item_2 == items[n]); // You can store your selection however you want, outside or inside your objects + if (ImGui::Selectable(items[n], is_selected)) + current_item_2 = items[n]; + if (is_selected) + ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch) + } + ImGui::EndCombo(); + } + } + + { + static char str0[128] = "Hello, world!"; + static int i0=123; + static float f0=0.001f; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); ShowHelpMarker("Hold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n"); + + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); + + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + ImGui::InputFloat3("input float3", vec4a); + } + + { + static int i1=50, i2=42; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%.0f%%"); + + static float f1=1.00f, f2=0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + } + + { + static int i1=0; + ImGui::SliderInt("slider int", &i1, -1, 3); + ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value."); + + static float f1=0.123f, f2=0.0f; + ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); + ImGui::SliderFloat("slider log float", &f2, -10.0f, 10.0f, "%.4f", 3.0f); + static float angle = 0.0f; + ImGui::SliderAngle("slider angle", &angle); + } + + static float col1[3] = { 1.0f,0.0f,0.2f }; + static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n"); + + ImGui::ColorEdit4("color 2", col2); + + const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int listbox_item_current = 1; + ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + + //static int listbox_item_current2 = 2; + //ImGui::PushItemWidth(-1); + //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + //ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Trees")) + { + if (ImGui::TreeNode("Basic trees")) + { + for (int i = 0; i < 5; i++) + if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("button")) { }; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced, with Selectable nodes")) + { + ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static bool align_label_with_current_x_position = false; + ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position); + ImGui::Text("Hello!"); + if (align_label_with_current_x_position) + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + + static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit. + int node_clicked = -1; // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc. + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents. + for (int i = 0; i < 6; i++) + { + // Disable the default open on single-click behavior and pass in Selected flag according to our selection state. + ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0); + if (i < 3) + { + // Node + bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (node_open) + { + ImGui::Text("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } + else + { + // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + } + } + if (node_clicked != -1) + { + // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame. + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + ImGui::PopStyleVar(); + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collapsing Headers")) + { + static bool closable_group = true; + ImGui::Checkbox("Enable extra group", &closable_group); + if (ImGui::CollapsingHeader("Header")) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Bullets")) + { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text")) + { + if (ImGui::TreeNode("Colored Text")) + { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Word Wrapping")) + { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); + ImGui::Spacing(); + + static float wrap_width = 200.0f; + ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + + ImGui::Text("Test paragraph 1:"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::Text("Test paragraph 2:"); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("UTF-8 Text")) + { + // UTF-8 test with Japanese characters + // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature') + // - HOWEVER, FOR THIS DEMO FILE, BECAUSE WE WANT TO SUPPORT COMPILER, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE. + // Instead we are encoding a few string with hexadecimal constants. Don't do this in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // "nihongo" + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Images")) + { + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); + ImGuiIO& io = ImGui::GetIO(); + + // Here we are grabbing the font texture because that's the only one we have access to inside the demo code. + // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure. + // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID. + // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.) + // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc. + // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this. + // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). + ImTextureID my_tex_id = io.Fonts->TexID; + float my_tex_w = (float)io.Fonts->TexWidth; + float my_tex_h = (float)io.Fonts->TexHeight; + + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float focus_sz = 32.0f; + float focus_x = io.MousePos.x - pos.x - focus_sz * 0.5f; if (focus_x < 0.0f) focus_x = 0.0f; else if (focus_x > my_tex_w - focus_sz) focus_x = my_tex_w - focus_sz; + float focus_y = io.MousePos.y - pos.y - focus_sz * 0.5f; if (focus_y < 0.0f) focus_y = 0.0f; else if (focus_y > my_tex_h - focus_sz) focus_y = my_tex_h - focus_sz; + ImGui::Text("Min: (%.2f, %.2f)", focus_x, focus_y); + ImGui::Text("Max: (%.2f, %.2f)", focus_x + focus_sz, focus_y + focus_sz); + ImVec2 uv0 = ImVec2((focus_x) / my_tex_w, (focus_y) / my_tex_h); + ImVec2 uv1 = ImVec2((focus_x + focus_sz) / my_tex_w, (focus_y + focus_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(128,128), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::EndTooltip(); + } + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); + int frame_padding = -1 + i; // -1 = uses default padding + if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255))) + pressed_count += 1; + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Selectables")) + { + // Selectable() has 2 overloads: + // - The one taking "bool selected" as a read-only selection information. When Selectable() has been clicked is returns true and you can alter selection state accordingly. + // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) + // The earlier is more flexible, as in real application your selection may be stored in a different manner (in flags within objects, as an external list, etc). + if (ImGui::TreeNode("Basic")) + { + static bool selection[5] = { false, true, false, false, false }; + ImGui::Selectable("1. I am selectable", &selection[0]); + ImGui::Selectable("2. I am selectable", &selection[1]); + ImGui::Text("3. I am not selectable"); + ImGui::Selectable("4. I am selectable", &selection[3]); + if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selection[4] = !selection[4]; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Single Selection")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Multiple Selection")) + { + ShowHelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; + } + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rendering more text into the same line")) + { + // Using the Selectable() override that takes "bool* p_selected" parameter and toggle your booleans automatically. + static bool selected[3] = { false, false, false }; + ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); + ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("In columns")) + { + ImGui::Columns(3, NULL, false); + static bool selected[16] = { 0 }; + for (int i = 0; i < 16; i++) + { + char label[32]; sprintf(label, "Item %d", i); + if (ImGui::Selectable(label, &selected[i])) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Grid")) + { + static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true }; + for (int i = 0; i < 16; i++) + { + ImGui::PushID(i); + if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50))) + { + int x = i % 4, y = i / 4; + if (x > 0) selected[i - 1] ^= 1; + if (x < 3) selected[i + 1] ^= 1; + if (y > 0) selected[i - 4] ^= 1; + if (y < 3) selected[i + 4] ^= 1; + } + if ((i % 4) < 3) ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Filtered Text Input")) + { + static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); + static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); + static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); + static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); + struct TextFilters { static int FilterImGuiLetters(ImGuiTextEditCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } }; + static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); + + ImGui::Text("Password input"); + static char bufpass[64] = "password123"; + ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank); + ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-line Text Input")) + { + static bool read_only = false; + static char text[1024*16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\n\n" + "label:\n" + "\tlock cmpxchg8b eax\n"; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Read-only", &read_only); + ImGui::PopStyleVar(); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Plots widgets")) + { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + + // Create a dummy array of contiguous float values to plot + // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter. + static float values[90] = { 0 }; + static int values_offset = 0; + static float refresh_time = 0.0f; + if (!animate || refresh_time == 0.0f) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset+1) % IM_ARRAYSIZE(values); + phase += 0.10f*values_offset; + refresh_time += 1.0f/60.0f; + } + ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80)); + + // Use functions to generate output + // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count. + struct Funcs + { + static float Sin(void*, int i) { return sinf(i * 0.1f); } + static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } + }; + static int func_type = 0, display_count = 70; + ImGui::Separator(); + ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::Separator(); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) + { + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + } + + // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; + char buf[32]; + sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Color/Picker Widgets")) + { + static ImVec4 color = ImColor(114, 144, 154, 200); + + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool options_menu = true; + static bool hdr = false; + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options."); + ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + ImGui::Text("Color widget:"); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n"); + ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); + + ImGui::Text("Color widget HSV with Alpha:"); + ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags); + + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); + + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup."); + ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); + + ImGui::Text("Color button with Custom Picker Popup:"); + + // Generate a dummy palette + static bool saved_palette_inited = false; + static ImVec4 saved_palette[32]; + if (!saved_palette_inited) + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_inited = true; + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(); + open_popup |= ImGui::Button("Palette"); + if (open_popup) + { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) + { + // FIXME: Adding a drag and drop example here would be perfect! + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("Current"); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::PushID(n); + if ((n % 8) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20))) + color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); + EndDragDropTarget(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::EndPopup(); + } + + ImGui::Text("Color button only:"); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); + + ImGui::Text("Color picker:"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f); + static int inputs_mode = 2; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) + { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) + { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0"); + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); + ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; + if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; + if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB; + if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV; + if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX; + ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + + ImGui::Text("Programmatically set defaults/options:"); + ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Uint8 + HSV")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV); + ImGui::SameLine(); + if (ImGui::Button("Float + HDR")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_RGB); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Range Widgets")) + { + static float begin = 10, end = 90; + static int begin_i = 100, end_i = 1000; + ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%"); + ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %.0f units", "Max: %.0f units"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-component Widgets")) + { + static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + static int vec4i[4] = { 1, 5, 100, 255 }; + + ImGui::InputFloat2("input float2", vec4f); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); + ImGui::InputInt2("input int2", vec4i); + ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat3("input float3", vec4f); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); + ImGui::InputInt3("input int3", vec4i); + ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat4("input float4", vec4f); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Vertical Sliders")) + { + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + static int int_value = 0; + ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5); + ImGui::SameLine(); + + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; + ImGui::PushID("set1"); + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; + const int rows = 3; + const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows); + for (int nx = 0; nx < 4; nx++) + { + if (nx > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) + { + ImGui::PushID(nx*rows+ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Layout")) + { + if (ImGui::TreeNode("Child regions")) + { + static bool disable_mouse_wheel = false; + static bool disable_menu = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + ImGui::Checkbox("Disable Menu", &disable_menu); + + static int line = 50; + bool goto_line = ImGui::Button("Goto"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopItemWidth(); + + // Child 1: no border, enable horizontal scrollbar + { + ImGui::BeginChild("Child1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); + for (int i = 0; i < 100; i++) + { + ImGui::Text("%04d: scrollable region", i); + if (goto_line && line == i) + ImGui::SetScrollHere(); + } + if (goto_line && line >= 100) + ImGui::SetScrollHere(); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("Child2", ImVec2(0,300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0) | (disable_menu ? 0 : ImGuiWindowFlags_MenuBar)); + if (!disable_menu && ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Columns(2); + for (int i = 0; i < 100; i++) + { + if (i == 50) + ImGui::NextColumn(); + char buf[32]; + sprintf(buf, "%08x", i*5731); + ImGui::Button(buf, ImVec2(-1.0f, 0.0f)); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Widgets Width")) + { + static float f = 0.0f; + ImGui::Text("PushItemWidth(100)"); + ImGui::SameLine(); ShowHelpMarker("Fixed width."); + ImGui::PushItemWidth(100); + ImGui::DragFloat("float##1", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of window width."); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + ImGui::DragFloat("float##2", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f); + ImGui::DragFloat("float##3", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-100)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##4", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-1)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge"); + ImGui::PushItemWidth(-1); + ImGui::DragFloat("float##5", &f); + ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Basic Horizontal Layout")) + { + ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + + // Text + ImGui::Text("Two items: Hello"); ImGui::SameLine(); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Adjust spacing + ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Button + ImGui::AlignTextToFramePadding(); + ImGui::Text("Normal buttons"); ImGui::SameLine(); + ImGui::Button("Banana"); ImGui::SameLine(); + ImGui::Button("Apple"); ImGui::SameLine(); + ImGui::Button("Corniflower"); + + // Button + ImGui::Text("Small buttons"); ImGui::SameLine(); + ImGui::SmallButton("Like this one"); ImGui::SameLine(); + ImGui::Text("can fit within a text block."); + + // Aligned to arbitrary position. Easy/cheap column. + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::Text("x=150"); + ImGui::SameLine(300); ImGui::Text("x=300"); + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::SmallButton("x=150"); + ImGui::SameLine(300); ImGui::SmallButton("x=300"); + + // Checkbox + static bool c1=false,c2=false,c3=false,c4=false; + ImGui::Checkbox("My", &c1); ImGui::SameLine(); + ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); + ImGui::Checkbox("Is", &c3); ImGui::SameLine(); + ImGui::Checkbox("Rich", &c4); + + // Various + static float f0=1.0f, f1=2.0f, f2=3.0f; + ImGui::PushItemWidth(80); + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Y", &f1, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Z", &f2, 0.0f,5.0f); + ImGui::PopItemWidth(); + + ImGui::PushItemWidth(80); + ImGui::Text("Lists:"); + static int selection[4] = { 0, 1, 2, 3 }; + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); + } + ImGui::PopItemWidth(); + + // Dummy + ImVec2 sz(30,30); + ImGui::Button("A", sz); ImGui::SameLine(); + ImGui::Dummy(sz); ImGui::SameLine(); + ImGui::Button("B", sz); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Groups")) + { + ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + ImVec2 size = ImGui::GetItemRectSize(); + const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; + ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::SameLine(); + ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::Button("LEVERAGE\nBUZZWORD", size); + ImGui::SameLine(); + + ImGui::ListBoxHeader("List", size); + ImGui::Selectable("Selected", true); + ImGui::Selectable("Not Selected", false); + ImGui::ListBoxFooter(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text Baseline Alignment")) + { + ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)"); + + ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("One\nTwo\nThree"); + + ImGui::Button("HOP##1"); ImGui::SameLine(); + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("HOP##2"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("TEST##1"); ImGui::SameLine(); + ImGui::Text("TEST"); ImGui::SameLine(); + ImGui::SmallButton("TEST##2"); + + ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. + ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); + ImGui::Button("Widget##1"); ImGui::SameLine(); + ImGui::Text("Widget"); ImGui::SameLine(); + ImGui::SmallButton("Widget##2"); ImGui::SameLine(); + ImGui::Button("Widget##3"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). + bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + // Bullet + ImGui::Button("Button##3"); + ImGui::SameLine(0.0f, spacing); + ImGui::BulletText("Bullet text"); + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Node"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Scrolling")) + { + ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)"); + static bool track = true; + static int track_line = 50, scroll_to_px = 200; + ImGui::Checkbox("Track", &track); + ImGui::PushItemWidth(100); + ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %.0f"); + bool scroll_to = ImGui::Button("Scroll To Pos"); + ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %.0f px"); + ImGui::PopItemWidth(); + if (scroll_to) track = false; + + for (int i = 0; i < 5; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom"); + ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true); + if (scroll_to) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f); + for (int line = 0; line < 100; line++) + { + if (track && line == track_line) + { + ImGui::TextColored(ImColor(255,255,0), "Line %d", line); + ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } + else + { + ImGui::Text("Line %d", line); + } + } + float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag."); + ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing()*7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) + { + // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off + // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API) + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) + { + if (n > 0) ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf; + float hue = n*0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("Scroll from code"); ImGui::SameLine(); + ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) + { + ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window) + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + ImGui::End(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Clipping")) + { + static ImVec2 size(100, 100), offset(50, 20); + ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::TextWrapped("(Click and drag)"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y); + ImGui::InvisibleButton("##dummy", size); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } + ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x+size.x,pos.y+size.y), IM_COL32(90,90,120,255)); + ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x+offset.x,pos.y+offset.y), IM_COL32(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Popups & Modal windows")) + { + if (ImGui::TreeNode("Popups")) + { + ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); + + static int selected_fish = -1; + const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; + static bool toggles[] = { true, false, false, false, false }; + + // Simple selection popup + // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label) + if (ImGui::Button("Select..")) + ImGui::OpenPopup("select"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("select")) + { + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("toggle"); + if (ImGui::BeginPopup("toggle")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); + } + + if (ImGui::Button("Popup Menu..")) + ImGui::OpenPopup("FilePopup"); + if (ImGui::BeginPopup("FilePopup")) + { + ShowExampleMenuFile(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Context menus")) + { + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (IsItemHovered() && IsMouseClicked(0)) + // OpenPopup(id); + // return BeginPopup(id); + // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. + static float value = 0.5f; + ImGui::Text("Value = %.3f (<-- right-click here)", value); + if (ImGui::BeginPopupContextItem("item context menu")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::PushItemWidth(-1); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PopItemWidth(); + ImGui::EndPopup(); + } + + static char name[32] = "Label1"; + char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Modals")) + { + ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window."); + + if (ImGui::Button("Delete..")) + ImGui::OpenPopup("Delete?"); + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Separator(); + + //static int dummy_i = 0; + //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0"); + + static bool dont_ask_me_next_time = false; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); + ImGui::PopStyleVar(); + + if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::Button("Stacked modals..")) + ImGui::OpenPopup("Stacked 1"); + if (ImGui::BeginPopupModal("Stacked 1")) + { + ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDarkening] for darkening."); + static int item = 1; + ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal + + if (ImGui::Button("Add another modal..")) + ImGui::OpenPopup("Stacked 2"); + if (ImGui::BeginPopupModal("Stacked 2")) + { + ImGui::Text("Hello from Stacked The Second!"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. + // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here + // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Columns")) + { + ImGui::PushID("Columns"); + + // Basic columns + if (ImGui::TreeNode("Basic")) + { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) {} + //if (ImGui::Button(label, ImVec2(-1,0))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); + ImGui::Separator(); + const char* names[3] = { "One", "Two", "Three" }; + const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; + static int selected = -1; + for (int i = 0; i < 3; i++) + { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); ImGui::NextColumn(); + ImGui::Text(paths[i]); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Create multiple items in a same cell before switching to next column + if (ImGui::TreeNode("Mixed items")) + { + ImGui::Columns(3, "mixed"); + ImGui::Separator(); + + ImGui::Text("Hello"); + ImGui::Button("Banana"); + ImGui::NextColumn(); + + ImGui::Text("ImGui"); + ImGui::Button("Apple"); + static float foo = 1.0f; + ImGui::InputFloat("red", &foo, 0.05f, 0, 3); + ImGui::Text("An extra line here."); + ImGui::NextColumn(); + + ImGui::Text("Sailor"); + ImGui::Button("Corniflower"); + static float bar = 1.0f; + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); + ImGui::NextColumn(); + + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + if (ImGui::TreeNode("Word-wrapping")) + { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Borders")) + { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(4, NULL, v_borders); + for (int i = 0; i < 4*3; i++) + { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i); + ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + ImGui::Separator(); + ImGui::TreePop(); + } + + // Scrolling columns + /* + if (ImGui::TreeNode("Vertical Scrolling")) + { + ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); + ImGui::Columns(3); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::EndChild(); + ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); + ImGui::Columns(3); + for (int i = 0; i < 10; i++) + { + ImGui::Text("%04d", i); ImGui::NextColumn(); + ImGui::Text("Foobar"); ImGui::NextColumn(); + ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + */ + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) + { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + bool node_open = ImGui::TreeNode("Tree within single cell"); + ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell."); + if (node_open) + { + ImGui::Columns(2, "tree items"); + ImGui::Separator(); + if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn(); + if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::PopID(); + } + + if (ImGui::CollapsingHeader("Filtering")) + { + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + } + + if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) + { + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantMoveMouse: %d", io.WantMoveMouse); + ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); + + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to render a mouse cursor for you in software. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::CheckboxFlags("io.NavFlags: EnableGamepad", (unsigned int *)&io.NavFlags, ImGuiNavFlags_EnableGamepad); + ImGui::CheckboxFlags("io.NavFlags: EnableKeyboard", (unsigned int *)&io.NavFlags, ImGuiNavFlags_EnableKeyboard); + ImGui::CheckboxFlags("io.NavFlags: MoveMouse", (unsigned int *)&io.NavFlags, ImGuiNavFlags_MoveMouse); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to move your move cursor when using gamepad/keyboard navigation. NewFrame() will change io.MousePos and set the io.WantMoveMouse flag, your backend will need to apply the new mouse position."); + + if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) + { + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + + ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } + ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } + ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } + + ImGui::Button("Hovering me sets the\nkeyboard capture flag"); + if (ImGui::IsItemHovered()) + ImGui::CaptureKeyboardFromApp(true); + ImGui::SameLine(); + ImGui::Button("Holding me clears the\nthe keyboard capture flag"); + if (ImGui::IsItemActive()) + ImGui::CaptureKeyboardFromApp(false); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tabbing")) + { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "dummy"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushAllowKeyboardFocus(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets."); + ImGui::PopAllowKeyboardFocus(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focus from code")) + { + bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); + bool focus_3 = ImGui::Button("Focus on 3"); + int has_focus = 0; + static char buf[128] = "click on a button to set focus"; + + if (focus_1) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 1; + + if (focus_2) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 2; + + ImGui::PushAllowKeyboardFocus(false); + if (focus_3) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 3; + ImGui::PopAllowKeyboardFocus(); + + if (has_focus) + ImGui::Text("Item with focus: %d", has_focus); + else + ImGui::Text("Item with focus: "); + + // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item + static float f3[3] = { 0.0f, 0.0f, 0.0f }; + int focus_ahead = -1; + if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine(); + if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine(); + if (ImGui::Button("Focus on Z")) focus_ahead = 2; + if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); + ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); + + ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focused & Hovered Test")) + { + static bool embed_all_inside_a_child_window = false; + ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); + if (embed_all_inside_a_child_window) + ImGui::BeginChild("embeddingchild", ImVec2(0, ImGui::GetFontSize() * 25), true); + + // Testing IsWindowFocused() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n" + "IsWindowFocused(_AnyWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + + // Testing IsWindowHovered() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n" + "IsWindowHovered(_AnyWindow) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); + + // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) + ImGui::Button("ITEM"); + ImGui::BulletText( + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemhovered(_RectOnly) = %d\n", + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + + ImGui::BeginChild("child", ImVec2(0,50), true); + ImGui::Text("This is another child window for testing IsWindowHovered() flags."); + ImGui::EndChild(); + + if (embed_all_inside_a_child_window) + EndChild(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Dragging")) + { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + ImGui::Text("IsMouseDragging(%d):\n w/ default threshold: %d,\n w/ zero threshold: %d\n w/ large threshold: %d", + button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f)); + ImGui::Button("Drag Me"); + if (ImGui::IsItemActive()) + { + // Draw a line between the button and the mouse cursor + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRectFullScreen(); + draw_list->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); + draw_list->PopClipRect(); + + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold) + // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta() + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Mouse cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_Count_); + + ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_Count_; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + } + + ImGui::End(); +} + +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsClassic(); break; + case 1: ImGui::StyleColorsDark(); break; + case 2: ImGui::StyleColorsLight(); break; + } + return true; + } + return false; +} + +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = ImGui::GetIO(); + ImFont* font_current = ImGui::GetFont(); + if (ImGui::BeginCombo(label, font_current->GetDebugName())) + { + for (int n = 0; n < io.Fonts->Fonts.Size; n++) + if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current)) + io.FontDefault = io.Fonts->Fonts[n]; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker( + "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and documentation in misc/fonts/ for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} + +void ImGui::ShowStyleEditor(ImGuiStyle* ref) +{ + // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + init = false; + if (ref == NULL) + ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere."); + + if (ImGui::TreeNode("Rendering")) + { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(100); + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f); + if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f; + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Settings")) + { + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui::Text("BorderSize"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::Text("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::Text("Alignment"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Colors")) + { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export Unsaved")) + { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu."); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + + static ImGuiColorEditFlags alpha_flags = 0; + ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine(); + ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine(); + ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf); + + ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons. + // Read the FAQ and misc/fonts/README.txt about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i]; + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::TreePop(); + } + + bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size); + if (fonts_opened) + { + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::TreePop(); + } + ImGui::PushItemWidth(100); + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); + bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); + ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; + if (font_details_opened) + { + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font + ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, 0); + ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)"); + ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); + ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface)); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + { + ImFontConfig* cfg = &font->ConfigData[config_i]; + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); + } + if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + // Display all glyphs of the fonts in separate pages of 256 characters + const ImFontGlyph* glyph_fallback = font->FallbackGlyph; // Forcefully/dodgily make FindGlyph() return NULL on fallback, which isn't the default behavior. + font->FallbackGlyph = NULL; + for (int base = 0; base < 0x10000; base += 256) + { + int count = 0; + for (int n = 0; n < 256; n++) + count += font->FindGlyph((ImWchar)(base + n)) ? 1 : 0; + if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph")) + { + float cell_spacing = style.ItemSpacing.y; + ImVec2 cell_size(font->FontSize * 1, font->FontSize * 1); + ImVec2 base_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (int n = 0; n < 256; n++) + { + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size.x + cell_spacing), base_pos.y + (n / 16) * (cell_size.y + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size.x, cell_p1.y + cell_size.y); + const ImFontGlyph* glyph = font->FindGlyph((ImWchar)(base+n));; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); + font->RenderChar(draw_list, cell_size.x, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. + if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) + { + ImGui::BeginTooltip(); + ImGui::Text("Codepoint: U+%04X", base+n); + ImGui::Separator(); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); + ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + ImGui::EndTooltip(); + } + } + ImGui::Dummy(ImVec2((cell_size.x + cell_spacing) * 16, (cell_size.y + cell_spacing) * 16)); + ImGui::TreePop(); + } + } + font->FallbackGlyph = glyph_fallback; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + static float window_scale = 1.0f; + ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window + ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything + ImGui::PopItemWidth(); + ImGui::SetWindowFontScale(window_scale); + ImGui::TreePop(); + } + + ImGui::PopItemWidth(); +} + +// Demonstrate creating a fullscreen menu bar and populating it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(dummy menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + static bool b = true; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::Checkbox("Check", &b); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Colors")) + { + float sz = ImGui::GetTextLineHeight(); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName((ImGuiCol)i); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x+sz, p.y+sz), ImGui::GetColorU32((ImGuiCol)i)); + ImGui::Dummy(ImVec2(sz, sz)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + +// Demonstrate creating a window which gets auto-resized according to its content. +static void ShowExampleAppAutoResize(bool* p_open) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + static int lines = 10; + ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + ImGui::Text("%*sThis is line %d", i*4, "", i); // Pad with space to extend size horizontally + ImGui::End(); +} + +// Demonstrate creating a window with custom resize constraints. +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints // Helper functions to demonstrate programmatic constraints + { + static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } + static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + static bool auto_resize = false; + static int type = 0; + static int display_lines = 10; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step + + ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + { + const char* desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } + ImGui::PushItemWidth(200); + ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::PopItemWidth(); + ImGui::Checkbox("Auto-resize", &auto_resize); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + ImGui::End(); +} + +// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. +static void ShowExampleAppFixedOverlay(bool* p_open) +{ + const float DISTANCE = 10.0f; + static int corner = 0; + ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background + if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoFocusOnAppearing|ImGuiWindowFlags_NoNav)) + { + ImGui::Text("Simple overlay\nin the corner of the screen.\n(right-click to change position)"); + ImGui::Separator(); + ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + if (p_open && ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndPopup(); + } + ImGui::End(); + } +} + +// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. +// This apply to regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. +static void ShowExampleAppWindowTitles(bool*) +{ + // By default, Windows are uniquely identified by their title. + // You can use the "##" and "###" markers to manipulate the display/ID. + + // Using "##" to display same title but have unique identifier. + ImGui::SetNextWindowPos(ImVec2(100,100), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##1"); + ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(100,200), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##2"); + ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); + ImGui::End(); + + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" + char buf[128]; + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime()/0.25f)&3], ImGui::GetFrameCount()); + ImGui::SetNextWindowPos(ImVec2(100,300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(350,560), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Custom rendering", p_open)) + { + ImGui::End(); + return; + } + + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc. + // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4. + // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types) + // In this example we are not using the maths operators! + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Primitives + ImGui::Text("Primitives"); + static float sz = 36.0f; + static ImVec4 col = ImVec4(1.0f,1.0f,0.4f,1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); + ImGui::ColorEdit3("Color", &col.x); + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImU32 col32 = ImColor(col); + float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; + for (int n = 0; n < 2; n++) + { + float thickness = (n == 0) ? 1.0f : 4.0f; + draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, thickness); x += sz+spacing; + draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, thickness); x += spacing; + draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, thickness); + x = p.x + 4; + y += sz+spacing; + } + draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; + draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255)); + ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); + } + ImGui::Separator(); + { + static ImVector points; + static bool adding_line = false; + ImGui::Text("Canvas example"); + if (ImGui::Button("Clear")) points.clear(); + if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } } + ImGui::Text("Left-click and drag to add lines,\nRight-click to undo"); + + // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered() + // However you can draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos(). + // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max). + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + if (canvas_size.x < 50.0f) canvas_size.x = 50.0f; + if (canvas_size.y < 50.0f) canvas_size.y = 50.0f; + draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(50,50,50,255), IM_COL32(50,50,60,255), IM_COL32(60,60,70,255), IM_COL32(50,50,60,255)); + draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(255,255,255,255)); + + bool adding_preview = false; + ImGui::InvisibleButton("canvas", canvas_size); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + if (adding_line) + { + adding_preview = true; + points.push_back(mouse_pos_in_canvas); + if (!ImGui::IsMouseDown(0)) + adding_line = adding_preview = false; + } + if (ImGui::IsItemHovered()) + { + if (!adding_line && ImGui::IsMouseClicked(0)) + { + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (ImGui::IsMouseClicked(1) && !points.empty()) + { + adding_line = adding_preview = false; + points.pop_back(); + points.pop_back(); + } + } + draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x+canvas_size.x, canvas_pos.y+canvas_size.y), true); // clip lines within the canvas (if we resize it, etc.) + for (int i = 0; i < points.Size - 1; i += 2) + draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i+1].x, canvas_pos.y + points[i+1].y), IM_COL32(255,255,0,255), 2.0f); + draw_list->PopClipRect(); + if (adding_preview) + points.pop_back(); + } + ImGui::End(); +} + +// Demonstrating creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + bool ScrollToBottom; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImVector Commands; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); // "classify" is here to provide an example of "C"+[tab] completing to "CL" and displaying matches. + AddLog("Welcome to ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } + static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + ScrollToBottom = true; + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf)-1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close")) + *p_open = false; + ImGui::EndPopup(); + } + + ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); + if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + static ImGuiTextFilter filter; + filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::PopStyleVar(); + ImGui::Separator(); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. + // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. + // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: + // ImGuiListClipper clipper(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // However take note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! + // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!filter.PassFilter(item)) + continue; + ImVec4 col = col_default_text; + if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f); + else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(item); + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + if (ScrollToBottom) + ImGui::SetScrollHere(); + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + bool reclaim_focus = false; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) + { + char* input_end = InputBuf+strlen(InputBuf); + while (input_end > InputBuf && input_end[-1] == ' ') { input_end--; } *input_end = 0; + if (InputBuf[0]) + ExecCommand(InputBuf); + strcpy(InputBuf, ""); + reclaim_focus = true; + } + + // Demonstrate keeping focus on the input box + ImGui::SetItemDefaultFocus(); + if (reclaim_focus) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size-1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + } + + static int TextEditCallbackStub(ImGuiTextEditCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiTextEditCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing + data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + data->CursorPos = data->SelectionStart = data->SelectionEnd = data->BufTextLen = (int)snprintf(data->Buf, (size_t)data->BufSize, "%s", (HistoryPos >= 0) ? History[HistoryPos] : ""); + data->BufDirty = true; + } + } + } + return 0; + } +}; + +static void ShowExampleAppConsole(bool* p_open) +{ + static ExampleAppConsole console; + console.Draw("Example: Console", p_open); +} + +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct ExampleAppLog +{ + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear() { Buf.clear(); LineOffsets.clear(); } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open = NULL) + { + ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver); + ImGui::Begin(title, p_open); + if (ImGui::Button("Clear")) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + if (copy) ImGui::LogToClipboard(); + + if (Filter.IsActive()) + { + const char* buf_begin = Buf.begin(); + const char* line = buf_begin; + for (int line_no = 0; line != NULL; line_no++) + { + const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL; + if (Filter.PassFilter(line, line_end)) + ImGui::TextUnformatted(line, line_end); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } + else + { + ImGui::TextUnformatted(Buf.begin()); + } + + if (ScrollToBottom) + ImGui::SetScrollHere(1.0f); + ScrollToBottom = false; + ImGui::EndChild(); + ImGui::End(); + } +}; + +// Demonstrate creating a simple log window with basic filtering. +static void ShowExampleAppLog(bool* p_open) +{ + static ExampleAppLog log; + + // Demo: add random items (unless Ctrl is held) + static float last_time = -1.0f; + float time = ImGui::GetTime(); + if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) + { + const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; + log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); + last_time = time; + } + + log.Draw("Example: Log", p_open); +} + +// Demonstrate create a window with multiple child windows. +static void ShowExampleAppLayout(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // left + static int selected = 0; + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) + { + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + ImGui::SameLine(); + + // right + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + ImGui::End(); +} + +// Demonstrate create a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) + { + ImGui::End(); + return; + } + + ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API."); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2)); + ImGui::Columns(2); + ImGui::Separator(); + + struct funcs + { + static void ShowDummyObject(const char* prefix, int uid) + { + ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::NextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("my sailor is rich"); + ImGui::NextColumn(); + if (node_open) + { + static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f }; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) + { + ShowDummyObject("Child", 424242); + } + else + { + ImGui::AlignTextToFramePadding(); + // Here we use a Selectable (instead of Text) to highlight on hover + //ImGui::Text("Field_%d", i); + char label[32]; + sprintf(label, "Field_%d", i); + ImGui::Bullet(); + ImGui::Selectable(label); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + if (i >= 5) + ImGui::InputFloat("##value", &dummy_members[i], 1.0f); + else + ImGui::DragFloat("##value", &dummy_members[i], 0.01f); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + }; + + // Iterate dummy objects with dummy members (all the same data) + for (int obj_i = 0; obj_i < 3; obj_i++) + funcs::ShowDummyObject("Object", obj_i); + + ImGui::Columns(1); + ImGui::Separator(); + ImGui::PopStyleVar(); + ImGui::End(); +} + +// Demonstrate/test rendering huge amount of text, and the incidence of clipping. +static void ShowExampleAppLongText(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) + { + ImGui::End(); + return; + } + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { log.clear(); lines = 0; } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) + { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) + { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: + { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + ImGuiListClipper clipper(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + ImGui::EndChild(); + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowDemoWindow(bool*) {} +void ImGui::ShowUserGuide() {} +void ImGui::ShowStyleEditor(ImGuiStyle*) {} + +#endif diff --git a/apps/bench_shared/imgui/imgui_draw.cpp b/apps/bench_shared/imgui/imgui_draw.cpp new file mode 100644 index 00000000..118acabc --- /dev/null +++ b/apps/bench_shared/imgui/imgui_draw.cpp @@ -0,0 +1,2940 @@ +// dear imgui, v1.60 WIP +// (drawing and font code) + +// Contains implementation for +// - Default styles +// - ImDrawList +// - ImDrawData +// - ImFontAtlas +// - ImFont +// - Default font data + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#ifdef _WIN32 +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#elif defined(__GLIBC__) || defined(__sun) +#include // alloca +#else +#include // alloca +#endif +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" // warning : possible misuse of comma operator here // +#endif +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#endif + +//------------------------------------------------------------------------- +// STB libraries implementation +//------------------------------------------------------------------------- + +//#define IMGUI_STB_NAMESPACE ImGuiStb +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION + +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE +{ +#endif + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] +#endif + +#define STBRP_ASSERT(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#include "stb_rect_pack.h" + +#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) +#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) +#define STBTT_assert(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#include "stb_truetype.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +#ifdef IMGUI_STB_NAMESPACE +} // namespace ImGuiStb +using namespace IMGUI_STB_NAMESPACE; +#endif + +//----------------------------------------------------------------------------- +// Style functions +//----------------------------------------------------------------------------- + +void ImGui::StyleColorsDark(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.41f, 0.41f, 0.41f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); +} + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); +} + +// Those light colors are better suited with a thicker font than the default one + FrameBorder +void ImGui::StyleColorsLight(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + //colors[ImGuiCol_TextHovered] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + //colors[ImGuiCol_TextActive] = ImVec4(1.00f, 1.00f, 0.00f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.59f, 0.59f, 0.59f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); +} + +//----------------------------------------------------------------------------- +// ImDrawListData +//----------------------------------------------------------------------------- + +ImDrawListSharedData::ImDrawListSharedData() +{ + Font = NULL; + FontSize = 0.0f; + CurveTessellationTol = 0.0f; + ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); + + // Const data + for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++) + { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12); + CircleVtx12[i] = ImVec2(cosf(a), sinf(a)); + } +} + +//----------------------------------------------------------------------------- +// ImDrawList +//----------------------------------------------------------------------------- + +void ImDrawList::Clear() +{ + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + // NB: Do not clear channels so our allocations are re-used after the first frame. +} + +void ImDrawList::ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + for (int i = 0; i < _Channels.Size; i++) + { + if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0])); // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i].CmdBuffer.clear(); + _Channels[i].IdxBuffer.clear(); + } + _Channels.clear(); +} + +// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds +#define GetCurrentClipRect() (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1] : _Data->ClipRectFullscreen) +#define GetCurrentTextureId() (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL) + +void ImDrawList::AddDrawCmd() +{ + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = GetCurrentClipRect(); + draw_cmd.TextureId = GetCurrentTextureId(); + + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); + CmdBuffer.push_back(draw_cmd); +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +{ + ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL) + { + AddDrawCmd(); + current_cmd = &CmdBuffer.back(); + } + current_cmd->UserCallback = callback; + current_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. +// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. +void ImDrawList::UpdateClipRect() +{ + // If current command is used with different settings we need to add a new command + const ImVec4 curr_clip_rect = GetCurrentClipRect(); + ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->ClipRect = curr_clip_rect; +} + +void ImDrawList::UpdateTextureID() +{ + // If current command is used with different settings we need to add a new command + const ImTextureID curr_texture_id = GetCurrentTextureId(); + ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->TextureId = curr_texture_id; +} + +#undef GetCurrentClipRect +#undef GetCurrentTextureId + +// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) +void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect && _ClipRectStack.Size) + { + ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1]; + if (cr.x < current.x) cr.x = current.x; + if (cr.y < current.y) cr.y = current.y; + if (cr.z > current.z) cr.z = current.z; + if (cr.w > current.w) cr.w = current.w; + } + cr.z = ImMax(cr.x, cr.z); + cr.w = ImMax(cr.y, cr.w); + + _ClipRectStack.push_back(cr); + UpdateClipRect(); +} + +void ImDrawList::PushClipRectFullScreen() +{ + PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); +} + +void ImDrawList::PopClipRect() +{ + IM_ASSERT(_ClipRectStack.Size > 0); + _ClipRectStack.pop_back(); + UpdateClipRect(); +} + +void ImDrawList::PushTextureID(const ImTextureID& texture_id) +{ + _TextureIdStack.push_back(texture_id); + UpdateTextureID(); +} + +void ImDrawList::PopTextureID() +{ + IM_ASSERT(_TextureIdStack.Size > 0); + _TextureIdStack.pop_back(); + UpdateTextureID(); +} + +void ImDrawList::ChannelsSplit(int channels_count) +{ + IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) + _Channels.resize(channels_count); + _ChannelsCount = channels_count; + + // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer + // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. + // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } + else + { + _Channels[i].CmdBuffer.resize(0); + _Channels[i].IdxBuffer.resize(0); + } + if (_Channels[i].CmdBuffer.Size == 0) + { + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = _ClipRectStack.back(); + draw_cmd.TextureId = _TextureIdStack.back(); + _Channels[i].CmdBuffer.push_back(draw_cmd); + } + } +} + +void ImDrawList::ChannelsMerge() +{ + // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. + if (_ChannelsCount <= 1) + return; + + ChannelsSetCurrent(0); + if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0) + CmdBuffer.pop_back(); + + int new_cmd_buffer_count = 0, new_idx_buffer_count = 0; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0) + ch.CmdBuffer.pop_back(); + new_cmd_buffer_count += ch.CmdBuffer.Size; + new_idx_buffer_count += ch.IdxBuffer.Size; + } + CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count); + IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count); + + ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count; + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } + if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } + } + UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. + _ChannelsCount = 1; +} + +void ImDrawList::ChannelsSetCurrent(int idx) +{ + IM_ASSERT(idx < _ChannelsCount); + if (_ChannelsCurrent == idx) return; + memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times + memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer)); + _ChannelsCurrent = idx; + memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer)); + memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer)); + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size; +} + +// NB: this can be called with negative count for removing primitives (as long as the result does not underflow) +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1]; + draw_cmd.ElemCount += idx_count; + + int vtx_buffer_old_size = VtxBuffer.Size; + VtxBuffer.resize(vtx_buffer_old_size + vtx_count); + _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + + int idx_buffer_old_size = IdxBuffer.Size; + IdxBuffer.resize(idx_buffer_old_size + idx_count); + _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; +} + +// Fully unrolled with inline call to keep our debug builds decently fast. +void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +// TODO: Thickness anti-aliased lines cap are missing their AA fringe. +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) +{ + if (points_count < 2) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + int count = points_count; + if (!closed) + count = points_count-1; + + const bool thick_line = thickness > 1.0f; + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + // Anti-aliased stroke + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const int idx_count = thick_line ? count*18 : count*12; + const int vtx_count = thick_line ? points_count*4 : points_count*3; + PrimReserve(idx_count, vtx_count); + + // Temporary buffer + ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); + ImVec2* temp_points = temp_normals + points_count; + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + ImVec2 diff = points[i2] - points[i1]; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i1].x = diff.y; + temp_normals[i1].y = -diff.x; + } + if (!closed) + temp_normals[points_count-1] = temp_normals[points_count-2]; + + if (!thick_line) + { + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; + temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; + temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; + temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE; + temp_points[i2*2+0] = points[i2] + dm; + temp_points[i2*2+1] = points[i2] - dm; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1); + _IdxWritePtr += 12; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; + _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans; + _VtxWritePtr += 3; + } + } + else + { + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE); + ImVec2 dm_in = dm * half_inner_thickness; + temp_points[i2*4+0] = points[i2] + dm_out; + temp_points[i2*4+1] = points[i2] + dm_in; + temp_points[i2*4+2] = points[i2] - dm_in; + temp_points[i2*4+3] = points[i2] - dm_out; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+1); _IdxWritePtr[1] = (ImDrawIdx)(idx1+1); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3); + _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2); + _IdxWritePtr += 18; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Stroke + const int idx_count = count*6; + const int vtx_count = count*4; // FIXME-OPT: Not sharing edges + PrimReserve(idx_count, vtx_count); + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + const ImVec2& p1 = points[i1]; + const ImVec2& p2 = points[i2]; + ImVec2 diff = p2 - p1; + diff *= ImInvLength(diff, 1.0f); + + const float dx = diff.x * (thickness * 0.5f); + const float dy = diff.y * (thickness * 0.5f); + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2); + _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3); + _IdxWritePtr += 6; + _VtxCurrentIdx += 4; + } + } +} + +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count-2)*3 + points_count*6; + const int vtx_count = (points_count*2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx+1; + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); + _IdxWritePtr += 3; + } + + // Compute normals + ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + ImVec2 diff = p1 - p0; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i0].x = diff.y; + temp_normals[i0].y = -diff.x; + } + + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + ImVec2 dm = (n0 + n1) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count-2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius == 0.0f || a_min_of_12 > a_max_of_12) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); + for (int a = a_min_of_12; a <= a_max_of_12; a++) + { + const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)]; + _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); + } +} + +void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) +{ + if (radius == 0.0f) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (num_segments + 1)); + for (int i = 0; i <= num_segments; i++) + { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _Path.push_back(ImVec2(centre.x + cosf(a) * radius, centre.y + sinf(a) * radius)); + } +} + +static void PathBezierToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy)) + { + path->push_back(ImVec2(x4, y4)); + } + else if (level < 10) + { + float x12 = (x1+x2)*0.5f, y12 = (y1+y2)*0.5f; + float x23 = (x2+x3)*0.5f, y23 = (y2+y3)*0.5f; + float x34 = (x3+x4)*0.5f, y34 = (y3+y4)*0.5f; + float x123 = (x12+x23)*0.5f, y123 = (y12+y23)*0.5f; + float x234 = (x23+x34)*0.5f, y234 = (y23+y34)*0.5f; + float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f; + + PathBezierToCasteljau(path, x1,y1, x12,y12, x123,y123, x1234,y1234, tess_tol, level+1); + PathBezierToCasteljau(path, x1234,y1234, x234,y234, x34,y34, x4,y4, tess_tol, level+1); + } +} + +void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + // Auto-tessellated + PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + { + float t = t_step * i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t*t*t; + _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y)); + } + } +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) +{ + rounding = ImMin(rounding, fabsf(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, fabsf(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); + + if (rounding <= 0.0f || rounding_corners == 0) + { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } + else + { + const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; + const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; + const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; + const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); + } +} + +void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(a + ImVec2(0.5f,0.5f)); + PathLineTo(b + ImVec2(0.5f,0.5f)); + PathStroke(col, false, thickness); +} + +// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly. +void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.5f,0.5f), rounding, rounding_corners_flags); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (rounding > 0.0f) + { + PathRect(a, b, rounding, rounding_corners_flags); + PathFillConvex(col); + } + else + { + PrimReserve(6, 4); + PrimRect(a, b, col); + } +} + +void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) +{ + if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + PrimReserve(6, 4); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3)); + PrimWriteVtx(a, uv, col_upr_left); + PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right); + PrimWriteVtx(c, uv, col_bot_right); + PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left); +} + +void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathFillConvex(col); +} + +void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathFillConvex(col); +} + +void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius, 0.0f, a_max, num_segments); + PathFillConvex(col); +} + +void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(pos0); + PathBezierCurveTo(cp0, cp1, pos1, num_segments); + PathStroke(col, false, thickness); +} + +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // Pull default font/size from the shared ImDrawListSharedData instance + if (font == NULL) + font = _Data->Font; + if (font_size == 0.0f) + font_size = _Data->FontSize; + + IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + + ImVec4 clip_rect = _ClipRectStack.back(); + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); +} + +void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) +{ + AddText(NULL, 0.0f, pos, col, text_begin, text_end); +} + +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(a, b, uv_a, uv_b, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) + { + AddImage(user_texture_id, a, b, uv_a, uv_b, col); + return; + } + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + int vert_start_idx = VtxBuffer.Size; + PathRect(a, b, rounding, rounding_corners); + PathFillConvex(col); + int vert_end_idx = VtxBuffer.Size; + ImGui::ShadeVertsLinearUV(VtxBuffer.Data + vert_start_idx, VtxBuffer.Data + vert_end_idx, a, b, uv_a, uv_b, true); + + if (push_texture_id) + PopTextureID(); +} + +//----------------------------------------------------------------------------- +// ImDrawData +//----------------------------------------------------------------------------- + +// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! +void ImDrawData::DeIndexAllBuffers() +{ + ImVector new_vtx_buffer; + TotalVtxCount = TotalIdxCount = 0; + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + if (cmd_list->IdxBuffer.empty()) + continue; + new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); + for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; + cmd_list->VtxBuffer.swap(new_vtx_buffer); + cmd_list->IdxBuffer.resize(0); + TotalVtxCount += cmd_list->VtxBuffer.Size; + } +} + +// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +void ImDrawData::ScaleClipRects(const ImVec2& scale) +{ + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y); + } + } +} + +//----------------------------------------------------------------------------- +// Shade functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Scan and shade backward from the end of given vertices. Assume vertices are text only (= vert_start..vert_end going left to right) so we can break as soon as we are out the gradient bounds. +void ImGui::ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x) +{ + float gradient_extent_x = gradient_p1_x - gradient_p0_x; + float gradient_inv_length2 = 1.0f / (gradient_extent_x * gradient_extent_x); + int full_alpha_count = 0; + for (ImDrawVert* vert = vert_end - 1; vert >= vert_start; vert--) + { + float d = (vert->pos.x - gradient_p0_x) * (gradient_extent_x); + float alpha_mul = 1.0f - ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + if (alpha_mul >= 1.0f && ++full_alpha_count > 2) + return; // Early out + int a = (int)(((vert->col >> IM_COL32_A_SHIFT) & 0xFF) * alpha_mul); + vert->col = (vert->col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); + } +} + +// Distribute UV over (a, b) rectangle +void ImGui::ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + const ImVec2 scale = ImVec2( + size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, + size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + + if (clamp) + { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } + else + { + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + FontData = NULL; + FontDataSize = 0; + FontDataOwnedByAtlas = true; + FontNo = 0; + SizePixels = 0.0f; + OversampleH = 3; + OversampleV = 1; + PixelSnapH = false; + GlyphExtraSpacing = ImVec2(0.0f, 0.0f); + GlyphOffset = ImVec2(0.0f, 0.0f); + GlyphRanges = NULL; + MergeMode = false; + RasterizerFlags = 0x00; + RasterizerMultiply = 1.0f; + memset(Name, 0, sizeof(Name)); + DstFont = NULL; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas +//----------------------------------------------------------------------------- + +// A work of art lies ahead! (. = white layer, X = black layer, others are blank) +// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. +const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 90; +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX" + "..- -X.....X- X.X - X.X -X.....X - X.....X" + "--- -XXX.XXX- X...X - X...X -X....X - X....X" + "X - X.X - X.....X - X.....X -X...X - X...X" + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X" + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X" + "X..X - X.X - X.X - X.X -XX X.X - X.X XX" + "X...X - X.X - X.X - XX X.X XX - X.X - X.X " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X " + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X " + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X " + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X " + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X " + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X " + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X " + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------" + "X.X X..X - -X.......X- X.......X - XX XX - " + "XX X..X - - X.....X - X.....X - X.X X.X - " + " X..X - X...X - X...X - X..X X..X - " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - " + "------------ - X - X -X.....................X- " + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_Count_][3] = +{ + // Pos ........ Size ......... Offset ...... + { ImVec2(0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow + { ImVec2(13,0), ImVec2(7,16), ImVec2( 4, 8) }, // ImGuiMouseCursor_TextInput + { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll + { ImVec2(21,0), ImVec2( 9,23), ImVec2( 5,11) }, // ImGuiMouseCursor_ResizeNS + { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 5) }, // ImGuiMouseCursor_ResizeEW + { ImVec2(73,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNESW + { ImVec2(55,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNWSE +}; + +ImFontAtlas::ImFontAtlas() +{ + Flags = 0x00; + TexID = NULL; + TexDesiredWidth = 0; + TexGlyphPadding = 1; + + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; + TexWidth = TexHeight = 0; + TexUvScale = ImVec2(0.0f, 0.0f); + TexUvWhitePixel = ImVec2(0.0f, 0.0f); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + for (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) + { + ImGui::MemFree(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) + { + Fonts[i]->ConfigData = NULL; + Fonts[i]->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +void ImFontAtlas::ClearTexData() +{ + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + +void ImFontAtlas::ClearFonts() +{ + for (int i = 0; i < Fonts.Size; i++) + IM_DELETE(Fonts[i]); + Fonts.clear(); +} + +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Build atlas on demand + if (TexPixelsAlpha8 == NULL) + { + if (ConfigData.empty()) + AddFontDefault(); + Build(); + } + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Convert to RGBA32 format on demand + // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels = NULL; + GetTexDataAsAlpha8(&pixels, NULL, NULL); + if (pixels) + { + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + } + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +{ + IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT(font_cfg->SizePixels > 0.0f); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + + ConfigData.push_back(*font_cfg); + ImFontConfig& new_font_cfg = ConfigData.back(); + if (!new_font_cfg.DstFont) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) + { + new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize); + new_font_cfg.FontDataOwnedByAtlas = true; + memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + } + + // Invalidate texture + ClearTexData(); + return new_font_cfg.DstFont; +} + +// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) +static unsigned int stb_decompress_length(unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); +static const char* GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } +static void Decode85(const unsigned char* src, unsigned char* dst) +{ + while (*src) + { + unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px"); + if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f; + + const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, GetGlyphRangesDefault()); + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + int data_size = 0; + void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); + if (!data) + { + IM_ASSERT(0); // Could not load file. + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') + { + // Store a short copy of filename into into the font name for convenience + const char* p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + snprintf(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges); +} + +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels; + if (glyph_ranges) + font_cfg.GlyphRanges = glyph_ranges; + return AddFont(&font_cfg); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + const unsigned int buf_decompressed_size = stb_decompress_length((unsigned char*)compressed_ttf_data); + unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed_data, (unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontDataOwnedByAtlas = true; + return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) +{ + int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size); + Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); + ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + ImGui::MemFree(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) +{ + IM_ASSERT(id >= 0x10000); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) +{ + IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed + *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); + *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); +} + +bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) +{ + if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_Count_) + return false; + if (Flags & ImFontAtlasFlags_NoMouseCursors) + return false; + + ImFontAtlas::CustomRect& r = CustomRects[CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r.X, (float)r.Y); + ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; + *out_size = size; + *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; + out_uv_border[0] = (pos) * TexUvScale; + out_uv_border[1] = (pos + size) * TexUvScale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + out_uv_fill[0] = (pos) * TexUvScale; + out_uv_fill[1] = (pos + size) * TexUvScale; + return true; +} + +bool ImFontAtlas::Build() +{ + return ImFontAtlasBuildWithStbTruetype(this); +} + +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + +bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->ConfigData.Size > 0); + + ImFontAtlasBuildRegisterDefaultCustomRects(atlas); + + atlas->TexID = NULL; + atlas->TexWidth = atlas->TexHeight = 0; + atlas->TexUvScale = ImVec2(0.0f, 0.0f); + atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); + atlas->ClearTexData(); + + // Count glyphs/ranges + int total_glyphs_count = 0; + int total_ranges_count = 0; + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + if (!cfg.GlyphRanges) + cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++) + total_glyphs_count += (in_range[1] - in_range[0]) + 1; + } + + // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. + // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. + atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; + atlas->TexHeight = 0; + + // Start packing + const int max_tex_height = 1024*32; + stbtt_pack_context spc = {}; + if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL)) + return false; + stbtt_PackSetOversampling(&spc, 1, 1); + + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + + // Initialize font information (so we can error without any cleanup) + struct ImFontTempBuildData + { + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + int RectsCount; + stbtt_pack_range* Ranges; + int RangesCount; + }; + ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData)); + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0); + if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + { + atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure + ImGui::MemFree(tmp_array); + return false; + } + } + + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; + stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); + stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); + stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); + memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); + memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. + memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); + + // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + + // Setup ranges + int font_glyphs_count = 0; + int font_ranges_count = 0; + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) + font_glyphs_count += (in_range[1] - in_range[0]) + 1; + tmp.Ranges = buf_ranges + buf_ranges_n; + tmp.RangesCount = font_ranges_count; + buf_ranges_n += font_ranges_count; + for (int i = 0; i < font_ranges_count; i++) + { + const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; + stbtt_pack_range& range = tmp.Ranges[i]; + range.font_size = cfg.SizePixels; + range.first_unicode_codepoint_in_range = in_range[0]; + range.num_chars = (in_range[1] - in_range[0]) + 1; + range.chardata_for_range = buf_packedchars + buf_packedchars_n; + buf_packedchars_n += range.num_chars; + } + + // Pack + tmp.Rects = buf_rects + buf_rects_n; + tmp.RectsCount = font_glyphs_count; + buf_rects_n += font_glyphs_count; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + IM_ASSERT(n == font_glyphs_count); + stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n); + + // Extend texture height + for (int i = 0; i < n; i++) + if (tmp.Rects[i].was_packed) + atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h); + } + IM_ASSERT(buf_rects_n == total_glyphs_count); + IM_ASSERT(buf_packedchars_n == total_glyphs_count); + IM_ASSERT(buf_ranges_n == total_ranges_count); + + // Create texture + atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); + atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); + memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + spc.pixels = atlas->TexPixelsAlpha8; + spc.height = atlas->TexHeight; + + // Second pass: render font characters + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); + } + tmp.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + ImGui::MemFree(buf_rects); + buf_rects = NULL; + + // Third pass: setup ImFont and glyphs for runtime + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) + + const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = unscaled_ascent * font_scale; + const float descent = unscaled_descent * font_scale; + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float off_x = cfg.GlyphOffset.x; + const float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); + + for (int i = 0; i < tmp.RangesCount; i++) + { + stbtt_pack_range& range = tmp.Ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) + { + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + const int codepoint = range.first_unicode_codepoint_in_range + char_idx; + if (cfg.MergeMode && dst_font->FindGlyph((unsigned short)codepoint)) + continue; + + stbtt_aligned_quad q; + float dummy_x = 0.0f, dummy_y = 0.0f; + stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); + dst_font->AddGlyph((ImWchar)codepoint, q.x0 + off_x, q.y0 + off_y, q.x1 + off_x, q.y1 + off_y, q.s0, q.t0, q.s1, q.t1, pc.xadvance); + } + } + } + + // Cleanup temporaries + ImGui::MemFree(buf_packedchars); + ImGui::MemFree(buf_ranges); + ImGui::MemFree(tmp_array); + + ImFontAtlasBuildFinish(atlas); + + return true; +} + +void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) +{ + if (atlas->CustomRectIds[0] >= 0) + return; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + else + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, 2, 2); +} + +void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) + { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) +{ + stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; + + ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + + ImVector pack_rects; + pack_rects.resize(user_rects.Size); + memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); + for (int i = 0; i < user_rects.Size; i++) + { + pack_rects[i].w = user_rects[i].Width; + pack_rects[i].h = user_rects[i].Height; + } + stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); + for (int i = 0; i < pack_rects.Size; i++) + if (pack_rects[i].was_packed) + { + user_rects[i].X = pack_rects[i].x; + user_rects[i].Y = pack_rects[i].y; + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + } +} + +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->CustomRectIds[0] >= 0); + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + IM_ASSERT(r.IsPacked()); + + const int w = atlas->TexWidth; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + { + // Render/copy pixels + IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * 2 + 1 && r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++) + for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++) + { + const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * w; + const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00; + atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00; + } + } + else + { + IM_ASSERT(r.Width == 2 && r.Height == 2); + const int offset = (int)(r.X) + (int)(r.Y) * w; + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + } + atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * atlas->TexUvScale.x, (r.Y + 0.5f) * atlas->TexUvScale.y); +} + +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data block + ImFontAtlasBuildRenderDefaultTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; + if (r.Font == NULL || r.ID > 0x10000) + continue; + + IM_ASSERT(r.Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + atlas->Fonts[i]->BuildLookupTable(); +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD79D, // Korean characters + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesChinese() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() +{ + // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. + // This encoding is designed to helps us reduce the source code size. + // FIXME: Source a list of the revised 2136 joyo kanji list from 2010 and rebuild this. + // The current list was sourced from http://theinstructionlimit.com/author/renaudbedardrenaudbedard/page/3 + // Note that you may use ImFontAtlas::GlyphRangesBuilder to create your own ranges, by merging existing ranges or adding new characters. + static const short offsets_from_0x4E00[] = + { + -1,0,1,3,0,0,0,0,1,0,5,1,1,0,7,4,6,10,0,1,9,9,7,1,3,19,1,10,7,1,0,1,0,5,1,0,6,4,2,6,0,0,12,6,8,0,3,5,0,1,0,9,0,0,8,1,1,3,4,5,13,0,0,8,2,17, + 4,3,1,1,9,6,0,0,0,2,1,3,2,22,1,9,11,1,13,1,3,12,0,5,9,2,0,6,12,5,3,12,4,1,2,16,1,1,4,6,5,3,0,6,13,15,5,12,8,14,0,0,6,15,3,6,0,18,8,1,6,14,1, + 5,4,12,24,3,13,12,10,24,0,0,0,1,0,1,1,2,9,10,2,2,0,0,3,3,1,0,3,8,0,3,2,4,4,1,6,11,10,14,6,15,3,4,15,1,0,0,5,2,2,0,0,1,6,5,5,6,0,3,6,5,0,0,1,0, + 11,2,2,8,4,7,0,10,0,1,2,17,19,3,0,2,5,0,6,2,4,4,6,1,1,11,2,0,3,1,2,1,2,10,7,6,3,16,0,8,24,0,0,3,1,1,3,0,1,6,0,0,0,2,0,1,5,15,0,1,0,0,2,11,19, + 1,4,19,7,6,5,1,0,0,0,0,5,1,0,1,9,0,0,5,0,2,0,1,0,3,0,11,3,0,2,0,0,0,0,0,9,3,6,4,12,0,14,0,0,29,10,8,0,14,37,13,0,31,16,19,0,8,30,1,20,8,3,48, + 21,1,0,12,0,10,44,34,42,54,11,18,82,0,2,1,2,12,1,0,6,2,17,2,12,7,0,7,17,4,2,6,24,23,8,23,39,2,16,23,1,0,5,1,2,15,14,5,6,2,11,0,8,6,2,2,2,14, + 20,4,15,3,4,11,10,10,2,5,2,1,30,2,1,0,0,22,5,5,0,3,1,5,4,1,0,0,2,2,21,1,5,1,2,16,2,1,3,4,0,8,4,0,0,5,14,11,2,16,1,13,1,7,0,22,15,3,1,22,7,14, + 22,19,11,24,18,46,10,20,64,45,3,2,0,4,5,0,1,4,25,1,0,0,2,10,0,0,0,1,0,1,2,0,0,9,1,2,0,0,0,2,5,2,1,1,5,5,8,1,1,1,5,1,4,9,1,3,0,1,0,1,1,2,0,0, + 2,0,1,8,22,8,1,0,0,0,0,4,2,1,0,9,8,5,0,9,1,30,24,2,6,4,39,0,14,5,16,6,26,179,0,2,1,1,0,0,0,5,2,9,6,0,2,5,16,7,5,1,1,0,2,4,4,7,15,13,14,0,0, + 3,0,1,0,0,0,2,1,6,4,5,1,4,9,0,3,1,8,0,0,10,5,0,43,0,2,6,8,4,0,2,0,0,9,6,0,9,3,1,6,20,14,6,1,4,0,7,2,3,0,2,0,5,0,3,1,0,3,9,7,0,3,4,0,4,9,1,6,0, + 9,0,0,2,3,10,9,28,3,6,2,4,1,2,32,4,1,18,2,0,3,1,5,30,10,0,2,2,2,0,7,9,8,11,10,11,7,2,13,7,5,10,0,3,40,2,0,1,6,12,0,4,5,1,5,11,11,21,4,8,3,7, + 8,8,33,5,23,0,0,19,8,8,2,3,0,6,1,1,1,5,1,27,4,2,5,0,3,5,6,3,1,0,3,1,12,5,3,3,2,0,7,7,2,1,0,4,0,1,1,2,0,10,10,6,2,5,9,7,5,15,15,21,6,11,5,20, + 4,3,5,5,2,5,0,2,1,0,1,7,28,0,9,0,5,12,5,5,18,30,0,12,3,3,21,16,25,32,9,3,14,11,24,5,66,9,1,2,0,5,9,1,5,1,8,0,8,3,3,0,1,15,1,4,8,1,2,7,0,7,2, + 8,3,7,5,3,7,10,2,1,0,0,2,25,0,6,4,0,10,0,4,2,4,1,12,5,38,4,0,4,1,10,5,9,4,0,14,4,2,5,18,20,21,1,3,0,5,0,7,0,3,7,1,3,1,1,8,1,0,0,0,3,2,5,2,11, + 6,0,13,1,3,9,1,12,0,16,6,2,1,0,2,1,12,6,13,11,2,0,28,1,7,8,14,13,8,13,0,2,0,5,4,8,10,2,37,42,19,6,6,7,4,14,11,18,14,80,7,6,0,4,72,12,36,27, + 7,7,0,14,17,19,164,27,0,5,10,7,3,13,6,14,0,2,2,5,3,0,6,13,0,0,10,29,0,4,0,3,13,0,3,1,6,51,1,5,28,2,0,8,0,20,2,4,0,25,2,10,13,10,0,16,4,0,1,0, + 2,1,7,0,1,8,11,0,0,1,2,7,2,23,11,6,6,4,16,2,2,2,0,22,9,3,3,5,2,0,15,16,21,2,9,20,15,15,5,3,9,1,0,0,1,7,7,5,4,2,2,2,38,24,14,0,0,15,5,6,24,14, + 5,5,11,0,21,12,0,3,8,4,11,1,8,0,11,27,7,2,4,9,21,59,0,1,39,3,60,62,3,0,12,11,0,3,30,11,0,13,88,4,15,5,28,13,1,4,48,17,17,4,28,32,46,0,16,0, + 18,11,1,8,6,38,11,2,6,11,38,2,0,45,3,11,2,7,8,4,30,14,17,2,1,1,65,18,12,16,4,2,45,123,12,56,33,1,4,3,4,7,0,0,0,3,2,0,16,4,2,4,2,0,7,4,5,2,26, + 2,25,6,11,6,1,16,2,6,17,77,15,3,35,0,1,0,5,1,0,38,16,6,3,12,3,3,3,0,9,3,1,3,5,2,9,0,18,0,25,1,3,32,1,72,46,6,2,7,1,3,14,17,0,28,1,40,13,0,20, + 15,40,6,38,24,12,43,1,1,9,0,12,6,0,6,2,4,19,3,7,1,48,0,9,5,0,5,6,9,6,10,15,2,11,19,3,9,2,0,1,10,1,27,8,1,3,6,1,14,0,26,0,27,16,3,4,9,6,2,23, + 9,10,5,25,2,1,6,1,1,48,15,9,15,14,3,4,26,60,29,13,37,21,1,6,4,0,2,11,22,23,16,16,2,2,1,3,0,5,1,6,4,0,0,4,0,0,8,3,0,2,5,0,7,1,7,3,13,2,4,10, + 3,0,2,31,0,18,3,0,12,10,4,1,0,7,5,7,0,5,4,12,2,22,10,4,2,15,2,8,9,0,23,2,197,51,3,1,1,4,13,4,3,21,4,19,3,10,5,40,0,4,1,1,10,4,1,27,34,7,21, + 2,17,2,9,6,4,2,3,0,4,2,7,8,2,5,1,15,21,3,4,4,2,2,17,22,1,5,22,4,26,7,0,32,1,11,42,15,4,1,2,5,0,19,3,1,8,6,0,10,1,9,2,13,30,8,2,24,17,19,1,4, + 4,25,13,0,10,16,11,39,18,8,5,30,82,1,6,8,18,77,11,13,20,75,11,112,78,33,3,0,0,60,17,84,9,1,1,12,30,10,49,5,32,158,178,5,5,6,3,3,1,3,1,4,7,6, + 19,31,21,0,2,9,5,6,27,4,9,8,1,76,18,12,1,4,0,3,3,6,3,12,2,8,30,16,2,25,1,5,5,4,3,0,6,10,2,3,1,0,5,1,19,3,0,8,1,5,2,6,0,0,0,19,1,2,0,5,1,2,5, + 1,3,7,0,4,12,7,3,10,22,0,9,5,1,0,2,20,1,1,3,23,30,3,9,9,1,4,191,14,3,15,6,8,50,0,1,0,0,4,0,0,1,0,2,4,2,0,2,3,0,2,0,2,2,8,7,0,1,1,1,3,3,17,11, + 91,1,9,3,2,13,4,24,15,41,3,13,3,1,20,4,125,29,30,1,0,4,12,2,21,4,5,5,19,11,0,13,11,86,2,18,0,7,1,8,8,2,2,22,1,2,6,5,2,0,1,2,8,0,2,0,5,2,1,0, + 2,10,2,0,5,9,2,1,2,0,1,0,4,0,0,10,2,5,3,0,6,1,0,1,4,4,33,3,13,17,3,18,6,4,7,1,5,78,0,4,1,13,7,1,8,1,0,35,27,15,3,0,0,0,1,11,5,41,38,15,22,6, + 14,14,2,1,11,6,20,63,5,8,27,7,11,2,2,40,58,23,50,54,56,293,8,8,1,5,1,14,0,1,12,37,89,8,8,8,2,10,6,0,0,0,4,5,2,1,0,1,1,2,7,0,3,3,0,4,6,0,3,2, + 19,3,8,0,0,0,4,4,16,0,4,1,5,1,3,0,3,4,6,2,17,10,10,31,6,4,3,6,10,126,7,3,2,2,0,9,0,0,5,20,13,0,15,0,6,0,2,5,8,64,50,3,2,12,2,9,0,0,11,8,20, + 109,2,18,23,0,0,9,61,3,0,28,41,77,27,19,17,81,5,2,14,5,83,57,252,14,154,263,14,20,8,13,6,57,39,38, + }; + static ImWchar base_ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + }; + static bool full_ranges_unpacked = false; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(offsets_from_0x4E00)*2 + 1]; + if (!full_ranges_unpacked) + { + // Unpack + int codepoint = 0x4e00; + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + ImWchar* dst = full_ranges + IM_ARRAYSIZE(base_ranges);; + for (int n = 0; n < IM_ARRAYSIZE(offsets_from_0x4E00); n++, dst += 2) + dst[0] = dst[1] = (ImWchar)(codepoint += (offsets_from_0x4E00[n] + 1)); + dst[0] = 0; + full_ranges_unpacked = true; + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas::GlyphRangesBuilder +//----------------------------------------------------------------------------- + +void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end) +{ + while (text_end ? (text < text_end) : *text) + { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + if (c < 0x10000) + AddChar((ImWchar)c); + } +} + +void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges) +{ + for (; ranges[0]; ranges += 2) + for (ImWchar c = ranges[0]; c <= ranges[1]; c++) + AddChar(c); +} + +void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector* out_ranges) +{ + for (int n = 0; n < 0x10000; n++) + if (GetBit(n)) + { + out_ranges->push_back((ImWchar)n); + while (n < 0x10000 && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + Scale = 1.0f; + FallbackChar = (ImWchar)'?'; + DisplayOffset = ImVec2(0.0f, 1.0f); + ClearOutputData(); +} + +ImFont::~ImFont() +{ + // Invalidate active font so that the user gets a clear crash instead of a dangling pointer. + // If you want to delete fonts you need to do it between Render() and NewFrame(). + // FIXME-CLEANUP + /* + ImGuiContext& g = *GImGui; + if (g.Font == this) + g.Font = NULL; + */ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + FallbackAdvanceX = 0.0f; + ConfigDataCount = 0; + ConfigData = NULL; + ContainerAtlas = NULL; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +void ImFont::BuildLookupTable() +{ + int max_codepoint = 0; + for (int i = 0; i != Glyphs.Size; i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + + IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + GrowIndex(max_codepoint + 1); + for (int i = 0; i < Glyphs.Size; i++) + { + int codepoint = (int)Glyphs[i].Codepoint; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; + IndexLookup[codepoint] = (unsigned short)i; + } + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (FindGlyph((unsigned short)' ')) + { + if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& tab_glyph = Glyphs.back(); + tab_glyph = *FindGlyph((unsigned short)' '); + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX *= 4; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; + IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); + } + + FallbackGlyph = NULL; + FallbackGlyph = FindGlyph(FallbackChar); + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + for (int i = 0; i < max_codepoint + 1; i++) + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; +} + +void ImFont::SetFallbackChar(ImWchar c) +{ + FallbackChar = c; + BuildLookupTable(); +} + +void ImFont::GrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (unsigned short)-1); +} + +void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX + + if (ConfigData->PixelSnapH) + glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. + int index_size = IndexLookup.Size; + + if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists + return; + if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op + return; + + GrowIndex(dst + 1); + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +{ + if (c < IndexLookup.Size) + { + const unsigned short i = IndexLookup[c]; + if (i != (unsigned short)-1) + return &Glyphs.Data[i]; + } + return FallbackGlyph; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ + // Simple word-wrapping for English, not full-featured. Please submit failing cases! + // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + + // For references, possible wrap point marked with ^ + // "aaa bbb, ccc,ddd. eee fff. ggg!" + // ^ ^ ^ ^ ^__ ^ ^ + + // List of hardcoded separators: .,;!?'" + + // Skip extra blanks after a line returns (that includes not counting them in width computation) + // e.g. "Hello world" --> "Hello" "World" + + // Cut words that cannot possibly fit within one line. + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + float line_width = 0.0f; + float word_width = 0.0f; + float blank_width = 0.0f; + wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + + const char* word_end = text; + const char* prev_word_end = NULL; + bool inside_word = true; + + const char* s = text; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + const char* next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) + break; + + if (c < 32) + { + if (c == '\n') + { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') + { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); + if (ImCharIsSpace(c)) + { + if (inside_word) + { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } + else + { + word_width += char_width; + if (inside_word) + { + word_end = next_s; + } + else + { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width >= wrap_width) + { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + s = prev_word_end ? prev_word_end : word_end; + break; + } + + s = next_s; + } + + return s; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + const char* s = text_begin; + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + const char* prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; + if (line_width + char_width >= max_width) + { + s = prev_s; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const +{ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. + return; + if (const ImFontGlyph* glyph = FindGlyph(c)) + { + float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + draw_list->PrimReserve(6, 4); + draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + } +} + +void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + float x = pos.x; + float y = pos.y; + if (y > clip_rect.w) + return; + + const float scale = size / FontSize; + const float line_height = FontSize * scale; + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + // Skip non-visible lines + const char* s = text_begin; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int vtx_count_max = (int)(text_end - s) * 4; + const int idx_count_max = (int)(text_end - s) * 6; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + x = pos.x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + x = pos.x; + y += line_height; + + if (y > clip_rect.w) + break; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + continue; + } + if (c == '\r') + continue; + } + + float char_width = 0.0f; + if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) + { + char_width = glyph->AdvanceX * scale; + + // Arbitrarily assume that both space and tabs are empty glyphs as an optimization + if (c != ' ' && c != '\t') + { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) + { + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip) + { + if (x1 < clip_rect.x) + { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) + { + x += char_width; + continue; + } + } + + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); + vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; + vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + } + } + } + } + + x += char_width; + } + + // Give back unused vertices + draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); + draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; +} + +//----------------------------------------------------------------------------- +// Internals Drawing Helpers +//----------------------------------------------------------------------------- + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return acosf(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + +//----------------------------------------------------------------------------- +// DEFAULT FONT DATA +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array. +// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- + +static unsigned int stb_decompress_length(unsigned char *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +static unsigned char *stb__barrier, *stb__barrier2, *stb__barrier3, *stb__barrier4; +static unsigned char *stb__dout; +static void stb__match(unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(unsigned char *data, unsigned int length) +{ + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static unsigned char *stb_decompress_token(unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} + +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length) +{ + unsigned int olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier2 = i; + stb__barrier3 = i+length; + stb__barrier = output + olen; + stb__barrier4 = output; + i += 16; + + stb__dout = output; + for (;;) { + unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using binary_to_compressed_c.cpp +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980+1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char* GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} diff --git a/apps/bench_shared/imgui/imgui_impl_glfw_gl2.cpp b/apps/bench_shared/imgui/imgui_impl_glfw_gl2.cpp new file mode 100644 index 00000000..381ff13a --- /dev/null +++ b/apps/bench_shared/imgui/imgui_impl_glfw_gl2.cpp @@ -0,0 +1,355 @@ +// ImGui GLFW binding with OpenGL (legacy, fixed pipeline) +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in the opengl3_example/ folder** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-20: Inputs: Renamed GLFW callbacks exposed in .h to not include GL2 in their name. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL2_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-09: Misc: Renamed imgui_impl_glfw.* to imgui_impl_glfw_gl2.*. +// 2017-09-01: OpenGL: Save and restore current polygon mode. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. +// 2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal). +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. + +#include "imgui.h" +#include "imgui_impl_glfw_gl2.h" + +// GLFW +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WGL +#include +#endif + +// GLFW data +static GLFWwindow* g_Window = NULL; +static double g_Time = 0.0f; +static bool g_MouseJustPressed[3] = { false, false, false }; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_Count_] = { 0 }; + +// OpenGL data +static GLuint g_FontTexture = 0; + +// OpenGL2 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplGlfwGL2_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT | GL_TEXTURE_BIT); // DAR Was missing GL_TEXTURE_BIT. Need to store the glTexEnv setting as well. + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // DAR IMGUI uses GL_MODULATE when not using GLSL shaders. + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + + // Restore modified state + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplGlfwGL2_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfwGL2_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) +{ + if (action == GLFW_PRESS && button >= 0 && button < 3) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + (void)mods; // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) +{ + ImGuiIO& io = ImGui::GetIO(); + if (c > 0 && c < 0x10000) + io.AddInputCharacter((unsigned short)c); +} + +bool ImGui_ImplGlfwGL2_CreateDeviceObjects() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplGlfwGL2_InvalidateDeviceObjects() +{ + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +static void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) +{ + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); +} + +bool ImGui_ImplGlfwGL2_Init(GLFWwindow* window, bool install_callbacks) +{ + g_Window = window; + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.SetClipboardTextFn = ImGui_ImplGlfwGL2_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfwGL2_GetClipboardText; + io.ClipboardUserData = g_Window; +#ifdef _WIN32 + io.ImeWindowHandle = glfwGetWin32Window(g_Window); +#endif + + // Load cursors + // FIXME: GLFW doesn't expose suitable cursors for ResizeAll, ResizeNESW, ResizeNWSE. We revert to arrow cursor for those. + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + + if (install_callbacks) + ImGui_ImplGlfw_InstallCallbacks(window); + + return true; +} + +void ImGui_ImplGlfwGL2_Shutdown() +{ + // Destroy GLFW mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_Count_; cursor_n++) + glfwDestroyCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + + // Destroy OpenGL objects + ImGui_ImplGlfwGL2_InvalidateDeviceObjects(); +} + +void ImGui_ImplGlfwGL2_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplGlfwGL2_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) + { + if (io.WantMoveMouse) + { + glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y); // Set mouse position if requested by io.WantMoveMouse flag (used when io.NavMovesTrue is enabled by user and using directional navigation) + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } + else + { + io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX); + } + + for (int i = 0; i < 3; i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update OS/hardware mouse cursor if imgui isn't drawing a software cursor + ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || cursor == ImGuiMouseCursor_None) + { + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + glfwSetCursor(g_Window, g_MouseCursors[cursor] ? g_MouseCursors[cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. + ImGui::NewFrame(); +} diff --git a/apps/bench_shared/imgui/imgui_impl_glfw_gl2.h b/apps/bench_shared/imgui/imgui_impl_glfw_gl2.h new file mode 100644 index 00000000..f7b5c22c --- /dev/null +++ b/apps/bench_shared/imgui/imgui_impl_glfw_gl2.h @@ -0,0 +1,32 @@ +// ImGui GLFW binding with OpenGL (legacy, fixed pipeline) +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in the opengl3_example/ folder** +// See imgui_impl_glfw_gl2.cpp for details. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct GLFWwindow; + +IMGUI_API bool ImGui_ImplGlfwGL2_Init(GLFWwindow* window, bool install_callbacks); +IMGUI_API void ImGui_ImplGlfwGL2_Shutdown(); +IMGUI_API void ImGui_ImplGlfwGL2_NewFrame(); +IMGUI_API void ImGui_ImplGlfwGL2_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplGlfwGL2_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplGlfwGL2_CreateDeviceObjects(); + +// GLFW callbacks (registered by default to GLFW if you enable 'install_callbacks' during initialization) +// Provided here if you want to chain callbacks yourself. You may also handle inputs yourself and use those as a reference. +IMGUI_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/apps/bench_shared/imgui/imgui_impl_glfw_gl3.cpp b/apps/bench_shared/imgui/imgui_impl_glfw_gl3.cpp new file mode 100644 index 00000000..6321e3cf --- /dev/null +++ b/apps/bench_shared/imgui/imgui_impl_glfw_gl3.cpp @@ -0,0 +1,501 @@ +// ImGui GLFW binding with OpenGL3 + shaders +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (GL3W is a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Gamepad navigation mapping. Enable with 'io.NavFlags |= ImGuiNavFlags_EnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-20: Inputs: Renamed GLFW callbacks exposed in .h to not include GL3 in their name. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Added gamepad support if ImGuiNavFlags_EnableGamepad is set. +// 2018-01-25: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. (Also changed GL context from 3.3 to 3.2 in example's main.cpp) +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2017-05-01: OpenGL: Fixed save and restore of current blend function state. +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-04-30: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. + +#include "imgui.h" +#include "imgui_impl_glfw_gl3.h" + +// GL3W/GLFW +//#include // This example is using gl3w to access OpenGL functions (because it is small). You may use glew/glad/glLoadGen/etc. whatever already works for you. +// DAR HACK Yes, using GLEW here. +#include +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WGL +#include +#endif + +// GLFW data +static GLFWwindow* g_Window = NULL; +static double g_Time = 0.0f; +static bool g_MouseJustPressed[3] = { false, false, false }; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_Count_] = { 0 }; + +// OpenGL3 data +static GLuint g_FontTexture = 0; +static int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; +static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0; +static unsigned int g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0; + +// OpenGL3 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplGlfwGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); + GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); + GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); + + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + const float ortho_projection[4][4] = + { + { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + {-1.0f, 1.0f, 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + glBindVertexArray(g_VaoHandle); + glBindSampler(0, 0); // Rely on combined texture/sampler state. + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawIdx* idx_buffer_offset = 0; + + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + } + idx_buffer_offset += pcmd->ElemCount; + } + } + + // Restore modified GL state + glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindSampler(0, last_sampler); + glActiveTexture(last_active_texture); + glBindVertexArray(last_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplGlfwGL3_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfwGL3_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) +{ + if (action == GLFW_PRESS && button >= 0 && button < 3) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + (void)mods; // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) +{ + ImGuiIO& io = ImGui::GetIO(); + if (c > 0 && c < 0x10000) + io.AddInputCharacter((unsigned short)c); +} + +bool ImGui_ImplGlfwGL3_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +bool ImGui_ImplGlfwGL3_CreateDeviceObjects() +{ + // Backup GL state + GLint last_texture, last_array_buffer, last_vertex_array; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + + const GLchar *vertex_shader = + "#version 150\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader = + "#version 150\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" + "}\n"; + + g_ShaderHandle = glCreateProgram(); + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_VertHandle, 1, &vertex_shader, 0); + glShaderSource(g_FragHandle, 1, &fragment_shader, 0); + glCompileShader(g_VertHandle); + glCompileShader(g_FragHandle); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + glGenBuffers(1, &g_VboHandle); + glGenBuffers(1, &g_ElementsHandle); + + glGenVertexArrays(1, &g_VaoHandle); + glBindVertexArray(g_VaoHandle); + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glEnableVertexAttribArray(g_AttribLocationPosition); + glEnableVertexAttribArray(g_AttribLocationUV); + glEnableVertexAttribArray(g_AttribLocationColor); + + glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); + + ImGui_ImplGlfwGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindVertexArray(last_vertex_array); + + return true; +} + +void ImGui_ImplGlfwGL3_InvalidateDeviceObjects() +{ + if (g_VaoHandle) glDeleteVertexArrays(1, &g_VaoHandle); + if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle); + if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle); + g_VaoHandle = g_VboHandle = g_ElementsHandle = 0; + + if (g_ShaderHandle && g_VertHandle) glDetachShader(g_ShaderHandle, g_VertHandle); + if (g_VertHandle) glDeleteShader(g_VertHandle); + g_VertHandle = 0; + + if (g_ShaderHandle && g_FragHandle) glDetachShader(g_ShaderHandle, g_FragHandle); + if (g_FragHandle) glDeleteShader(g_FragHandle); + g_FragHandle = 0; + + if (g_ShaderHandle) glDeleteProgram(g_ShaderHandle); + g_ShaderHandle = 0; + + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +static void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) +{ + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); +} + +bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks) +{ + g_Window = window; + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.SetClipboardTextFn = ImGui_ImplGlfwGL3_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfwGL3_GetClipboardText; + io.ClipboardUserData = g_Window; +#ifdef _WIN32 + io.ImeWindowHandle = glfwGetWin32Window(g_Window); +#endif + + // Load cursors + // FIXME: GLFW doesn't expose suitable cursors for ResizeAll, ResizeNESW, ResizeNWSE. We revert to arrow cursor for those. + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + + if (install_callbacks) + ImGui_ImplGlfw_InstallCallbacks(window); + + return true; +} + +void ImGui_ImplGlfwGL3_Shutdown() +{ + // Destroy GLFW mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_Count_; cursor_n++) + glfwDestroyCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + + // Destroy OpenGL objects + ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); +} + +void ImGui_ImplGlfwGL3_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplGlfwGL3_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) + { + if (io.WantMoveMouse) + { + glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y); // Set mouse position if requested by io.WantMoveMouse flag (used when io.NavMovesTrue is enabled by user and using directional navigation) + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } + else + { + io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX); + } + + for (int i = 0; i < 3; i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update OS/hardware mouse cursor if imgui isn't drawing a software cursor + ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || cursor == ImGuiMouseCursor_None) + { + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + glfwSetCursor(g_Window, g_MouseCursors[cursor] ? g_MouseCursors[cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + // Gamepad navigation mapping [BETA] + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if (io.NavFlags & ImGuiNavFlags_EnableGamepad) + { + // Update gamepad inputs + #define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; } + #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; } + int axes_count = 0, buttons_count = 0; + const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + MAP_BUTTON(ImGuiNavInput_Activate, 0); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, 1); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, 2); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, 3); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, 13); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, 11); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, 10); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, 12); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, 5); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, 0, -0.3f, -0.9f); + MAP_ANALOG(ImGuiNavInput_LStickRight,0, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickUp, 1, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickDown, 1, -0.3f, -0.9f); + #undef MAP_BUTTON + #undef MAP_ANALOG + } + + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. + ImGui::NewFrame(); +} diff --git a/apps/bench_shared/imgui/imgui_impl_glfw_gl3.h b/apps/bench_shared/imgui/imgui_impl_glfw_gl3.h new file mode 100644 index 00000000..71ea4122 --- /dev/null +++ b/apps/bench_shared/imgui/imgui_impl_glfw_gl3.h @@ -0,0 +1,31 @@ +// ImGui GLFW binding with OpenGL3 + shaders +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (GL3W is a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Gamepad navigation mapping. Enable with 'io.NavFlags |= ImGuiNavFlags_EnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct GLFWwindow; + +IMGUI_API bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks); +IMGUI_API void ImGui_ImplGlfwGL3_Shutdown(); +IMGUI_API void ImGui_ImplGlfwGL3_NewFrame(); +IMGUI_API void ImGui_ImplGlfwGL3_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplGlfwGL3_CreateDeviceObjects(); + +// GLFW callbacks (installed by default if you enable 'install_callbacks' during initialization) +// Provided here if you want to chain callbacks. +// You can also handle inputs yourself and use those as a reference. +IMGUI_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/apps/bench_shared/imgui/imgui_internal.h b/apps/bench_shared/imgui/imgui_internal.h new file mode 100644 index 00000000..77d621bb --- /dev/null +++ b/apps/bench_shared/imgui/imgui_internal.h @@ -0,0 +1,1156 @@ +// dear imgui, v1.60 WIP +// (internals) + +// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! +// Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +#pragma once + +#ifndef IMGUI_VERSION +#error Must include imgui.h before imgui_internal.h +#endif + +#include // FILE* +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf +#include // INT_MIN, INT_MAX + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +//----------------------------------------------------------------------------- +// Forward Declarations +//----------------------------------------------------------------------------- + +struct ImRect; +struct ImGuiColMod; +struct ImGuiStyleMod; +struct ImGuiGroupData; +struct ImGuiMenuColumns; +struct ImGuiDrawContext; +struct ImGuiTextEditState; +struct ImGuiPopupRef; +struct ImGuiWindow; +struct ImGuiWindowSettings; + +typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_ +typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_ +typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_ +typedef int ImGuiItemStatusFlags; // flags: storage for DC.LastItemXXX // enum ImGuiItemStatusFlags_ +typedef int ImGuiNavHighlightFlags; // flags: for RenderNavHighlight() // enum ImGuiNavHighlightFlags_ +typedef int ImGuiNavDirSourceFlags; // flags: for GetNavInputAmount2d() // enum ImGuiNavDirSourceFlags_ +typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_ +typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_ + +//------------------------------------------------------------------------- +// STB libraries +//------------------------------------------------------------------------- + +namespace ImGuiStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiTextEditState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f +#include "stb_textedit.h" + +} // namespace ImGuiStb + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +#ifndef GImGui +extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +#define IM_PI 3.14159265358979323846f + +// Helpers: UTF-8 <> wchar +IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string as UTF-8 code-points + +// Helpers: Misc +IMGUI_API ImU32 ImHash(const void* data, int data_size, ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size = NULL, int padding_bytes = 0); +IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); +static inline bool ImCharIsSpace(int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } + +// Helpers: Geometry +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); + +// Helpers: String +IMGUI_API int ImStricmp(const char* str1, const char* str2); +IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); +IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); +IMGUI_API char* ImStrdup(const char* str); +IMGUI_API char* ImStrchrRange(const char* str_begin, const char* str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar* str); +IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line +IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); +IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); + +// Helpers: Math +// We are keeping those not leaking to the user by default, in the case the user has implicit cast operators between ImVec2 and its own types (when IM_VEC2_CLASS_EXTRA is defined) +#ifdef IMGUI_DEFINE_MATH_OPERATORS +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x/rhs, lhs.y/rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x*rhs.x, lhs.y*rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x/rhs.x, lhs.y/rhs.y); } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); } +#endif + +static inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } +static inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } +static inline float ImMin(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; } +static inline float ImMax(float lhs, float rhs) { return lhs >= rhs ? lhs : rhs; } +static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +static inline int ImClamp(int v, int mn, int mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline ImVec2 ImClamp(const ImVec2& f, const ImVec2& mn, ImVec2 mx) { return ImVec2(ImClamp(f.x,mn.x,mx.x), ImClamp(f.y,mn.y,mx.y)); } +static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline void ImSwap(int& a, int& b) { int tmp = a; a = b; b = tmp; } +static inline void ImSwap(float& a, float& b) { float tmp = a; a = b; b = tmp; } +static inline int ImLerp(int a, int b, float t) { return (int)(a + (b - a) * t); } +static inline float ImLerp(float a, float b, float t) { return a + (b - a) * t; } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } +static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / sqrtf(d); return fail_value; } +static inline float ImFloor(float f) { return (float)(int)f; } +static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } +static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + +// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. +// Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. +struct ImNewPlacementDummy {}; +inline void* operator new(size_t, ImNewPlacementDummy, void* ptr) { return ptr; } +inline void operator delete(void*, ImNewPlacementDummy, void*) {} // This is only required so we can use the symetrical new() +#define IM_PLACEMENT_NEW(_PTR) new(ImNewPlacementDummy(), _PTR) +#define IM_NEW(_TYPE) new(ImNewPlacementDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +template void IM_DELETE(T*& p) { if (p) { p->~T(); ImGui::MemFree(p); p = NULL; } } + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +enum ImGuiButtonFlags_ +{ + ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat + ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] + ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) + ImGuiButtonFlags_FlattenChildren = 1 << 5, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowItemOverlap = 1 << 6, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() + ImGuiButtonFlags_DontClosePopups = 1 << 7, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 8, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveID = 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12, // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) + ImGuiButtonFlags_NoNavFocus = 1 << 13 // don't override navigation focus when activated +}; + +enum ImGuiSliderFlags_ +{ + ImGuiSliderFlags_Vertical = 1 << 0 +}; + +enum ImGuiColumnsFlags_ +{ + // Default: 0 + ImGuiColumnsFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiColumnsFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + ImGuiColumnsFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns + ImGuiColumnsFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window + ImGuiColumnsFlags_GrowParentContentsSize= 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. +}; + +enum ImGuiSelectableFlagsPrivate_ +{ + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_Menu = 1 << 3, // -> PressedOnClick + ImGuiSelectableFlags_MenuItem = 1 << 4, // -> PressedOnRelease + ImGuiSelectableFlags_Disabled = 1 << 5, + ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 6 +}; + +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1 +}; + +// Storage for LastItem data +enum ImGuiItemStatusFlags_ +{ + ImGuiItemStatusFlags_HoveredRect = 1 << 0, + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1 +}; + +// FIXME: this is in development, not exposed/functional as a generic feature yet. +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Vertical, + ImGuiLayoutType_Horizontal +}; + +enum ImGuiAxis +{ + ImGuiAxis_None = -1, + ImGuiAxis_X = 0, + ImGuiAxis_Y = 1 +}; + +enum ImGuiPlotType +{ + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram +}; + +enum ImGuiDataType +{ + ImGuiDataType_Int, + ImGuiDataType_Float, + ImGuiDataType_Float2 +}; + +enum ImGuiDir +{ + ImGuiDir_None = -1, + ImGuiDir_Left = 0, + ImGuiDir_Right = 1, + ImGuiDir_Up = 2, + ImGuiDir_Down = 3, + ImGuiDir_Count_ +}; + +enum ImGuiInputSource +{ + ImGuiInputSource_None = 0, + ImGuiInputSource_Mouse, + ImGuiInputSource_Nav, + ImGuiInputSource_NavKeyboard, // Only used occasionally for storage, not tested/handled by most code + ImGuiInputSource_NavGamepad, // " + ImGuiInputSource_Count_, +}; + +// FIXME-NAV: Clarify/expose various repeat delay/rate +enum ImGuiInputReadMode +{ + ImGuiInputReadMode_Down, + ImGuiInputReadMode_Pressed, + ImGuiInputReadMode_Released, + ImGuiInputReadMode_Repeat, + ImGuiInputReadMode_RepeatSlow, + ImGuiInputReadMode_RepeatFast +}; + +enum ImGuiNavHighlightFlags_ +{ + ImGuiNavHighlightFlags_TypeDefault = 1 << 0, + ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, + ImGuiNavHighlightFlags_NoRounding = 1 << 3 +}; + +enum ImGuiNavDirSourceFlags_ +{ + ImGuiNavDirSourceFlags_Keyboard = 1 << 0, + ImGuiNavDirSourceFlags_PadDPad = 1 << 1, + ImGuiNavDirSourceFlags_PadLStick = 1 << 2 +}; + +enum ImGuiNavForward +{ + ImGuiNavForward_None, + ImGuiNavForward_ForwardQueued, + ImGuiNavForward_ForwardActive +}; + +// 2D axis aligned bounding-box +// NB: we can't rely on ImVec2 math operators being available here +struct IMGUI_API ImRect +{ + ImVec2 Min; // Upper-left + ImVec2 Max; // Lower-right + + ImRect() : Min(FLT_MAX,FLT_MAX), Max(-FLT_MAX,-FLT_MAX) {} + ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + + ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } + ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } + float GetWidth() const { return Max.x - Min.x; } + float GetHeight() const { return Max.y - Min.y; } + ImVec2 GetTL() const { return Min; } // Top-left + ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right + ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left + ImVec2 GetBR() const { return Max; } // Bottom-right + bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } + bool Contains(const ImRect& r) const { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; } + bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } + void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } + void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } + void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } + void Translate(const ImVec2& v) { Min.x += v.x; Min.y += v.y; Max.x += v.x; Max.y += v.y; } + void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. + void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. + void Floor() { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; } + void FixInverted() { if (Min.x > Max.x) ImSwap(Min.x, Max.x); if (Min.y > Max.y) ImSwap(Min.y, Max.y); } + bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } + bool IsFinite() const { return Min.x != FLT_MAX; } +}; + +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +// Stacked data for BeginGroup()/EndGroup() +struct ImGuiGroupData +{ + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + float BackupIndentX; + float BackupGroupOffsetX; + float BackupCurrentLineHeight; + float BackupCurrentLineTextBaseOffset; + float BackupLogLinePosY; + bool BackupActiveIdIsAlive; + bool AdvanceCursor; +}; + +// Simple column measurement currently used for MenuItem() only. This is very short-sighted/throw-away code and NOT a generic helper. +struct IMGUI_API ImGuiMenuColumns +{ + int Count; + float Spacing; + float Width, NextWidth; + float Pos[4], NextWidths[4]; + + ImGuiMenuColumns(); + void Update(int count, float spacing, bool clear); + float DeclColumns(float w0, float w1, float w2); + float CalcExtraSpace(float avail_w); +}; + +// Internal state of the currently focused/edited text input box +struct IMGUI_API ImGuiTextEditState +{ + ImGuiID Id; // widget id owning the text state + ImVector Text; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempTextBuffer; + int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. + int BufSizeA; // end-user buffer size + float ScrollX; + ImGuiStb::STB_TexteditState StbState; + float CursorAnim; + bool CursorFollow; + bool SelectedAllMouseLock; + + ImGuiTextEditState() { memset(this, 0, sizeof(*this)); } + void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); } + bool HasSelection() const { return StbState.select_start != StbState.select_end; } + void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } + void SelectAll() { StbState.select_start = 0; StbState.cursor = StbState.select_end = CurLenW; StbState.has_preferred_x = false; } + void OnKeyPressed(int key); +}; + +// Data saved in imgui.ini file +struct ImGuiWindowSettings +{ + char* Name; + ImGuiID Id; + ImVec2 Pos; + ImVec2 Size; + bool Collapsed; + + ImGuiWindowSettings() { Name = NULL; Id = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } +}; + +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) + void* (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name); + void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); + void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); + void* UserData; + + ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } +}; + +// Storage for current popup stack +struct ImGuiPopupRef +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* ParentWindow; // Set on OpenPopup() + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differenciate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup +}; + +struct ImGuiColumnData +{ + float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) + float OffsetNormBeforeResize; + ImGuiColumnsFlags Flags; // Not exposed + ImRect ClipRect; + + ImGuiColumnData() { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = 0; } +}; + +struct ImGuiColumnsSet +{ + ImGuiID ID; + ImGuiColumnsFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float MinX, MaxX; + float StartPosY; + float StartMaxPosX; // Backup of CursorMaxPos + float CellMinY, CellMaxY; + ImVector Columns; + + ImGuiColumnsSet() { Clear(); } + void Clear() + { + ID = 0; + Flags = 0; + IsFirstFrame = false; + IsBeingResized = false; + Current = 0; + Count = 1; + MinX = MaxX = 0.0f; + StartPosY = 0.0f; + StartMaxPosX = 0.0f; + CellMinY = CellMaxY = 0.0f; + Columns.clear(); + } +}; + +struct IMGUI_API ImDrawListSharedData +{ + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + ImFont* Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + + // Const data + // FIXME: Bake rounded corners fill/borders in atlas + ImVec2 CircleVtx12[12]; + + ImDrawListSharedData(); +}; + +struct ImDrawDataBuilder +{ + ImVector Layers[2]; // Global layers for: regular, tooltip + + void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); } + void ClearFreeMemory() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); } + IMGUI_API void FlattenIntoSingleLayer(); +}; + +struct ImGuiNavMoveResult +{ + ImGuiID ID; // Best candidate + ImGuiID ParentID; // Best candidate window->IDStack.back() - to compare context + ImGuiWindow* Window; // Best candidate window + float DistBox; // Best candidate box distance to current NavId + float DistCenter; // Best candidate center distance to current NavId + float DistAxial; + ImRect RectRel; // Best candidate bounding box in window relative space + + ImGuiNavMoveResult() { Clear(); } + void Clear() { ID = ParentID = 0; Window = NULL; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } +}; + +// Storage for SetNexWindow** functions +struct ImGuiNextWindowData +{ + ImGuiCond PosCond; + ImGuiCond SizeCond; + ImGuiCond ContentSizeCond; + ImGuiCond CollapsedCond; + ImGuiCond SizeConstraintCond; + ImGuiCond FocusCond; + ImGuiCond BgAlphaCond; + ImVec2 PosVal; + ImVec2 PosPivotVal; + ImVec2 SizeVal; + ImVec2 ContentSizeVal; + bool CollapsedVal; + ImRect SizeConstraintRect; // Valid if 'SetNextWindowSizeConstraint' is true + ImGuiSizeCallback SizeCallback; + void* SizeCallbackUserData; + float BgAlphaVal; + + ImGuiNextWindowData() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f); + ContentSizeVal = ImVec2(0.0f, 0.0f); + CollapsedVal = false; + SizeConstraintRect = ImRect(); + SizeCallback = NULL; + SizeCallbackUserData = NULL; + BgAlphaVal = FLT_MAX; + } + + void Clear() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + } +}; + +// Main state for ImGui +struct ImGuiContext +{ + bool Initialized; + bool FontAtlasOwnedByContext; // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. + ImGuiIO IO; + ImGuiStyle Style; + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + ImDrawListSharedData DrawListSharedData; + + float Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + ImVector Windows; + ImVector WindowsSortBuffer; + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; + int WindowsActiveCount; + ImGuiWindow* CurrentWindow; // Being drawn into + ImGuiWindow* HoveredWindow; // Will catch mouse inputs + ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) + ImGuiID HoveredId; // Hovered widget + bool HoveredIdAllowOverlap; + ImGuiID HoveredIdPreviousFrame; + float HoveredIdTimer; + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdPreviousFrame; + float ActiveIdTimer; + bool ActiveIdIsAlive; // Active widget has been seen this frame + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and move away from it) + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow* ActiveIdWindow; + ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actually window that is moved is generally MovingWindow->RootWindow. + ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() + ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() + ImVector FontStack; // Stack for PushFont()/PopFont() + ImVector OpenPopupStack; // Which popups are open (persistent) + ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + bool NextTreeNodeOpenVal; // Storage for SetNextTreeNode** functions + ImGuiCond NextTreeNodeOpenCond; + + // Navigation data (for gamepad/keyboard) + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' + ImGuiID NavId; // Focused item for navigation + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 + ImGuiID NavJustTabbedId; // Just tabbed to this id. + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest) + ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring. + int NavScoringCount; // Metrics for debugging + ImGuiWindow* NavWindowingTarget; // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) this window is temporarily displayed front-most. + float NavWindowingHighlightTimer; + float NavWindowingHighlightAlpha; + bool NavWindowingToggleLayer; + ImGuiInputSource NavWindowingInputSource; // Gamepad or keyboard mode + int NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. + int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRefRectRel is valid + bool NavMousePosDirty; // When set we will update mouse position if (NavFlags & ImGuiNavFlags_MoveMouse) if set (NB: this not enabled by default) + bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (nb: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) + bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest + bool NavInitRequest; // Init request for appearing window to select first item + bool NavInitRequestFromMove; + ImGuiID NavInitResultId; + ImRect NavInitResultRectRel; + bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items + bool NavMoveRequest; // Move request for this frame + ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) + ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request + ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow + ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using the NavFlattened flag) + + // Render + ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user + ImDrawDataBuilder DrawDataBuilder; + float ModalWindowDarkeningRatio; + ImDrawList OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays + ImGuiMouseCursor MouseCursor; + + // Drag and Drop + bool DragDropActive; + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; + ImGuiID DragDropTargetId; + float DragDropAcceptIdCurrRectSurface; + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly + unsigned char DragDropPayloadBufLocal[8]; + + // Widget state + ImGuiTextEditState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + ImVec4 ColorPickerRef; + float DragCurrentValue; // Currently dragged value, always float, not rounded by end-user precision settings + ImVec2 DragLastMouseDelta; + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + float DragSpeedScaleSlow; + float DragSpeedScaleFast; + ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + int TooltipOverrideCount; + ImVector PrivateClipboard; // If no custom clipboard handler is defined + ImVec2 OsImePosRequest, OsImePosSet; // Cursor position request & last passed to the OS Input Method Editor + + // Settings + bool SettingsLoaded; + float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero + ImVector SettingsWindows; // .ini settings for ImGuiWindow + ImVector SettingsHandlers; // List of .ini settings handlers + + // Logging + bool LogEnabled; + FILE* LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer* LogClipboard; // Else log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + int LogStartDepth; + int LogAutoExpandMaxDepth; + + // Misc + float FramerateSecPerFrame[120]; // calculate estimate of framerate for user + int FramerateSecPerFrameIdx; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // explicit capture via CaptureInputs() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; + char TempBuffer[1024*3+1]; // temporary text buffer + + ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL) + { + Initialized = false; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredRootWindow = NULL; + HoveredId = 0; + HoveredIdAllowOverlap = false; + HoveredIdPreviousFrame = 0; + HoveredIdTimer = 0.0f; + ActiveId = 0; + ActiveIdPreviousFrame = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsAlive = false; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdAllowNavDirFlags = 0; + ActiveIdClickOffset = ImVec2(-1,-1); + ActiveIdWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + MovingWindow = NULL; + NextTreeNodeOpenVal = false; + NextTreeNodeOpenCond = 0; + + NavWindow = NULL; + NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; + NavScoringRectScreen = ImRect(); + NavScoringCount = 0; + NavWindowingTarget = NULL; + NavWindowingHighlightTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + NavWindowingInputSource = ImGuiInputSource_None; + NavLayer = 0; + NavIdTabCounter = INT_MAX; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavInitResultId = 0; + NavMoveFromClampedRefRect = false; + NavMoveRequest = false; + NavMoveRequestForward = ImGuiNavForward_None; + NavMoveDir = NavMoveDirLast = ImGuiDir_None; + + ModalWindowDarkeningRatio = 0.0f; + OverlayDrawList._Data = &DrawListSharedData; + OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging + MouseCursor = ImGuiMouseCursor_Arrow; + + DragDropActive = false; + DragDropSourceFlags = 0; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ScalarAsInputTextId = 0; + ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + DragCurrentValue = 0.0f; + DragLastMouseDelta = ImVec2(0.0f, 0.0f); + DragSpeedDefaultRatio = 1.0f / 100.0f; + DragSpeedScaleSlow = 1.0f / 100.0f; + DragSpeedScaleFast = 10.0f; + ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); + TooltipOverrideCount = 0; + OsImePosRequest = OsImePosSet = ImVec2(-1.0f, -1.0f); + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + + LogEnabled = false; + LogFile = NULL; + LogClipboard = NULL; + LogStartDepth = 0; + LogAutoExpandMaxDepth = 2; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempBuffer, 0, sizeof(TempBuffer)); + } +}; + +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // FIXME-WIP: Disable interactions but doesn't affect visuals. Should be: grey out and disable interactions with widgets that affect data + view widgets (WIP) + ImGuiItemFlags_NoNav = 1 << 3, // false + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus +}; + +// Transient per-window data, reset at the beginning of the frame +// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiDrawContext is quite tenuous and could be reconsidered. +struct IMGUI_API ImGuiDrawContext +{ + ImVec2 CursorPos; + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; + ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame + float CurrentLineHeight; + float CurrentLineTextBaseOffset; + float PrevLineHeight; + float PrevLineTextBaseOffset; + float LogLinePosY; + int TreeDepth; + ImU32 TreeDepthMayCloseOnPop; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31 + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; // Interaction rect + ImRect LastItemDisplayRect; // End-user display rect (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) + bool NavHideHighlightOneFrame; + bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) + int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) + int NavLayerCurrentMask; // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping. + int NavLayerActiveMask; // Which layer have been written to (result from previous frame) + int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame) + bool MenuBarAppending; // FIXME: Remove this + float MenuBarOffsetX; + ImVector ChildWindows; + ImGuiStorage* StateStorage; + ImGuiLayoutType LayoutType; + ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] + float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window + float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] + ImVectorItemFlagsStack; + ImVector ItemWidthStack; + ImVector TextWrapPosStack; + ImVectorGroupStack; + int StackSizesBackup[6]; // Store size of various stacks for asserting + + float IndentX; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + float GroupOffsetX; + float ColumnsOffsetX; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + ImGuiColumnsSet* ColumnsSet; // Current columns set + + ImGuiDrawContext() + { + CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); + CurrentLineHeight = PrevLineHeight = 0.0f; + CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; + LogLinePosY = -1.0f; + TreeDepth = 0; + TreeDepthMayCloseOnPop = 0x00; + LastItemId = 0; + LastItemStatusFlags = 0; + LastItemRect = LastItemDisplayRect = ImRect(); + NavHideHighlightOneFrame = false; + NavHasScroll = false; + NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; + NavLayerCurrent = 0; + NavLayerCurrentMask = 1 << 0; + MenuBarAppending = false; + MenuBarOffsetX = 0.0f; + StateStorage = NULL; + LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical; + ItemWidth = 0.0f; + ItemFlags = ImGuiItemFlags_Default_; + TextWrapPos = -1.0f; + memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); + + IndentX = 0.0f; + GroupOffsetX = 0.0f; + ColumnsOffsetX = 0.0f; + ColumnsSet = NULL; + } +}; + +// Windows data +struct IMGUI_API ImGuiWindow +{ + char* Name; + ImGuiID ID; // == ImHash(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImVec2 PosFloat; + ImVec2 Pos; // Position rounded-up to nearest pixel + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars. + ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc. + ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() + ImRect ContentsRegionRect; // Maximum visible content position in window coordinates. ~~ (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis + ImVec2 WindowPadding; // Window padding at the time of begin. + float WindowRounding; // Window rounding at the time of begin. + float WindowBorderSize; // Window border size at the time of begin. + ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID ChildId; // Id of corresponding item in parent window (for child windows) + ImVec2 Scroll; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + bool ScrollbarX, ScrollbarY; + ImVec2 ScrollbarSizes; + bool Active; // Set to true on Begin(), unless Collapsed + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool CollapseToggleWanted; + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool CloseButton; // Set when the window has a close button (p_open != NULL) + int BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. + int BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order related issues. + int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + int AutoFitFramesX, AutoFitFramesY; + bool AutoFitOnlyGrows; + int AutoFitChildAxises; + ImGuiDir AutoPosLastDirection; + int HiddenFrames; + ImGuiCond SetWindowPosAllowFlags; // store condition flags for next SetWindowPos() call. + ImGuiCond SetWindowSizeAllowFlags; // store condition flags for next SetWindowSize() call. + ImGuiCond SetWindowCollapsedAllowFlags; // store condition flags for next SetWindowCollapsed() call. + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + + ImGuiDrawContext DC; // Temporary per-window data, reset at the beginning of the frame + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack + ImRect ClipRect; // = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2. + ImRect WindowRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. + ImRect InnerRect; + int LastFrameActive; + float ItemWidthDefault; + ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // Scale multiplier per-window + ImDrawList* DrawList; + ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. + ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. + ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. + ImGuiWindow* RootWindowForTabbing; // Point to ourself or first ancestor which can be CTRL-Tabbed into. + ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. + + ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) + ImGuiID NavLastIds[2]; // Last known NavId for this window, per layer (0/1) + ImRect NavRectRel[2]; // Reference rectangle, in window relative space + + // Navigation / Focus + // FIXME-NAV: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext + int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() + int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through) + int FocusIdxAllRequestCurrent; // Item being requested for focus + int FocusIdxTabRequestCurrent; // Tab-able item being requested for focus + int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame) + int FocusIdxTabRequestNext; // " + +public: + ImGuiWindow(ImGuiContext* context, const char* name); + ~ImGuiWindow(); + + ImGuiID GetID(const char* str, const char* str_end = NULL); + ImGuiID GetID(const void* ptr); + ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); + ImGuiID GetIDFromRectangle(const ImRect& r_abs); + + // We don't use g.FontSize because the window may be != g.CurrentWidow. + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } + float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } + float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } + float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } +}; + +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiItemHoveredDataBackup +{ + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; + ImRect LastItemDisplayRect; + + ImGuiItemHoveredDataBackup() { Backup(); } + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; } + void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } +}; + +//----------------------------------------------------------------------------- +// Internal API +// No guarantee of forward compatibility here. +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) + // If this ever crash because g.CurrentWindow is NULL it means that either + // - ImGui::NewFrame() has never been called, which is illegal. + // - You are calling ImGui functions after ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } + inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByName(const char* name); + IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void BringWindowToFront(ImGuiWindow* window); + IMGUI_API void BringWindowToBack(ImGuiWindow* window); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); + IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); + + IMGUI_API void Initialize(ImGuiContext* context); + IMGUI_API void Shutdown(ImGuiContext* context); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); + IMGUI_API ImGuiID GetActiveID(); + IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void ClearActiveID(); + IMGUI_API void SetHoveredID(ImGuiID id); + IMGUI_API ImGuiID GetHoveredID(); + IMGUI_API void KeepAliveID(ImGuiID id); + + IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); + IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested + IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); + IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); + IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + + IMGUI_API void SetCurrentFont(ImFont* font); + + IMGUI_API void OpenPopupEx(ImGuiID id); + IMGUI_API void ClosePopup(ImGuiID id); + IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window); + IMGUI_API bool IsPopupOpen(ImGuiID id); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); + + IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); + IMGUI_API void ActivateItem(ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. + + IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode); + IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); + IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); + + IMGUI_API void Scrollbar(ImGuiLayoutType direction); + IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). not exposed because it is misleading what it doesn't have an effect on regular layout. + IMGUI_API bool SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f); + + IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API void ClearDragDrop(); + IMGUI_API bool IsDragDropPayloadBeingAccepted(); + + // FIXME-WIP: New Columns API + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index = -1); + + // NB: All position are in absolute pixels coordinates (never using window coordinates internally) + // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. + IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); + IMGUI_API void RenderTriangle(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); + IMGUI_API void RenderBullet(ImVec2 pos); + IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. + + IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); + IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0); + IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); + IMGUI_API bool ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags = 0); + + IMGUI_API bool SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format); + + IMGUI_API bool DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power); + IMGUI_API bool DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format); + + IMGUI_API bool InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision); + + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); + + IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging + IMGUI_API void TreePushRawID(ImGuiID id); + + IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size); + + IMGUI_API int ParseFormatPrecision(const char* fmt, int default_value); + IMGUI_API float RoundScalar(float value, int decimal_precision); + + // Shade functions + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x); + IMGUI_API void ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); + +} // namespace ImGui + +// ImFontAtlas internals +IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); +IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif diff --git a/apps/bench_shared/imgui/stb_rect_pack.h b/apps/bench_shared/imgui/stb_rect_pack.h new file mode 100644 index 00000000..2b07dcc8 --- /dev/null +++ b/apps/bench_shared/imgui/stb_rect_pack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v0.11 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// +// Version history: +// +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/apps/bench_shared/imgui/stb_textedit.h b/apps/bench_shared/imgui/stb_textedit.h new file mode 100644 index 00000000..4b731a0c --- /dev/null +++ b/apps/bench_shared/imgui/stb_textedit.h @@ -0,0 +1,1322 @@ +// [ImGui] this is a slightly modified version of stb_truetype.h 1.9. Those changes would need to be pushed into nothings/sb +// [ImGui] - fixed linestart handler when over last character of multi-line buffer + simplified existing code (#588, #815) +// [ImGui] - fixed a state corruption/crash bug in stb_text_redo and stb_textedit_discard_redo (#715) +// [ImGui] - fixed a crash bug in stb_textedit_discard_redo (#681) +// [ImGui] - fixed some minor warnings + +// stb_textedit.h - v1.9 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Todo: +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + short insert_length; + short delete_length; + short char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + short undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = stb_text_locate_coord(str, x, y); + if (state->select_start == state->select_end) + state->select_start = state->cursor; + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + i = 0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicity clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + STB_TEXTEDIT_CHARTYPE *text = (STB_TEXTEDIT_CHARTYPE *) ctext; + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // remove the undo since we didn't actually insert the characters + if (state->undostate.undo_point) + --state->undostate.undo_point; + return 0; +} + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicity clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str,state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // now find character position down a row + if (find.length) { + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + int start = find.first_char + find.length; + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // can only go up if there's a previous row + if (find.prev_first != find.first_char) { + // now find character position up a row + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + +// @TODO: +// STB_TEXTEDIT_K_PGUP - move cursor up a page +// STB_TEXTEDIT_K_PGDOWN - move cursor down a page + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) ((size_t)state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) ((size_t)state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // delete n characters from all other records + state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((size_t)(STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05 + } + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point, state->undo_rec + state->redo_point-1, (size_t) ((size_t)(STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (short) insert_len; + r->delete_length = (short) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point = state->undo_char_point + (short) insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - (short) u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} +#endif//STB_TEXTEDIT_IMPLEMENTATION diff --git a/apps/bench_shared/imgui/stb_truetype.h b/apps/bench_shared/imgui/stb_truetype.h new file mode 100644 index 00000000..a08e929f --- /dev/null +++ b/apps/bench_shared/imgui/stb_truetype.h @@ -0,0 +1,4853 @@ +// stb_truetype.h - v1.19 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/apps/bench_shared/inc/Application.h b/apps/bench_shared/inc/Application.h new file mode 100644 index 00000000..2cdb36ce --- /dev/null +++ b/apps/bench_shared/inc/Application.h @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef APPLICATION_H +#define APPLICATION_H + +// DAR This renderer only uses the CUDA Driver API! +// (CMake uses the CUDA_CUDA_LIBRARY which is nvcuda.lib. At runtime that loads nvcuda.dll from the driver.) +//#include + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS 1 +#include "imgui_internal.h" +#include "imgui_impl_glfw_gl3.h" + +#include +#if defined( _WIN32 ) +#include +#endif + +#include "inc/Camera.h" +#include "inc/Options.h" +#include "inc/Rasterizer.h" +#include "inc/Raytracer.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/Timer.h" + +#include + +#include "shaders/material_definition.h" + +// This include gl.h and needs to be done after glew.h +#include + +// assimp include files. +#include +#include +#include +#include +#include + +#include +#include + + +constexpr int APP_EXIT_SUCCESS = 0; +constexpr int APP_ERROR_UNKNOWN = -1; +constexpr int APP_ERROR_CREATE_WINDOW = -2; +constexpr int APP_ERROR_GLFW_INIT = -3; +constexpr int APP_ERROR_GLEW_INIT = -4; +constexpr int APP_ERROR_APP_INIT = -5; + + +enum GuiState +{ + GUI_STATE_NONE, + GUI_STATE_ORBIT, + GUI_STATE_PAN, + GUI_STATE_DOLLY, + GUI_STATE_FOCUS +}; + + +enum KeywordScene +{ + KS_ALBEDO, + KS_ALBEDO_TEXTURE, + KS_CUTOUT_TEXTURE, + KS_ROUGHNESS, + KS_ABSORPTION, + KS_ABSORPTION_SCALE, + KS_IOR, + KS_THINWALLED, + KS_MATERIAL, + KS_IDENTITY, + KS_PUSH, + KS_POP, + KS_ROTATE, + KS_SCALE, + KS_TRANSLATE, + KS_MODEL +}; + + +class Application +{ +public: + + Application(GLFWwindow* window, const Options& options); + ~Application(); + + bool isValid() const; + + void reshape(const int w, const int h); + bool render(); + void benchmark(); + + void display(); + + void guiNewFrame(); + void guiWindow(); + void guiEventHandler(); + void guiReferenceManual(); // The ImGui "programming manual" in form of a live window. + void guiRender(); + +private: + bool loadSystemDescription(const std::string& filename); + bool saveSystemDescription(); + bool loadSceneDescription(const std::string& filename); + + void restartRendering(); + + bool screenshot(const bool tonemap); + + void createPictures(); + void createCameras(); + void createLights(); + + void appendInstance(std::shared_ptr& group, + std::shared_ptr geometry, // Baseclass Node to be prepared for different geometric primitives. + const dp::math::Mat44f& matrix, + const std::string& reference, + unsigned int& idInstance); + + std::shared_ptr createASSIMP(const std::string& filename); + std::shared_ptr traverseScene(const struct aiScene *scene, const unsigned int indexSceneBase, const struct aiNode* node); + + void calculateTangents(std::vector& attributes, const std::vector& indices); + + void guiRenderingIndicator(const bool isRendering); + + bool loadString(const std::string& filename, std::string& text); + bool saveString(const std::string& filename, const std::string& text); + std::string getDateTime(); + void convertPath(std::string& path); + void convertPath(char* path); + + bool generatePNG(const unsigned int width, + const unsigned int height, + const unsigned char r, + const unsigned char g, + const unsigned char b); + + +private: + GLFWwindow* m_window; + bool m_isValid; + + GuiState m_guiState; + bool m_isVisibleGUI; + + // Command line options: + int m_width; // Client window size. + int m_height; + int m_mode; // Application mode 0 = interactive, 1 = batched benchmark (single shot). + bool m_optimize; // Command line option to let the assimp importer optimize the graph (sorts by material). + + // System options: + int m_strategy; // "strategy" // Ignored in this renderer. Always behaves like RS_INTERACTIVE_MULTI_GPU_LOCAL_COPY. + int m_maskDevices; // "devicesMask" // Bitmask with enabled devices, default 0x00FFFFFF for 24 devices. Only the visible ones will be used. + size_t m_sizeArena; // "arenaSize" // Default size for Arena allocations in mega-bytes. + int m_light; // "light" + int m_miss; // "miss" + std::string m_environment; // "envMap" + int m_interop; // "interop" // 0 = none all through host, 1 = register texture image, 2 = register pixel buffer + int m_peerToPeer; // "peerToPeer // Bitfield controlling P2P resource sharing.: + // Bit 0 = Allow peer-to-peer access via PCI-E (default off) + // Bit 1 = Share material textures (default on) + // Bit 2 = Share GAS (default on) + // Bit 3 = Share environment texture and CDFs (default off) + bool m_present; // "present" + bool m_presentNext; // (derived) + double m_presentAtSecond; // (derived) + bool m_previousComplete; // (derived) // Prevents spurious benchmark prints and image updates. + + // GUI Data representing raytracer settings. + LensShader m_lensShader; // "lensShader" + int2 m_pathLengths; // "pathLengths" // min, max + int2 m_resolution; // "resolution" // The actual size of the rendering, independent of the window's client size. (Preparation for final frame rendering.) + int2 m_tileSize; // "tileSize" // Multi-GPU distribution tile size. Must be power-of-two values. + int m_samplesSqrt; // "sampleSqrt" + float m_epsilonFactor; // "epsilonFactor" + float m_environmentRotation; // "envRotation" + float m_clockFactor; // "clockFactor" + + std::string m_prefixScreenshot; // "prefixScreenshot", allows to set a path and the prefix for the screenshot filename. spp, data, time and extension will be appended. + + TonemapperGUI m_tonemapperGUI; // "gamma", "whitePoint", "burnHighlights", "crushBlacks", "saturation", "brightness" + + Camera m_camera; // "center", "camera" + + float m_mouseSpeedRatio; + + Timer m_timer; + + std::map m_mapKeywordScene; + + std::unique_ptr m_rasterizer; + + std::unique_ptr m_raytracer; + + DeviceState m_state; + + // The scene description: + // Unique identifiers per host scene node. + unsigned int m_idGroup; + unsigned int m_idInstance; + unsigned int m_idGeometry; + + std::shared_ptr m_scene; // Root group node of the scene. + + std::vector< std::shared_ptr > m_geometries; // All geometries in the scene. Baseclass Node to be prepared for different geometric primitives. + + // For the runtime generated objects, this allows to find geometries with the same type and construction parameters. + // DAR HACK DEBUG No Instancing in this versions: std::map m_mapGeometries; + + // For all model file format loaders. Allows instancing of full models in the host side scene graph. + std::map< std::string, std::shared_ptr > m_mapGroups; + + std::vector m_cameras; + std::vector m_lights; + std::vector m_materialsGUI; + + // Map of local material names to indices in the m_materialsGUI vector. + std::map m_mapMaterialReferences; + + std::map m_mapPictures; + + std::vector m_remappedMeshIndices; +}; + +#endif // APPLICATION_H + diff --git a/apps/bench_shared/inc/Arena.h b/apps/bench_shared/inc/Arena.h new file mode 100644 index 00000000..abe50a44 --- /dev/null +++ b/apps/bench_shared/inc/Arena.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef ARENA_H +#define ARENA_H + +#include + +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include + +namespace cuda +{ + enum Usage + { + USAGE_STATIC, + USAGE_TEMP + }; + + class Arena; + + struct Block + { + Block(cuda::Arena* arena = nullptr, const CUdeviceptr addr = 0, const size_t size = 0, const CUdeviceptr ptr = 0); + + bool isValid() const; + bool operator<(const cuda::Block& rhs); + + cuda::Arena* m_arena; // The arena which owns this block. + CUdeviceptr m_addr; // The internal address inside the arena of the memory containing the aligned block at ptr. + size_t m_size; // The internal size of the allocation starting at m_addr. + CUdeviceptr m_ptr; // The aligned pointer to size bytes the user requested. + }; + + + class Arena + { + public: + Arena(const size_t size); + ~Arena(); + + bool allocBlock(cuda::Block& block, const size_t size, const size_t alignment, const cuda::Usage usage); + void freeBlock(const cuda::Block& block); + + private: + CUdeviceptr m_addr; // The start pointer of the CUDA memory for this arena. This is 256 bytes aligned due to cuMalloc(). + size_t m_size; // The overall size of the arena in bytes. + std::list m_blocksFree; // A list of free blocks inside this arena. This is normally very short because static and temporary allocations are not interleaved. + }; + + + class ArenaAllocator + { + public: + ArenaAllocator(const size_t sizeArenaBytes); + ~ArenaAllocator(); + + CUdeviceptr alloc(const size_t size, const size_t alignment, const cuda::Usage usage = cuda::USAGE_STATIC); + void free(const CUdeviceptr ptr); + + size_t getSizeMemoryAllocated() const; + + private: + size_t m_sizeArenaBytes; // The minimum size an arena is allocated with. If calls alloc() with a bigger size, that will be used. + size_t m_sizeMemoryAllocated; // The number of bytes which have been allocated (with alignment). + std::vector m_arenas; // A number of Arenas managing a CUdeviceptr with at least m_sizeArenaBytes each. + std::map m_blocksAllocated; // Map the user pointer to the internal block. This abstracts Arena and Block from the user. (Kind of expensive.) + }; + +} // namespace cuda + +#endif // ARENA_H diff --git a/apps/bench_shared/inc/Camera.h b/apps/bench_shared/inc/Camera.h new file mode 100644 index 00000000..29b72daa --- /dev/null +++ b/apps/bench_shared/inc/Camera.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CAMERA_H +#define CAMERA_H + +#include + + +class Camera +{ +public: + Camera(); + //~Camera(); + + void setResolution(int w, int h); + void setBaseCoordinates(int x, int y); + void setSpeedRatio(float f); + void setFocusDistance(float f); + + void orbit(int x, int y); + void pan(int x, int y); + void dolly(int x, int y); + void focus(int x, int y); + void zoom(float x); + + bool getFrustum(float3& p, float3& u, float3& v, float3& w, bool force = false); + float getAspectRatio() const; + void markDirty(); + +public: // public just to be able to load and save them easily. + float3 m_center; // Center of interest point, around which is orbited (and the sharp plane of a depth of field camera). + float m_distance; // Distance of the camera from the center of interest. + float m_phi; // Range [0.0f, 1.0f] from positive x-axis 360 degrees around the latitudes. + float m_theta; // Range [0.0f, 1.0f] from negative to positive y-axis. + float m_fov; // In degrees. Default is 60.0f + +private: + bool setDelta(int x, int y); + +private: + int m_widthResolution; + int m_heightResolution; + float m_aspect; // width / height + int m_baseX; + int m_baseY; + float m_speedRatio; + + // Derived values: + int m_dx; + int m_dy; + bool m_changed; + + float3 m_cameraP; + float3 m_cameraU; + float3 m_cameraV; + float3 m_cameraW; +}; + +#endif // CAMERA_H diff --git a/apps/bench_shared/inc/CheckMacros.h b/apps/bench_shared/inc/CheckMacros.h new file mode 100644 index 00000000..57060aa7 --- /dev/null +++ b/apps/bench_shared/inc/CheckMacros.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CHECK_MACROS_H +#define CHECK_MACROS_H + +#include +#include + +#include "inc/MyAssert.h" + +#define CU_CHECK(call) \ +{ \ + const CUresult result = call; \ + if (result != CUDA_SUCCESS) \ + { \ + const char* name; \ + cuGetErrorName(result, &name); \ + const char *error; \ + cuGetErrorString(result, &error); \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ") " << name << ": " << error; \ + MY_ASSERT(!"CU_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +#define CU_CHECK_NO_THROW(call) \ +{ \ + const CUresult result = call; \ + if (result != CUDA_SUCCESS) \ + { \ + const char* name; \ + cuGetErrorName(result, &name); \ + const char *error; \ + cuGetErrorString(result, &error); \ + std::cerr << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ") " << name << ": " << error << '\n'; \ + MY_ASSERT(!"CU_CHECK_NO_THROW"); \ + } \ +} + +#define OPTIX_CHECK(call) \ +{ \ + const OptixResult result = call; \ + if (result != OPTIX_SUCCESS) \ + { \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")"; \ + MY_ASSERT(!"OPTIX_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +#define OPTIX_CHECK_NO_THROW(call) \ +{ \ + const OptixResult result = call; \ + if (result != OPTIX_SUCCESS) \ + { \ + std::cerr << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")\n"; \ + MY_ASSERT(!"OPTIX_CHECK_NO_THROW"); \ + } \ +} + + +#endif // CHECK_MACROS_H diff --git a/apps/bench_shared/inc/Device.h b/apps/bench_shared/inc/Device.h new file mode 100644 index 00000000..c6c9b43a --- /dev/null +++ b/apps/bench_shared/inc/Device.h @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef DEVICE_H +#define DEVICE_H + +#include "shaders/config.h" + +#include +// This is needed for the __align__ only. +#include + +#include + +// OptiX 7 function table structure. +#include + +#include "inc/Arena.h" +#include "inc/MaterialGUI.h" +#include "inc/Picture.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/MyAssert.h" + +#include "shaders/system_data.h" +#include "shaders/per_ray_data.h" + +#include +#include +#include + +struct DeviceAttribute +{ + int maxThreadsPerBlock; + int maxBlockDimX; + int maxBlockDimY; + int maxBlockDimZ; + int maxGridDimX; + int maxGridDimY; + int maxGridDimZ; + int maxSharedMemoryPerBlock; + int sharedMemoryPerBlock; + int totalConstantMemory; + int warpSize; + int maxPitch; + int maxRegistersPerBlock; + int registersPerBlock; + int clockRate; + int textureAlignment; + int gpuOverlap; + int multiprocessorCount; + int kernelExecTimeout; + int integrated; + int canMapHostMemory; + int computeMode; + int maximumTexture1dWidth; + int maximumTexture2dWidth; + int maximumTexture2dHeight; + int maximumTexture3dWidth; + int maximumTexture3dHeight; + int maximumTexture3dDepth; + int maximumTexture2dLayeredWidth; + int maximumTexture2dLayeredHeight; + int maximumTexture2dLayeredLayers; + int maximumTexture2dArrayWidth; + int maximumTexture2dArrayHeight; + int maximumTexture2dArrayNumslices; + int surfaceAlignment; + int concurrentKernels; + int eccEnabled; + int pciBusId; + int pciDeviceId; + int tccDriver; + int memoryClockRate; + int globalMemoryBusWidth; + int l2CacheSize; + int maxThreadsPerMultiprocessor; + int asyncEngineCount; + int unifiedAddressing; + int maximumTexture1dLayeredWidth; + int maximumTexture1dLayeredLayers; + int canTex2dGather; + int maximumTexture2dGatherWidth; + int maximumTexture2dGatherHeight; + int maximumTexture3dWidthAlternate; + int maximumTexture3dHeightAlternate; + int maximumTexture3dDepthAlternate; + int pciDomainId; + int texturePitchAlignment; + int maximumTexturecubemapWidth; + int maximumTexturecubemapLayeredWidth; + int maximumTexturecubemapLayeredLayers; + int maximumSurface1dWidth; + int maximumSurface2dWidth; + int maximumSurface2dHeight; + int maximumSurface3dWidth; + int maximumSurface3dHeight; + int maximumSurface3dDepth; + int maximumSurface1dLayeredWidth; + int maximumSurface1dLayeredLayers; + int maximumSurface2dLayeredWidth; + int maximumSurface2dLayeredHeight; + int maximumSurface2dLayeredLayers; + int maximumSurfacecubemapWidth; + int maximumSurfacecubemapLayeredWidth; + int maximumSurfacecubemapLayeredLayers; + int maximumTexture1dLinearWidth; + int maximumTexture2dLinearWidth; + int maximumTexture2dLinearHeight; + int maximumTexture2dLinearPitch; + int maximumTexture2dMipmappedWidth; + int maximumTexture2dMipmappedHeight; + int computeCapabilityMajor; + int computeCapabilityMinor; + int maximumTexture1dMipmappedWidth; + int streamPrioritiesSupported; + int globalL1CacheSupported; + int localL1CacheSupported; + int maxSharedMemoryPerMultiprocessor; + int maxRegistersPerMultiprocessor; + int managedMemory; + int multiGpuBoard; + int multiGpuBoardGroupId; + int hostNativeAtomicSupported; + int singleToDoublePrecisionPerfRatio; + int pageableMemoryAccess; + int concurrentManagedAccess; + int computePreemptionSupported; + int canUseHostPointerForRegisteredMem; + int canUse64BitStreamMemOps; + int canUseStreamWaitValueNor; + int cooperativeLaunch; + int cooperativeMultiDeviceLaunch; + int maxSharedMemoryPerBlockOptin; + int canFlushRemoteWrites; + int hostRegisterSupported; + int pageableMemoryAccessUsesHostPageTables; + int directManagedMemAccessFromHost; +}; + +struct DeviceProperty +{ + unsigned int rtcoreVersion; + unsigned int limitMaxTraceDepth; + unsigned int limitMaxTraversableGraphDepth; + unsigned int limitMaxPrimitivesPerGas; + unsigned int limitMaxInstancesPerIas; + unsigned int limitMaxInstanceId; + unsigned int limitNumBitsInstanceVisibilityMask; + unsigned int limitMaxSbtRecordsPerGas; + unsigned int limitMaxSbtOffset; +}; + + +// All programs outside the hit groups do not have any per program data. +struct SbtRecordHeader +{ + __align__(OPTIX_SBT_RECORD_ALIGNMENT) char header[OPTIX_SBT_RECORD_HEADER_SIZE]; +}; + +// The hit group gets per instance data in addition. +template +struct SbtRecordData +{ + __align__(OPTIX_SBT_RECORD_ALIGNMENT) char header[OPTIX_SBT_RECORD_HEADER_SIZE]; + T data; +}; + +typedef SbtRecordData SbtRecordGeometryInstanceData; + + +enum ModuleIdentifier +{ + MODULE_ID_RAYGENERATION, + MODULE_ID_EXCEPTION, + MODULE_ID_MISS, + MODULE_ID_CLOSESTHIT, + MODULE_ID_ANYHIT, + MODULE_ID_LENS_SHADER, + MODULE_ID_LIGHT_SAMPLE, + MODULE_ID_BXDF_DIFFUSE, + MODULE_ID_BXDF_SPECULAR, + MODULE_ID_BXDF_GGX_SMITH, + NUM_MODULE_IDENTIFIERS +}; + + +enum ProgramGroupId +{ + // Programs using SbtRecordHeader + PGID_RAYGENERATION, + PGID_EXCEPTION, + PGID_MISS_RADIANCE, + PGID_MISS_SHADOW, + // Direct Callables using SbtRecordHeader + PGID_LENS_PINHOLE, + FIRST_DIRECT_CALLABLE_ID = PGID_LENS_PINHOLE, + PGID_LENS_FISHEYE, + PGID_LENS_SPHERE, + PGID_LIGHT_ENV, + PGID_LIGHT_AREA, + PGID_BRDF_DIFFUSE_SAMPLE, + PGID_BRDF_DIFFUSE_EVAL, + PGID_BRDF_SPECULAR_SAMPLE, + PGID_BRDF_SPECULAR_EVAL, + PGID_BSDF_SPECULAR_SAMPLE, + PGID_BSDF_SPECULAR_EVAL, + PGID_BRDF_GGX_SMITH_SAMPLE, + PGID_BRDF_GGX_SMITH_EVAL, + PGID_BSDF_GGX_SMITH_SAMPLE, + PGID_BSDF_GGX_SMITH_EVAL, + LAST_DIRECT_CALLABLE_ID = PGID_BSDF_GGX_SMITH_EVAL, + // Programs using SbtRecordGeometryInstanceData + PGID_HIT_RADIANCE, + PGID_HIT_SHADOW, + PGID_HIT_RADIANCE_CUTOUT, + PGID_HIT_SHADOW_CUTOUT, + // Number of all program group entries. + NUM_PROGRAM_GROUP_IDS +}; + +// This is in preparation for more than one geometric primitive type. +enum PrimitiveType +{ + PT_UNKNOWN, // It's an error when this is still set. + PT_TRIANGLES +}; + + +struct GeometryData +{ + GeometryData() + : primitiveType(PT_UNKNOWN) + , owner(-1) + , traversable(0) + , d_attributes(0) + , d_indices(0) + , numAttributes(0) + , numIndices(0) + , d_gas(0) + { + info = {}; + } + + PrimitiveType primitiveType; // In preparation for more geometric primitive types in a renderer. Used during SBT creation to assign the proper hit records. + int owner; // The device index which originally allocated all device side memory below. Needed when sharing GeometryData, resp. when freeing it. + OptixTraversableHandle traversable; // The traversable handle for this GAS. Assigned to the Instance above it. + CUdeviceptr d_attributes; // Array of VertexAttribute structs. + CUdeviceptr d_indices; // Array of unsigned int indices. + size_t numAttributes; // Count of attributes structs. + size_t numIndices; // Count of unsigned int indices (not triplets for triangles). + CUdeviceptr d_gas; // The geometry AS for the traversable handle. +#if (OPTIX_VERSION >= 70600) + OptixRelocationInfo info; // The relocation info allows to check if the AS is compatible across OptiX contexts. + // (In the NVLINK case that isn't really required, because the GPU configuration must be homogeneous inside an NVLINK island.) +#else + OptixAccelRelocationInfo info; +#endif +}; + +struct InstanceData +{ + InstanceData(unsigned int geometry, int material, int light) + : idGeometry(geometry) + , idMaterial(material) + , idLight(light) + { + } + + unsigned int idGeometry; + int idMaterial; // Negative is an error. + int idLight; // Negative means no light. +}; + +// GUI controllable settings in the device. +struct DeviceState +{ + int2 resolution; + int2 tileSize; + int2 pathLengths; + int samplesSqrt; + LensShader lensShader; + float epsilonFactor; + float envRotation; + float clockFactor; +}; + + +class Device +{ +public: + Device(const int ordinal, // The original CUDA ordinal ID. + const int index, // The zero based index of this device, required for multi-GPU work distribution. + const int count, // The number of active devices, required for multi-GPU work distribution. + const int miss, // The miss shader ID to use. + const int interop, // The interop mode to use. + const unsigned int tex, // OpenGL HDR texture object handle + const unsigned int pbo, // OpenGL PBO handle. + const size_t sizeArena); // The default Arena size in mega-bytes. + ~Device(); + + bool matchUUID(const char* uuid); + bool matchLUID(const char* luid, const unsigned int nodeMask); + + void initCameras(const std::vector& cameras); + void initLights(const std::vector& lights); + void initMaterials(const std::vector& materialsGUI); + + GeometryData createGeometry(std::shared_ptr geometry); + void destroyGeometry(GeometryData& data); + void createInstance(const GeometryData& geometryData, const InstanceData& data, const float matrix[12]); + void createTLAS(); + void createHitGroupRecords(const std::vector& geometryData, const unsigned int stride, const unsigned int index); + + void updateCamera(const int idCamera, const CameraDefinition& camera); + void updateLight(const int idLight, const LightDefinition& light); + void updateMaterial(const int idMaterial, const MaterialGUI& materialGUI); + + void setState(const DeviceState& state); + void compositor(Device* other); + + void activateContext() const ; + void synchronizeStream() const; + void render(const unsigned int iterationIndex, void** buffer); + void updateDisplayTexture(); + const void* getOutputBufferHost(); + + CUdeviceptr memAlloc(const size_t size, const size_t alignment, const cuda::Usage usage = cuda::USAGE_STATIC); + void memFree(const CUdeviceptr ptr); + + size_t getMemoryFree() const; + size_t getMemoryAllocated() const; + + Texture* initTexture(const std::string& name, const Picture* picture, const unsigned int flags); + void shareTexture(const std::string & name, const Texture* shared); + +private: + OptixResult initFunctionTable(); + void initDeviceAttributes(); + void initDeviceProperties(); + void initPipeline(); + +public: + // Constructor arguments: + int m_ordinal; // The ordinal number of this CUDA device. Used to get the CUdevice handle m_cudaDevice. + int m_index; // The index inside the m_devicesActive vector. + int m_count; // The number of active devices. + int m_miss; // Type of environment miss shader to use. 0 = black no light, 1 = constant white, 2 = spherical HDR env map. + int m_interop; // The interop mode to use. + unsigned int m_tex; // The OpenGL HDR texture object. + unsigned int m_pbo; // The OpenGL PixelBufferObject handle when interop should be used. 0 when not. + + float m_clockFactor; // Clock Factor scaled by CLOCK_FACTOR_SCALE (1.0e-9f) for USE_TIME_VIEW + + CUuuid m_deviceUUID; + + // Not actually used because this only works under Windows WDDM mode, not in TCC mode! + // (Though using LUID is recommended under Windows when possible because you can potentially + // get the same UUID for MS hybrid configurations (like when running under Remote Desktop). + char m_deviceLUID[8]; + unsigned int m_nodeMask; + + std::string m_deviceName; + std::string m_devicePciBusId; // domain:bus:device.function, required to find matching CUDA device via NVML. + + DeviceAttribute m_deviceAttribute; // CUDA + DeviceProperty m_deviceProperty; // OptiX + + CUdevice m_cudaDevice; // The CUdevice handle of the CUDA device ordinal. (Usually identical to m_ordinal.) + CUcontext m_cudaContext; + CUstream m_cudaStream; + + OptixFunctionTable m_api; + OptixDeviceContext m_optixContext; + + std::vector m_moduleFilenames; + + OptixPipeline m_pipeline; + + OptixShaderBindingTable m_sbt; + + CUdeviceptr m_d_sbtRecordHeaders; + + SbtRecordGeometryInstanceData m_sbtRecordHitRadiance; + SbtRecordGeometryInstanceData m_sbtRecordHitShadow; + + SbtRecordGeometryInstanceData m_sbtRecordHitRadianceCutout; + SbtRecordGeometryInstanceData m_sbtRecordHitShadowCutout; + + CUdeviceptr m_d_ias; + + std::vector m_instances; + std::vector m_instanceData; // idGeometry, idMaterial, idLight + + std::vector m_sbtRecordGeometryInstanceData; + SbtRecordGeometryInstanceData* m_d_sbtRecordGeometryInstanceData; + + SystemData m_systemData; // This contains the root traversable handle as well. + SystemData* m_d_systemData; // Device side CUdeviceptr of the system data. + + int m_launchWidth; + + bool m_isDirtySystemData; + bool m_isDirtyOutputBuffer; + bool m_ownsSharedBuffer; // DAR DEBUG Used in asserts only. + + std::map m_mapTextures; + + std::vector m_materials; // Staging data for the device side sysData.materialDefinitions + + // From the previously derived class DeviceMultiGPULocalCopy. + CUgraphicsResource m_cudaGraphicsResource; // The handle for the registered OpenGL PBO or texture when using interop. + + CUmodule m_moduleCompositor; + CUfunction m_functionCompositor; + CUdeviceptr m_d_compositorData; + + std::vector m_bufferHost; + + cuda::ArenaAllocator* m_allocator; + + // The sum of all texture CUarray resp. CUmipmappedArray data sent in bytes + // (without GPU padding and row alignment). + size_t m_sizeMemoryTextureArrays; +}; + +#endif // DEVICE_H diff --git a/apps/bench_shared/inc/MaterialGUI.h b/apps/bench_shared/inc/MaterialGUI.h new file mode 100644 index 00000000..7f4b3277 --- /dev/null +++ b/apps/bench_shared/inc/MaterialGUI.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MATERIAL_GUI_H +#define MATERIAL_GUI_H + +#include "shaders/function_indices.h" + +#include + + // Host side GUI material parameters +struct MaterialGUI +{ + std::string name; // The name used in the scene description to identify this material instance. + std::string nameTextureAlbedo; // The filename of the albedo texture for this material. Empty when none. + std::string nameTextureCutout; // The filename of the cutout opacity texture for this material. Empty when none. + FunctionIndex indexBSDF; // BSDF index to use in the closest hit program. + float3 albedo; // Tint, throughput change for specular materials. + float3 absorptionColor; // absorptionColor and absorptionScale together build the absorption coefficient + float absorptionScale; + float2 roughness; // Anisotropic roughness for microfacet distributions. + float ior; // Index of Refraction. + bool thinwalled; +}; + +#endif // MATERIAL_GUI_H diff --git a/apps/bench_shared/inc/MyAssert.h b/apps/bench_shared/inc/MyAssert.h new file mode 100644 index 00000000..6e2234a5 --- /dev/null +++ b/apps/bench_shared/inc/MyAssert.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MY_ASSERT_H +#define MY_ASSERT_H + +#undef MY_STATIC_ASSERT + +#define MY_STATIC_ASSERT(exp) static_assert(exp, #exp); + +#undef MY_ASSERT + +#if !defined(NDEBUG) + #if defined(_WIN32) // Windows 32/64-bit + #include + #define DBGBREAK() DebugBreak(); + #else + #define DBGBREAK() __builtin_trap(); + #endif + #define MY_ASSERT(expr) if (!(expr)) DBGBREAK() +#else + #define MY_ASSERT(expr) +#endif + +#if !defined(NDEBUG) + #define MY_VERIFY(expr) MY_ASSERT(expr) +#else + #define MY_VERIFY(expr) (static_cast(expr)) +#endif + +#endif // MY_ASSERT_H diff --git a/apps/bench_shared/inc/NVMLImpl.h b/apps/bench_shared/inc/NVMLImpl.h new file mode 100644 index 00000000..c104176a --- /dev/null +++ b/apps/bench_shared/inc/NVMLImpl.h @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef NVML_IMPL_H +#define NVML_IMPL_H + +// This header ships with the CUDA Toolkit. +#include + + +#define NVML_CHECK(call) \ +{ \ + const nvmlReturn_t result = call; \ + if (result != NVML_SUCCESS) \ + { \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")"; \ + MY_ASSERT(!"NVML_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +// These function entry point types and names support the automatic NVML API versioning inside nvml.h. +// Some of the NVML functions are versioned by a #define adding a version suffix (_v2, _v3) to the name, +// which requires a set of two macros to resolve the unversioned function name to the versioned one. + +#define FUNC_T_V(name) name##_t +#define FUNC_T(name) FUNC_T_V(name) + +#define FUNC_P_V(name) name##_t name +#define FUNC_P(name) FUNC_P_V(name) + +typedef nvmlReturn_t (*FUNC_T(nvmlInit))(void); +//typedef nvmlReturn_t (*FUNC_T(nvmlInitWithFlags))(unsigned int flags); +typedef nvmlReturn_t (*FUNC_T(nvmlShutdown))(void); +//typedef const char* (*FUNC_T(nvmlErrorString))(nvmlReturn_t result); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetDriverVersion))(char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetNVMLVersion))(char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetCudaDriverVersion))(int *cudaDriverVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetCudaDriverVersion_v2))(int *cudaDriverVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetProcessName))(unsigned int pid, char *name, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetCount))(unsigned int *unitCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetHandleByIndex))(unsigned int index, nvmlUnit_t *unit); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetUnitInfo))(nvmlUnit_t unit, nvmlUnitInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetLedState))(nvmlUnit_t unit, nvmlLedState_t *state); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetPsuInfo))(nvmlUnit_t unit, nvmlPSUInfo_t *psu); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetTemperature))(nvmlUnit_t unit, unsigned int type, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetFanSpeedInfo))(nvmlUnit_t unit, nvmlUnitFanSpeeds_t *fanSpeeds); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetDevices))(nvmlUnit_t unit, unsigned int *deviceCount, nvmlDevice_t *devices); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetHicVersion))(unsigned int *hwbcCount, nvmlHwbcEntry_t *hwbcEntries); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCount))(unsigned int *deviceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAttributes))(nvmlDevice_t device, nvmlDeviceAttributes_t *attributes); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByIndex))(unsigned int index, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleBySerial))(const char *serial, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByUUID))(const char *uuid, nvmlDevice_t *device); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByPciBusId))(const char *pciBusId, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetName))(nvmlDevice_t device, char *name, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBrand))(nvmlDevice_t device, nvmlBrandType_t *type); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetIndex))(nvmlDevice_t device, unsigned int *index); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSerial))(nvmlDevice_t device, char *serial, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryAffinity))(nvmlDevice_t device, unsigned int nodeSetSize, unsigned long *nodeSet, nvmlAffinityScope_t scope); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCpuAffinityWithinScope))(nvmlDevice_t device, unsigned int cpuSetSize, unsigned long *cpuSet, nvmlAffinityScope_t scope); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCpuAffinity))(nvmlDevice_t device, unsigned int cpuSetSize, unsigned long *cpuSet); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetCpuAffinity))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearCpuAffinity))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTopologyCommonAncestor))(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuTopologyLevel_t *pathInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTopologyNearestGpus))(nvmlDevice_t device, nvmlGpuTopologyLevel_t level, unsigned int *count, nvmlDevice_t *deviceArray); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetTopologyGpuSet))(unsigned int cpuNumber, unsigned int *count, nvmlDevice_t *deviceArray); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetP2PStatus))(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuP2PCapsIndex_t p2pIndex,nvmlGpuP2PStatus_t *p2pStatus); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetUUID))(nvmlDevice_t device, char *uuid, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetMdevUUID))(nvmlVgpuInstance_t vgpuInstance, char *mdevUuid, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMinorNumber))(nvmlDevice_t device, unsigned int *minorNumber); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBoardPartNumber))(nvmlDevice_t device, char* partNumber, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomVersion))(nvmlDevice_t device, nvmlInforomObject_t object, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomImageVersion))(nvmlDevice_t device, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomConfigurationChecksum))(nvmlDevice_t device, unsigned int *checksum); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceValidateInforom))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDisplayMode))(nvmlDevice_t device, nvmlEnableState_t *display); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDisplayActive))(nvmlDevice_t device, nvmlEnableState_t *isActive); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPersistenceMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPciInfo))(nvmlDevice_t device, nvmlPciInfo_t *pci); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxPcieLinkGeneration))(nvmlDevice_t device, unsigned int *maxLinkGen); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxPcieLinkWidth))(nvmlDevice_t device, unsigned int *maxLinkWidth); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrPcieLinkGeneration))(nvmlDevice_t device, unsigned int *currLinkGen); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrPcieLinkWidth))(nvmlDevice_t device, unsigned int *currLinkWidth); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPcieThroughput))(nvmlDevice_t device, nvmlPcieUtilCounter_t counter, unsigned int *value); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPcieReplayCounter))(nvmlDevice_t device, unsigned int *value); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetClockInfo))(nvmlDevice_t device, nvmlClockType_t type, unsigned int *clock); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxClockInfo))(nvmlDevice_t device, nvmlClockType_t type, unsigned int *clock); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetApplicationsClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDefaultApplicationsClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetApplicationsClocks))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetClock))(nvmlDevice_t device, nvmlClockType_t clockType, nvmlClockId_t clockId, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxCustomerBoostClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedMemoryClocks))(nvmlDevice_t device, unsigned int *count, unsigned int *clocksMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedGraphicsClocks))(nvmlDevice_t device, unsigned int memoryClockMHz, unsigned int *count, unsigned int *clocksMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t *isEnabled, nvmlEnableState_t *defaultIsEnabled); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t enabled); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetDefaultAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t enabled, unsigned int flags); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFanSpeed))(nvmlDevice_t device, unsigned int *speed); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFanSpeed_v2))(nvmlDevice_t device, unsigned int fan, unsigned int * speed); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTemperature))(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTemperatureThreshold))(nvmlDevice_t device, nvmlTemperatureThresholds_t thresholdType, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPerformanceState))(nvmlDevice_t device, nvmlPstates_t *pState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrentClocksThrottleReasons))(nvmlDevice_t device, unsigned long long *clocksThrottleReasons); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedClocksThrottleReasons))(nvmlDevice_t device, unsigned long long *supportedClocksThrottleReasons); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerState))(nvmlDevice_t device, nvmlPstates_t *pState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementLimit))(nvmlDevice_t device, unsigned int *limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementLimitConstraints))(nvmlDevice_t device, unsigned int *minLimit, unsigned int *maxLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementDefaultLimit))(nvmlDevice_t device, unsigned int *defaultLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerUsage))(nvmlDevice_t device, unsigned int *power); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTotalEnergyConsumption))(nvmlDevice_t device, unsigned long long *energy); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEnforcedPowerLimit))(nvmlDevice_t device, unsigned int *limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuOperationMode))(nvmlDevice_t device, nvmlGpuOperationMode_t *current, nvmlGpuOperationMode_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryInfo))(nvmlDevice_t device, nvmlMemory_t *memory); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeMode))(nvmlDevice_t device, nvmlComputeMode_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCudaComputeCapability))(nvmlDevice_t device, int *major, int *minor); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEccMode))(nvmlDevice_t device, nvmlEnableState_t *current, nvmlEnableState_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBoardId))(nvmlDevice_t device, unsigned int *boardId); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMultiGpuBoard))(nvmlDevice_t device, unsigned int *multiGpuBool); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTotalEccErrors))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, unsigned long long *eccCounts); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDetailedEccErrors))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, nvmlEccErrorCounts_t *eccCounts); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryErrorCounter))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, nvmlMemoryLocation_t locationType, unsigned long long *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetUtilizationRates))(nvmlDevice_t device, nvmlUtilization_t *utilization); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderUtilization))(nvmlDevice_t device, unsigned int *utilization, unsigned int *samplingPeriodUs); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderCapacity))(nvmlDevice_t device, nvmlEncoderType_t encoderQueryType, unsigned int *encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderStats))(nvmlDevice_t device, unsigned int *sessionCount, unsigned int *averageFps, unsigned int *averageLatency); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderSessions))(nvmlDevice_t device, unsigned int *sessionCount, nvmlEncoderSessionInfo_t *sessionInfos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDecoderUtilization))(nvmlDevice_t device, unsigned int *utilization, unsigned int *samplingPeriodUs); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFBCStats))(nvmlDevice_t device, nvmlFBCStats_t *fbcStats); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFBCSessions))(nvmlDevice_t device, unsigned int *sessionCount, nvmlFBCSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDriverModel))(nvmlDevice_t device, nvmlDriverModel_t *current, nvmlDriverModel_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVbiosVersion))(nvmlDevice_t device, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBridgeChipInfo))(nvmlDevice_t device, nvmlBridgeChipHierarchy_t *bridgeHierarchy); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeRunningProcesses))(nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGraphicsRunningProcesses))(nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceOnSameBoard))(nvmlDevice_t device1, nvmlDevice_t device2, int *onSameBoard); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAPIRestriction))(nvmlDevice_t device, nvmlRestrictedAPI_t apiType, nvmlEnableState_t *isRestricted); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSamples))(nvmlDevice_t device, nvmlSamplingType_t type, unsigned long long lastSeenTimeStamp, nvmlValueType_t *sampleValType, unsigned int *sampleCount, nvmlSample_t *samples); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBAR1MemoryInfo))(nvmlDevice_t device, nvmlBAR1Memory_t *bar1Memory); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetViolationStatus))(nvmlDevice_t device, nvmlPerfPolicyType_t perfPolicyType, nvmlViolationTime_t *violTime); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingStats))(nvmlDevice_t device, unsigned int pid, nvmlAccountingStats_t *stats); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingPids))(nvmlDevice_t device, unsigned int *count, unsigned int *pids); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingBufferSize))(nvmlDevice_t device, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPages))(nvmlDevice_t device, nvmlPageRetirementCause_t cause, unsigned int *pageCount, unsigned long long *addresses); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPages_v2))(nvmlDevice_t device, nvmlPageRetirementCause_t cause, unsigned int *pageCount, unsigned long long *addresses, unsigned long long *timestamps); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPagesPendingStatus))(nvmlDevice_t device, nvmlEnableState_t *isPending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRemappedRows))(nvmlDevice_t device, unsigned int *corrRows, unsigned int *uncRows, unsigned int *isPending, unsigned int *failureOccurred); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetArchitecture))(nvmlDevice_t device, nvmlDeviceArchitecture_t *arch); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitSetLedState))(nvmlUnit_t unit, nvmlLedColor_t color); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetPersistenceMode))(nvmlDevice_t device, nvmlEnableState_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetComputeMode))(nvmlDevice_t device, nvmlComputeMode_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetEccMode))(nvmlDevice_t device, nvmlEnableState_t ecc); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearEccErrorCounts))(nvmlDevice_t device, nvmlEccCounterType_t counterType); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetDriverModel))(nvmlDevice_t device, nvmlDriverModel_t driverModel, unsigned int flags); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetGpuLockedClocks))(nvmlDevice_t device, unsigned int minGpuClockMHz, unsigned int maxGpuClockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetGpuLockedClocks))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetApplicationsClocks))(nvmlDevice_t device, unsigned int memClockMHz, unsigned int graphicsClockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetPowerManagementLimit))(nvmlDevice_t device, unsigned int limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetGpuOperationMode))(nvmlDevice_t device, nvmlGpuOperationMode_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAPIRestriction))(nvmlDevice_t device, nvmlRestrictedAPI_t apiType, nvmlEnableState_t isRestricted); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAccountingMode))(nvmlDevice_t device, nvmlEnableState_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearAccountingPids))(nvmlDevice_t device); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkState))(nvmlDevice_t device, unsigned int link, nvmlEnableState_t *isActive); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkVersion))(nvmlDevice_t device, unsigned int link, unsigned int *version); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkCapability))(nvmlDevice_t device, unsigned int link, nvmlNvLinkCapability_t capability, unsigned int *capResult); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkRemotePciInfo))(nvmlDevice_t device, unsigned int link, nvmlPciInfo_t *pci); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkErrorCounter))(nvmlDevice_t device, unsigned int link, nvmlNvLinkErrorCounter_t counter, unsigned long long *counterValue); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetNvLinkErrorCounters))(nvmlDevice_t device, unsigned int link); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetNvLinkUtilizationControl))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlNvLinkUtilizationControl_t *control, unsigned int reset); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkUtilizationControl))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlNvLinkUtilizationControl_t *control); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter, unsigned long long *rxcounter, unsigned long long *txcounter); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceFreezeNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlEnableState_t freeze); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetCreate))(nvmlEventSet_t *set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceRegisterEvents))(nvmlDevice_t device, unsigned long long eventTypes, nvmlEventSet_t set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedEventTypes))(nvmlDevice_t device, unsigned long long *eventTypes); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetWait))(nvmlEventSet_t set, nvmlEventData_t * data, unsigned int timeoutms); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetFree))(nvmlEventSet_t set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceModifyDrainState))(nvmlPciInfo_t *pciInfo, nvmlEnableState_t newState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceQueryDrainState))(nvmlPciInfo_t *pciInfo, nvmlEnableState_t *currentState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceRemoveGpu))(nvmlPciInfo_t *pciInfo, nvmlDetachGpuState_t gpuState, nvmlPcieLinkState_t linkState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceDiscoverGpus))(nvmlPciInfo_t *pciInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFieldValues))(nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVirtualizationMode))(nvmlDevice_t device, nvmlGpuVirtualizationMode_t *pVirtualMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHostVgpuMode))(nvmlDevice_t device, nvmlHostVgpuMode_t *pHostVgpuMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetVirtualizationMode))(nvmlDevice_t device, nvmlGpuVirtualizationMode_t virtualMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGridLicensableFeatures))(nvmlDevice_t device, nvmlGridLicensableFeatures_t *pGridLicensableFeatures); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetProcessUtilization))(nvmlDevice_t device, nvmlProcessUtilizationSample_t *utilization, unsigned int *processSamplesCount, unsigned long long lastSeenTimeStamp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCreatableVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetClass))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeClass, unsigned int *size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetName))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeName, unsigned int *size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetDeviceID))(nvmlVgpuTypeId_t vgpuTypeId, unsigned long long *deviceID, unsigned long long *subsystemID); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetFramebufferSize))(nvmlVgpuTypeId_t vgpuTypeId, unsigned long long *fbSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetNumDisplayHeads))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *numDisplayHeads); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetResolution))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int displayIndex, unsigned int *xdim, unsigned int *ydim); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetLicense))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeLicenseString, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetFrameRateLimit))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *frameRateLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetMaxInstances))(nvmlDevice_t device, nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetMaxInstancesPerVm))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCountPerVm); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetActiveVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuInstance_t *vgpuInstances); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetVmID))(nvmlVgpuInstance_t vgpuInstance, char *vmId, unsigned int size, nvmlVgpuVmIdType_t *vmIdType); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetUUID))(nvmlVgpuInstance_t vgpuInstance, char *uuid, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetVmDriverVersion))(nvmlVgpuInstance_t vgpuInstance, char* version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFbUsage))(nvmlVgpuInstance_t vgpuInstance, unsigned long long *fbUsage); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetLicenseStatus))(nvmlVgpuInstance_t vgpuInstance, unsigned int *licensed); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetType))(nvmlVgpuInstance_t vgpuInstance, nvmlVgpuTypeId_t *vgpuTypeId); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFrameRateLimit))(nvmlVgpuInstance_t vgpuInstance, unsigned int *frameRateLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEccMode))(nvmlVgpuInstance_t vgpuInstance, nvmlEnableState_t *eccMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderCapacity))(nvmlVgpuInstance_t vgpuInstance, unsigned int *encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceSetEncoderCapacity))(nvmlVgpuInstance_t vgpuInstance, unsigned int encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderStats))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, unsigned int *averageFps, unsigned int *averageLatency); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderSessions))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, nvmlEncoderSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFBCStats))(nvmlVgpuInstance_t vgpuInstance, nvmlFBCStats_t *fbcStats); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFBCSessions))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, nvmlFBCSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetMetadata))(nvmlVgpuInstance_t vgpuInstance, nvmlVgpuMetadata_t *vgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuMetadata))(nvmlDevice_t device, nvmlVgpuPgpuMetadata_t *pgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetVgpuCompatibility))(nvmlVgpuMetadata_t *vgpuMetadata, nvmlVgpuPgpuMetadata_t *pgpuMetadata, nvmlVgpuPgpuCompatibility_t *compatibilityInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPgpuMetadataString))(nvmlDevice_t device, char *pgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetVgpuVersion))(nvmlVgpuVersion_t *supported, nvmlVgpuVersion_t *current); +//typedef nvmlReturn_t (*FUNC_T(nvmlSetVgpuVersion))(nvmlVgpuVersion_t *vgpuVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuUtilization))(nvmlDevice_t device, unsigned long long lastSeenTimeStamp, nvmlValueType_t *sampleValType, unsigned int *vgpuInstanceSamplesCount, nvmlVgpuInstanceUtilizationSample_t *utilizationSamples); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuProcessUtilization))(nvmlDevice_t device, unsigned long long lastSeenTimeStamp, unsigned int *vgpuProcessSamplesCount, nvmlVgpuProcessUtilizationSample_t *utilizationSamples); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingMode))(nvmlVgpuInstance_t vgpuInstance, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingPids))(nvmlVgpuInstance_t vgpuInstance, unsigned int *count, unsigned int *pids); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingStats))(nvmlVgpuInstance_t vgpuInstance, unsigned int pid, nvmlAccountingStats_t *stats); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceClearAccountingPids))(nvmlVgpuInstance_t vgpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetBlacklistDeviceCount))(unsigned int *deviceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetBlacklistDeviceInfoByIndex))(unsigned int index, nvmlBlacklistDeviceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetMigMode))(nvmlDevice_t device, unsigned int mode, nvmlReturn_t *activationStatus); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMigMode))(nvmlDevice_t device, unsigned int *currentMode, unsigned int *pendingMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceProfileInfo))(nvmlDevice_t device, unsigned int profile, nvmlGpuInstanceProfileInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstancePossiblePlacements))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstancePlacement_t *placements, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceRemainingCapacity))(nvmlDevice_t device, unsigned int profileId, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceCreateGpuInstance))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstance_t *gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceDestroy))(nvmlGpuInstance_t gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstances))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstance_t *gpuInstances, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceById))(nvmlDevice_t device, unsigned int id, nvmlGpuInstance_t *gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetInfo))(nvmlGpuInstance_t gpuInstance, nvmlGpuInstanceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceProfileInfo))(nvmlGpuInstance_t gpuInstance, unsigned int profile, unsigned int engProfile, nvmlComputeInstanceProfileInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceRemainingCapacity))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceCreateComputeInstance))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, nvmlComputeInstance_t *computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlComputeInstanceDestroy))(nvmlComputeInstance_t computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstances))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, nvmlComputeInstance_t *computeInstances, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceById))(nvmlGpuInstance_t gpuInstance, unsigned int id, nvmlComputeInstance_t *computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlComputeInstanceGetInfo))(nvmlComputeInstance_t computeInstance, nvmlComputeInstanceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceIsMigDeviceHandle))(nvmlDevice_t device, unsigned int *isMigDevice); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceId))(nvmlDevice_t device, unsigned int *id); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeInstanceId))(nvmlDevice_t device, unsigned int *id); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxMigDeviceCount))(nvmlDevice_t device, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMigDeviceHandleByIndex))(nvmlDevice_t device, unsigned int index, nvmlDevice_t *migDevice); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDeviceHandleFromMigDeviceHandle))(nvmlDevice_t migDevice, nvmlDevice_t *device); + + + +struct NVMLFunctionTable +{ + FUNC_P(nvmlInit); + //FUNC_P(nvmlInitWithFlags); + FUNC_P(nvmlShutdown); + //FUNC_P(nvmlErrorString); + //FUNC_P(nvmlSystemGetDriverVersion); + //FUNC_P(nvmlSystemGetNVMLVersion); + //FUNC_P(nvmlSystemGetCudaDriverVersion); + //FUNC_P(nvmlSystemGetCudaDriverVersion_v2); + //FUNC_P(nvmlSystemGetProcessName); + //FUNC_P(nvmlUnitGetCount); + //FUNC_P(nvmlUnitGetHandleByIndex); + //FUNC_P(nvmlUnitGetUnitInfo); + //FUNC_P(nvmlUnitGetLedState); + //FUNC_P(nvmlUnitGetPsuInfo); + //FUNC_P(nvmlUnitGetTemperature); + //FUNC_P(nvmlUnitGetFanSpeedInfo); + //FUNC_P(nvmlUnitGetDevices); + //FUNC_P(nvmlSystemGetHicVersion); + //FUNC_P(nvmlDeviceGetCount); + //FUNC_P(nvmlDeviceGetAttributes); + //FUNC_P(nvmlDeviceGetHandleByIndex); + //FUNC_P(nvmlDeviceGetHandleBySerial); + //FUNC_P(nvmlDeviceGetHandleByUUID); + FUNC_P(nvmlDeviceGetHandleByPciBusId); + //FUNC_P(nvmlDeviceGetName); + //FUNC_P(nvmlDeviceGetBrand); + //FUNC_P(nvmlDeviceGetIndex); + //FUNC_P(nvmlDeviceGetSerial); + //FUNC_P(nvmlDeviceGetMemoryAffinity); + //FUNC_P(nvmlDeviceGetCpuAffinityWithinScope); + //FUNC_P(nvmlDeviceGetCpuAffinity); + //FUNC_P(nvmlDeviceSetCpuAffinity); + //FUNC_P(nvmlDeviceClearCpuAffinity); + //FUNC_P(nvmlDeviceGetTopologyCommonAncestor); + //FUNC_P(nvmlDeviceGetTopologyNearestGpus); + //FUNC_P(nvmlSystemGetTopologyGpuSet); + //FUNC_P(nvmlDeviceGetP2PStatus); + //FUNC_P(nvmlDeviceGetUUID); + //FUNC_P(nvmlVgpuInstanceGetMdevUUID); + //FUNC_P(nvmlDeviceGetMinorNumber); + //FUNC_P(nvmlDeviceGetBoardPartNumber); + //FUNC_P(nvmlDeviceGetInforomVersion); + //FUNC_P(nvmlDeviceGetInforomImageVersion); + //FUNC_P(nvmlDeviceGetInforomConfigurationChecksum); + //FUNC_P(nvmlDeviceValidateInforom); + //FUNC_P(nvmlDeviceGetDisplayMode); + //FUNC_P(nvmlDeviceGetDisplayActive); + //FUNC_P(nvmlDeviceGetPersistenceMode); + //FUNC_P(nvmlDeviceGetPciInfo); + //FUNC_P(nvmlDeviceGetMaxPcieLinkGeneration); + //FUNC_P(nvmlDeviceGetMaxPcieLinkWidth); + //FUNC_P(nvmlDeviceGetCurrPcieLinkGeneration); + //FUNC_P(nvmlDeviceGetCurrPcieLinkWidth); + //FUNC_P(nvmlDeviceGetPcieThroughput); + //FUNC_P(nvmlDeviceGetPcieReplayCounter); + //FUNC_P(nvmlDeviceGetClockInfo); + //FUNC_P(nvmlDeviceGetMaxClockInfo); + //FUNC_P(nvmlDeviceGetApplicationsClock); + //FUNC_P(nvmlDeviceGetDefaultApplicationsClock); + //FUNC_P(nvmlDeviceResetApplicationsClocks); + //FUNC_P(nvmlDeviceGetClock); + //FUNC_P(nvmlDeviceGetMaxCustomerBoostClock); + //FUNC_P(nvmlDeviceGetSupportedMemoryClocks); + //FUNC_P(nvmlDeviceGetSupportedGraphicsClocks); + //FUNC_P(nvmlDeviceGetAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceSetAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceSetDefaultAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceGetFanSpeed); + //FUNC_P(nvmlDeviceGetFanSpeed_v2); + //FUNC_P(nvmlDeviceGetTemperature); + //FUNC_P(nvmlDeviceGetTemperatureThreshold); + //FUNC_P(nvmlDeviceGetPerformanceState); + //FUNC_P(nvmlDeviceGetCurrentClocksThrottleReasons); + //FUNC_P(nvmlDeviceGetSupportedClocksThrottleReasons); + //FUNC_P(nvmlDeviceGetPowerState); + //FUNC_P(nvmlDeviceGetPowerManagementMode); + //FUNC_P(nvmlDeviceGetPowerManagementLimit); + //FUNC_P(nvmlDeviceGetPowerManagementLimitConstraints); + //FUNC_P(nvmlDeviceGetPowerManagementDefaultLimit); + //FUNC_P(nvmlDeviceGetPowerUsage); + //FUNC_P(nvmlDeviceGetTotalEnergyConsumption); + //FUNC_P(nvmlDeviceGetEnforcedPowerLimit); + //FUNC_P(nvmlDeviceGetGpuOperationMode); + //FUNC_P(nvmlDeviceGetMemoryInfo); + //FUNC_P(nvmlDeviceGetComputeMode); + //FUNC_P(nvmlDeviceGetCudaComputeCapability); + //FUNC_P(nvmlDeviceGetEccMode); + //FUNC_P(nvmlDeviceGetBoardId); + //FUNC_P(nvmlDeviceGetMultiGpuBoard); + //FUNC_P(nvmlDeviceGetTotalEccErrors); + //FUNC_P(nvmlDeviceGetDetailedEccErrors); + //FUNC_P(nvmlDeviceGetMemoryErrorCounter); + //FUNC_P(nvmlDeviceGetUtilizationRates); + //FUNC_P(nvmlDeviceGetEncoderUtilization); + //FUNC_P(nvmlDeviceGetEncoderCapacity); + //FUNC_P(nvmlDeviceGetEncoderStats); + //FUNC_P(nvmlDeviceGetEncoderSessions); + //FUNC_P(nvmlDeviceGetDecoderUtilization); + //FUNC_P(nvmlDeviceGetFBCStats); + //FUNC_P(nvmlDeviceGetFBCSessions); + //FUNC_P(nvmlDeviceGetDriverModel); + //FUNC_P(nvmlDeviceGetVbiosVersion); + //FUNC_P(nvmlDeviceGetBridgeChipInfo); + //FUNC_P(nvmlDeviceGetComputeRunningProcesses); + //FUNC_P(nvmlDeviceGetGraphicsRunningProcesses); + //FUNC_P(nvmlDeviceOnSameBoard); + //FUNC_P(nvmlDeviceGetAPIRestriction); + //FUNC_P(nvmlDeviceGetSamples); + //FUNC_P(nvmlDeviceGetBAR1MemoryInfo); + //FUNC_P(nvmlDeviceGetViolationStatus); + //FUNC_P(nvmlDeviceGetAccountingMode); + //FUNC_P(nvmlDeviceGetAccountingStats); + //FUNC_P(nvmlDeviceGetAccountingPids); + //FUNC_P(nvmlDeviceGetAccountingBufferSize); + //FUNC_P(nvmlDeviceGetRetiredPages); + //FUNC_P(nvmlDeviceGetRetiredPages_v2); + //FUNC_P(nvmlDeviceGetRetiredPagesPendingStatus); + //FUNC_P(nvmlDeviceGetRemappedRows); + //FUNC_P(nvmlDeviceGetArchitecture); + //FUNC_P(nvmlUnitSetLedState); + //FUNC_P(nvmlDeviceSetPersistenceMode); + //FUNC_P(nvmlDeviceSetComputeMode); + //FUNC_P(nvmlDeviceSetEccMode); + //FUNC_P(nvmlDeviceClearEccErrorCounts); + //FUNC_P(nvmlDeviceSetDriverModel); + //FUNC_P(nvmlDeviceSetGpuLockedClocks); + //FUNC_P(nvmlDeviceResetGpuLockedClocks); + //FUNC_P(nvmlDeviceSetApplicationsClocks); + //FUNC_P(nvmlDeviceSetPowerManagementLimit); + //FUNC_P(nvmlDeviceSetGpuOperationMode); + //FUNC_P(nvmlDeviceSetAPIRestriction); + //FUNC_P(nvmlDeviceSetAccountingMode); + //FUNC_P(nvmlDeviceClearAccountingPids); + FUNC_P(nvmlDeviceGetNvLinkState); + //FUNC_P(nvmlDeviceGetNvLinkVersion); + FUNC_P(nvmlDeviceGetNvLinkCapability); + FUNC_P(nvmlDeviceGetNvLinkRemotePciInfo); + //FUNC_P(nvmlDeviceGetNvLinkErrorCounter); + //FUNC_P(nvmlDeviceResetNvLinkErrorCounters); + //FUNC_P(nvmlDeviceSetNvLinkUtilizationControl); + //FUNC_P(nvmlDeviceGetNvLinkUtilizationControl); + //FUNC_P(nvmlDeviceGetNvLinkUtilizationCounter); + //FUNC_P(nvmlDeviceFreezeNvLinkUtilizationCounter); + //FUNC_P(nvmlDeviceResetNvLinkUtilizationCounter); + //FUNC_P(nvmlEventSetCreate); + //FUNC_P(nvmlDeviceRegisterEvents); + //FUNC_P(nvmlDeviceGetSupportedEventTypes); + //FUNC_P(nvmlEventSetWait); + //FUNC_P(nvmlEventSetFree); + //FUNC_P(nvmlDeviceModifyDrainState); + //FUNC_P(nvmlDeviceQueryDrainState); + //FUNC_P(nvmlDeviceRemoveGpu); + //FUNC_P(nvmlDeviceDiscoverGpus); + //FUNC_P(nvmlDeviceGetFieldValues); + //FUNC_P(nvmlDeviceGetVirtualizationMode); + //FUNC_P(nvmlDeviceGetHostVgpuMode); + //FUNC_P(nvmlDeviceSetVirtualizationMode); + //FUNC_P(nvmlDeviceGetGridLicensableFeatures); + //FUNC_P(nvmlDeviceGetProcessUtilization); + //FUNC_P(nvmlDeviceGetSupportedVgpus); + //FUNC_P(nvmlDeviceGetCreatableVgpus); + //FUNC_P(nvmlVgpuTypeGetClass); + //FUNC_P(nvmlVgpuTypeGetName); + //FUNC_P(nvmlVgpuTypeGetDeviceID); + //FUNC_P(nvmlVgpuTypeGetFramebufferSize); + //FUNC_P(nvmlVgpuTypeGetNumDisplayHeads); + //FUNC_P(nvmlVgpuTypeGetResolution); + //FUNC_P(nvmlVgpuTypeGetLicense); + //FUNC_P(nvmlVgpuTypeGetFrameRateLimit); + //FUNC_P(nvmlVgpuTypeGetMaxInstances); + //FUNC_P(nvmlVgpuTypeGetMaxInstancesPerVm); + //FUNC_P(nvmlDeviceGetActiveVgpus); + //FUNC_P(nvmlVgpuInstanceGetVmID); + //FUNC_P(nvmlVgpuInstanceGetUUID); + //FUNC_P(nvmlVgpuInstanceGetVmDriverVersion); + //FUNC_P(nvmlVgpuInstanceGetFbUsage); + //FUNC_P(nvmlVgpuInstanceGetLicenseStatus); + //FUNC_P(nvmlVgpuInstanceGetType); + //FUNC_P(nvmlVgpuInstanceGetFrameRateLimit); + //FUNC_P(nvmlVgpuInstanceGetEccMode); + //FUNC_P(nvmlVgpuInstanceGetEncoderCapacity); + //FUNC_P(nvmlVgpuInstanceSetEncoderCapacity); + //FUNC_P(nvmlVgpuInstanceGetEncoderStats); + //FUNC_P(nvmlVgpuInstanceGetEncoderSessions); + //FUNC_P(nvmlVgpuInstanceGetFBCStats); + //FUNC_P(nvmlVgpuInstanceGetFBCSessions); + //FUNC_P(nvmlVgpuInstanceGetMetadata); + //FUNC_P(nvmlDeviceGetVgpuMetadata); + //FUNC_P(nvmlGetVgpuCompatibility); + //FUNC_P(nvmlDeviceGetPgpuMetadataString); + //FUNC_P(nvmlGetVgpuVersion); + //FUNC_P(nvmlSetVgpuVersion); + //FUNC_P(nvmlDeviceGetVgpuUtilization); + //FUNC_P(nvmlDeviceGetVgpuProcessUtilization); + //FUNC_P(nvmlVgpuInstanceGetAccountingMode); + //FUNC_P(nvmlVgpuInstanceGetAccountingPids); + //FUNC_P(nvmlVgpuInstanceGetAccountingStats); + //FUNC_P(nvmlVgpuInstanceClearAccountingPids); + //FUNC_P(nvmlGetBlacklistDeviceCount); + //FUNC_P(nvmlGetBlacklistDeviceInfoByIndex); + //FUNC_P(nvmlDeviceSetMigMode); + //FUNC_P(nvmlDeviceGetMigMode); + //FUNC_P(nvmlDeviceGetGpuInstanceProfileInfo); + //FUNC_P(nvmlDeviceGetGpuInstancePossiblePlacements); + //FUNC_P(nvmlDeviceGetGpuInstanceRemainingCapacity); + //FUNC_P(nvmlDeviceCreateGpuInstance); + //FUNC_P(nvmlGpuInstanceDestroy); + //FUNC_P(nvmlDeviceGetGpuInstances); + //FUNC_P(nvmlDeviceGetGpuInstanceById); + //FUNC_P(nvmlGpuInstanceGetInfo); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceProfileInfo); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceRemainingCapacity); + //FUNC_P(nvmlGpuInstanceCreateComputeInstance); + //FUNC_P(nvmlComputeInstanceDestroy); + //FUNC_P(nvmlGpuInstanceGetComputeInstances); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceById); + //FUNC_P(nvmlComputeInstanceGetInfo); + //FUNC_P(nvmlDeviceIsMigDeviceHandle); + //FUNC_P(nvmlDeviceGetGpuInstanceId); + //FUNC_P(nvmlDeviceGetComputeInstanceId); + //FUNC_P(nvmlDeviceGetMaxMigDeviceCount); + //FUNC_P(nvmlDeviceGetMigDeviceHandleByIndex); + //FUNC_P(nvmlDeviceGetDeviceHandleFromMigDeviceHandle); +}; + +#undef FUNC_T_V +#undef FUNC_T + +#undef FUNC_P_V +#undef FUNC_P + +class NVMLImpl +{ +public: + NVMLImpl(); + //~NVMLImpl(); + + bool initFunctionTable(); + +public: + NVMLFunctionTable m_api; + +private: + void* m_handle; // nullptr when the library could not be found +}; + + +#endif // NVML_IMPL_H + diff --git a/apps/bench_shared/inc/Options.h b/apps/bench_shared/inc/Options.h new file mode 100644 index 00000000..a6cc1370 --- /dev/null +++ b/apps/bench_shared/inc/Options.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include + +class Options +{ +public: + Options(); + //~Options(); + + bool parseCommandLine(int argc, char *argv[]); + + int getWidth() const; + int getHeight() const; + int getMode() const; + bool getOptimize() const; + std::string getSystem() const; + std::string getScene() const; + +private: + void printUsage(const std::string& argv); + +private: + int m_width; + int m_height; + int m_mode; + bool m_optimize; + std::string m_filenameSystem; + std::string m_filenameScene; +}; + +#endif // OPTIONS_H diff --git a/apps/bench_shared/inc/Parser.h b/apps/bench_shared/inc/Parser.h new file mode 100644 index 00000000..89dda384 --- /dev/null +++ b/apps/bench_shared/inc/Parser.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef PARSER_H +#define PARSER_H + +#include + +enum ParserTokenType +{ + PTT_UNKNOWN, // Unknown, normally indicates an error. + PTT_ID, // Keywords and identifiers (not a number). + PTT_VAL, // Immediate floating point value. + PTT_STRING, // Filenames and any other identifier in quotation marks. + PTT_EOL, // End of line. + PTT_EOF // End of file. +}; + + +// System and scene file parsing information. +class Parser +{ +public: + Parser(); + //~Parser(); + + bool load(const std::string& filename); + + ParserTokenType getNextToken(std::string& token); + + size_t getSize() const; + std::string::size_type getIndex() const; + unsigned int getLine() const; + +private: + std::string m_source; // System or scene description file contents. + std::string::size_type m_index; // Parser's current character index into m_source. + unsigned int m_line; // Current source code line, one-based for error messages. +}; + +#endif // PARSER_H diff --git a/apps/bench_shared/inc/Picture.h b/apps/bench_shared/inc/Picture.h new file mode 100644 index 00000000..88336cfa --- /dev/null +++ b/apps/bench_shared/inc/Picture.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// Code in these classes is based on the ILTexLoader.h/.cpp routines inside the NVIDIA nvpro-pipeline ILTexLoader plugin: +// https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp + +#pragma once + +#ifndef PICTURE_H +#define PICTURE_H + +#include + +#include +#include + +// Bits for the image handling and texture creation flags. +#define IMAGE_FLAG_1D 0x00000001 +#define IMAGE_FLAG_2D 0x00000002 +#define IMAGE_FLAG_3D 0x00000004 +#define IMAGE_FLAG_CUBE 0x00000008 +// Modifier bits on the above types. +// Layered image (not applicable to 3D) +#define IMAGE_FLAG_LAYER 0x00000010 +// Mipmapped image (ignored when there are no mipmaps provided or for environment map). +#define IMAGE_FLAG_MIPMAP 0x00000020 +// Special case for a 2D spherical environment map. +#define IMAGE_FLAG_ENV 0x00000040 + +struct Image +{ + Image(unsigned int width, unsigned int height, unsigned int depth, int format, int type); + ~Image(); + + unsigned int m_width; + unsigned int m_height; + unsigned int m_depth; + + int m_format; // DevIL image format. + int m_type; // DevIL image component type. + + // Derived values. + unsigned int m_bpp; // bytes per pixel + unsigned int m_bpl; // bytes per scanline + unsigned int m_bps; // bytes per slice (plane) + unsigned int m_nob; // number of bytes (complete image) + + unsigned char* m_pixels; // The pixel data of one image. +}; + + +class Picture +{ +public: + Picture(); + ~Picture(); + + bool load(const std::string& filename, const unsigned int flags); + void clear(); + + // Add an empty new vector of images. Each vector can hold one mipmap chain. Returns the new image index. + unsigned int addImages(); + + // Add a mipmap chain as new vector of images. Returns the new image index. + // pixels and extents are for the LOD 0. mipmaps can be empty. + unsigned int addImages(const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type, + const std::vector& mipmaps, const unsigned int flags); + + // Append a new image LOD to the existing vector of images building a mipmap chain. (No mipmap consistency check here.) + unsigned int addLevel(const unsigned int index, + const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type); + + unsigned int getFlags() const; + unsigned int getNumberOfImages() const; + unsigned int getNumberOfLevels(unsigned int indexImage) const; + const Image* getImageLevel(unsigned int indexImage, unsigned int indexLevel) const; + bool isCubemap() const; + + // This is needed when generating cubemaps without loading them via DevIL. + void setIsCubemap(const bool isCube); + + // DEBUG Function to generate all 14 texture targets with RGBA8 images. + void generateRGBA8(unsigned int width, unsigned int height, unsigned int depth, const unsigned int flags); + +private: + void mirrorX(unsigned int index); + void mirrorY(unsigned int index); + + void clearImages(); // Delete all pixel data, all Image pointers and clear the m_images vector. + +private: + unsigned int m_flags; // The image flags which which this Picture has been loaded. + bool m_isCube; // Track if the picture is a cube map. + std::vector< std::vector > m_images; +}; + +#endif // PICTURE_H diff --git a/apps/bench_shared/inc/Rasterizer.h b/apps/bench_shared/inc/Rasterizer.h new file mode 100644 index 00000000..3bbe8e9f --- /dev/null +++ b/apps/bench_shared/inc/Rasterizer.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RASTERIZER_H +#define RASTERIZER_H + +#include +#if defined( _WIN32 ) +#include +#endif + +#include "inc/Timer.h" +#include "inc/TonemapperGUI.h" + +#include + +typedef struct +{ + float u; // u-coordinate in range [0.0, 1.0] + float c[3]; // color, components in range [0.0, 1.0] +} ColorRampElement; + + +class Rasterizer +{ +public: + Rasterizer(const int w, const int h, const int interop); + ~Rasterizer(); + + void reshape(const int w, const int h); + void display(); + + const int getNumDevices() const; + + const unsigned char* getUUID(const unsigned int index) const; + const unsigned char* getLUID() const; + int getNodeMask() const; + + unsigned int getTextureObject() const; + unsigned int getPixelBufferObject() const; + + void setResolution(const int w, const int h); + void setTonemapper(const TonemapperGUI& tm); + +private: + void checkInfoLog(const char *msg, GLuint object); + void initGLSL(); + void updateProjectionMatrix(); + void updateVertexAttributes(); + +private: + int m_width; + int m_height; + int m_interop; + + int m_widthResolution; + int m_heightResolution; + + GLint m_numDevices; // Number of OpenGL devices. Normally 1, unless multicast is enabled. + GLubyte m_deviceUUID[24][GL_UUID_SIZE_EXT]; // Max. 24 devices expected. 16 bytes identifier. + //GLubyte m_driverUUID[GL_UUID_SIZE_EXT]; // 16 bytes identifier, unused. + + GLubyte m_deviceLUID[GL_LUID_SIZE_EXT]; // 8 bytes identifier. + GLint m_nodeMask; // Node mask used together with the LUID to identify OpenGL device uniquely. + + GLuint m_hdrTexture; + GLuint m_pbo; + + GLuint m_colorRampTexture; + + GLuint m_glslProgram; + + GLuint m_vboAttributes; + GLuint m_vboIndices; + + GLint m_locAttrPosition; + GLint m_locAttrTexCoord; + GLint m_locProjection; + + GLint m_locSamplerHDR; + GLint m_locSamplerColorRamp; + + // Rasterizer side of the TonemapperGUI data + GLint m_locInvGamma; + GLint m_locColorBalance; + GLint m_locInvWhitePoint; + GLint m_locBurnHighlights; + GLint m_locCrushBlacks; + GLint m_locSaturation; + + Timer m_timer; +}; + +#endif // RASTERIZER_H diff --git a/apps/bench_shared/inc/Raytracer.h b/apps/bench_shared/inc/Raytracer.h new file mode 100644 index 00000000..8f7b354d --- /dev/null +++ b/apps/bench_shared/inc/Raytracer.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RAYTRACER_H +#define RAYTRACER_H + +#include "inc/Device.h" +#include "inc/MaterialGUI.h" +#include "inc/Picture.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/NVMLImpl.h" + +#include "shaders/system_data.h" + +#include +#include +#include + +// Bitfield encoding for m_peerToPeer: +#define P2P_PCI 1 +#define P2P_TEX 2 +#define P2P_GAS 4 +#define P2P_ENV 8 + +class Raytracer +{ +public: + Raytracer(const int maskDevices, + const int miss, + const int interop, + const unsigned int tex, + const unsigned int pbo, + const size_t sizeArena, + const int p2p); + ~Raytracer(); + + int matchUUID(const char* uuid); + int matchLUID(const char* luid, const unsigned int nodeMask); + + bool enablePeerAccess(); // Calculates peer-to-peer access bit matrix in m_peerConnections and sets the m_islands. + void disablePeerAccess(); // Clear the peer-to-peer islands. Afterwards each device is its own island. + + void synchronize(); // Needed for the benchmark to wait for all asynchronous rendering to have finished. + + void initTextures(const std::map& mapOfPictures); + void initCameras(const std::vector& cameras); + void initLights(const std::vector& lights); + void initMaterials(const std::vector& materialsGUI); + void initScene(std::shared_ptr root, const unsigned int numGeometries); + void initState(const DeviceState& state); + + // Update functions should be replaced with NOP functions in a derived batch renderer because the device functions are fully asynchronous then. + void updateCamera(const int idCamera, const CameraDefinition& camera); + void updateLight(const int idLight, const LightDefinition& light); + void updateMaterial(const int idMaterial, const MaterialGUI& src); + void updateState(const DeviceState& state); + + // Abstract functions must be implemented by each derived Raytracer per strategy individually. + unsigned int render(); + void updateDisplayTexture(); + const void* getOutputBufferHost(); + +private: + void selectDevices(); + int getDeviceHome(const std::vector& island) const; + void traverseNode(std::shared_ptr node, InstanceData instanceData, float matrix[12]); + bool activeNVLINK(const int home, const int peer) const; + int findActiveDevice(const unsigned int domain, const unsigned int bus, const unsigned int device) const; + +public: + // Constructor arguments + unsigned int m_maskDevices; // The bitmask with the devices the user selected. + int m_miss; // The miss program selection (0 = black, 1 = white, 2 = env map) + int m_interop; // Which CUDA-OpenGL interop to use. + unsigned int m_tex; // The OpenGL texture object used for display. + unsigned int m_pbo; // The pixel buffer object when using INTEROP_MODE_PBO. + size_t m_sizeArena; // The default Arena allocation size in mega-bytes, just routed through to Device class. + int m_peerToPeer; // Bitfield encoding for which resources CUDA P2P sharing is allowed: + // Bit 0: Allow sharing via PCI-E, ignores the NVLINK topology, just checks cuDeviceCanAccessPeer(). + // Bit 1: Share material textures (not the HDR environment). + // Bit 2: Share GAS and vertex attribute data. + // Bit 3: Share (optional) HDR environment and its CDF data. + + bool m_isValid; + + int m_numDevicesVisible; // The number of visible CUDA devices. (What you can control via the CUDA_VISIBLE_DEVICES environment variable.) + int m_indexDeviceOGL; // The first device which matches with the OpenGL LUID and node mask. -1 when there was no match. + unsigned int m_maskDevicesActive; // The bitmask marking the actually enabled devices. + std::vector m_devicesActive; + + unsigned int m_iterationIndex; // Tracks which frame is currently raytraced. + unsigned int m_samplesPerPixel; // This is samplesSqrt squared. Rendering end-condition is: m_iterationIndex == m_samplesPerPixel. + + std::vector m_peerConnections; // Bitfield indicating peer-to-peer access between devices. Indexing is m_peerConnections[home] & (1 << peer) + std::vector< std::vector > m_islands; // Vector with vector of device indices (not ordinals) building a peer-to-peer island. + + std::vector m_geometryData; + + NVMLImpl m_nvml; +}; + +#endif // RAYTRACER_H diff --git a/apps/bench_shared/inc/SceneGraph.h b/apps/bench_shared/inc/SceneGraph.h new file mode 100644 index 00000000..486b9c2d --- /dev/null +++ b/apps/bench_shared/inc/SceneGraph.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SCENEGRAPH_H +#define SCENEGRAPH_H + +// For the vector types. +#include + +#include "shaders/vertex_attributes.h" +#include "shaders/vector_math.h" + +#include +#include + +namespace sg +{ + + enum NodeType + { + NT_GROUP, + NT_INSTANCE, + NT_TRIANGLES + }; + + class Node + { + public: + Node(const unsigned int id); + //~Node(); + + virtual sg::NodeType getType() const = 0; + + unsigned int getId() const + { + return m_id; + } + + private: + unsigned int m_id; + }; + + + class Triangles : public Node + { + public: + Triangles(const unsigned int id); + //~Triangles(); + + sg::NodeType getType() const; + + void createBox(); + void createPlane(const unsigned int tessU, const unsigned int tessV, const unsigned int upAxis); + void createSphere(const unsigned int tessU, const unsigned int tessV, const float radius, const float maxTheta); + void createTorus(const unsigned int tessU, const unsigned int tessV, const float innerRadius, const float outerRadius); + void createParallelogram(const float3& position, const float3& vecU, const float3& vecV, const float3& normal); + + void setAttributes(const std::vector& attributes); + const std::vector& getAttributes() const; + + void setIndices(const std::vector& indices); + const std::vector& getIndices() const; + + private: + std::vector m_attributes; + std::vector m_indices; // If m_indices.size() == 0, m_attributes are independent primitives. // Not actually supported in this renderer implementation. + }; + + class Instance : public Node + { + public: + Instance(const unsigned int id); + //~Instance(); + + sg::NodeType getType() const; + + void setTransform(const float m[12]); + const float* getTransform() const; + + void setChild(std::shared_ptr node); + std::shared_ptr getChild(); + + void setMaterial(const int index); + int getMaterial() const; + + void setLight(const int index); + int getLight() const; + + private: + int m_material; + int m_light; + float m_matrix[12]; + std::shared_ptr m_child; // An Instance can either hold a Group or Triangles as child. + }; + + class Group : public Node + { + public: + Group(const unsigned int id); + //~Group(); + + sg::NodeType getType() const; + + void addChild(std::shared_ptr instance); // Groups can only hold Instances. + + size_t getNumChildren() const; + std::shared_ptr getChild(size_t index); + + private: + std::vector< std::shared_ptr > m_children; + }; + +} // namespace sg + +#endif // SCENEGRAPH_H diff --git a/apps/bench_shared/inc/Texture.h b/apps/bench_shared/inc/Texture.h new file mode 100644 index 00000000..608b7f96 --- /dev/null +++ b/apps/bench_shared/inc/Texture.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TEXTURE_H +#define TEXTURE_H + +// Always include this before any OptiX headers. +#include +#include + +#include "inc/Picture.h" + +#include +#include + +// Bitfield encoding of the texture channels. +// These are used to remap user format and user data to the internal format. +// Each four bits hold the channel index of red, green, blue, alpha, and luminance. +// (encoding >> ENC_*_SHIFT) & ENC_MASK gives the channel index if the result is less than 4. +// That encoding allows to automatically swap red and blue, map luminance to RGB (not the other way round though!), +// fill in alpha with input data or force it to one if required. +// 49 remapper functions take care to convert the data types including fixed-point adjustments. + +#define ENC_MASK 0xF + +#define ENC_RED_SHIFT 0 +#define ENC_RED_0 ( 0u << ENC_RED_SHIFT) +#define ENC_RED_1 ( 1u << ENC_RED_SHIFT) +#define ENC_RED_2 ( 2u << ENC_RED_SHIFT) +#define ENC_RED_3 ( 3u << ENC_RED_SHIFT) +#define ENC_RED_NONE (15u << ENC_RED_SHIFT) + +#define ENC_GREEN_SHIFT 4 +#define ENC_GREEN_0 ( 0u << ENC_GREEN_SHIFT) +#define ENC_GREEN_1 ( 1u << ENC_GREEN_SHIFT) +#define ENC_GREEN_2 ( 2u << ENC_GREEN_SHIFT) +#define ENC_GREEN_3 ( 3u << ENC_GREEN_SHIFT) +#define ENC_GREEN_NONE (15u << ENC_GREEN_SHIFT) + +#define ENC_BLUE_SHIFT 8 +#define ENC_BLUE_0 ( 0u << ENC_BLUE_SHIFT) +#define ENC_BLUE_1 ( 1u << ENC_BLUE_SHIFT) +#define ENC_BLUE_2 ( 2u << ENC_BLUE_SHIFT) +#define ENC_BLUE_3 ( 3u << ENC_BLUE_SHIFT) +#define ENC_BLUE_NONE (15u << ENC_BLUE_SHIFT) + +#define ENC_ALPHA_SHIFT 12 +#define ENC_ALPHA_0 ( 0u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_1 ( 1u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_2 ( 2u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_3 ( 3u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_NONE (15u << ENC_ALPHA_SHIFT) + +#define ENC_LUM_SHIFT 16 +#define ENC_LUM_0 ( 0u << ENC_LUM_SHIFT) +#define ENC_LUM_1 ( 1u << ENC_LUM_SHIFT) +#define ENC_LUM_2 ( 2u << ENC_LUM_SHIFT) +#define ENC_LUM_3 ( 3u << ENC_LUM_SHIFT) +#define ENC_LUM_NONE (15u << ENC_LUM_SHIFT) + +#define ENC_CHANNELS_SHIFT 20 +#define ENC_CHANNELS_1 (1u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_2 (2u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_3 (3u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_4 (4u << ENC_CHANNELS_SHIFT) + +#define ENC_TYPE_SHIFT 24 +// These are indices into the remapper table. +#define ENC_TYPE_CHAR ( 0u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_CHAR ( 1u << ENC_TYPE_SHIFT) +#define ENC_TYPE_SHORT ( 2u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_SHORT ( 3u << ENC_TYPE_SHIFT) +#define ENC_TYPE_INT ( 4u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_INT ( 5u << ENC_TYPE_SHIFT) +#define ENC_TYPE_FLOAT ( 6u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNDEFINED (15u << ENC_TYPE_SHIFT) + +// Flags to indicate that special handling is required. +#define ENC_MISC_SHIFT 28 +#define ENC_FIXED_POINT (1u << ENC_MISC_SHIFT) +#define ENC_ALPHA_ONE (2u << ENC_MISC_SHIFT) +// Highest bit set means invalid encoding. +#define ENC_INVALID (8u << ENC_MISC_SHIFT) + + +class Device; + +class Texture +{ +public: + Texture(Device* device); + ~Texture(); + + size_t destroy(Device* device); + + void setTextureDescription(const CUDA_TEXTURE_DESC& descr); + + void setAddressMode(CUaddress_mode s, CUaddress_mode t, CUaddress_mode r); + void setFilterMode(CUfilter_mode filter, CUfilter_mode filterMipmap); + void setReadMode(bool asInteger); + void setSRGB(bool srgb); + void setBorderColor(float r, float g, float b, float a); + void setNormalizedCoords(bool normalized); + void setMaxAnisotropy(unsigned int aniso); + void setMipmapLevelBiasMinMax(float bias, float minimum, float maximum); + + bool create(const Picture* picture, const unsigned int flags); + bool create(const Texture* shared); + + bool update(const Picture* picture); + + Device* getOwner() const; + + unsigned int getWidth() const; + unsigned int getHeight() const; + unsigned int getDepth() const; + + cudaTextureObject_t getTextureObject() const; + + size_t getSizeBytes() const; // Texture memory tracking of CUarrays and CUmipmappedArrays in bytes sent to the cuMemCpy3D(). + + // Specific to spherical environment map. // DAR FIXME Move into a derived class instead. + // Create cumulative distribution function for importance sampling of spherical environment lights. Call last. + void calculateSphericalCDF(const float* rgba); + CUdeviceptr getCDF_U() const; + CUdeviceptr getCDF_V() const; + float getIntegral() const; + +private: + bool create1D(const Picture* picture); + bool create2D(const Picture* picture); + bool create3D(const Picture* picture); + bool createCube(const Picture* picture); + bool createEnv(const Picture* picture); + + bool update1D(const Picture* picture); + bool update2D(const Picture* picture); + bool update3D(const Picture* picture); + bool updateCube(const Picture* picture); + bool updateEnv(const Picture* picture); + +private: + Device* m_owner; // The device which created this Texture. Needed for peer-to-peer sharing, resp. for proper destruction. + + unsigned int m_width; + unsigned int m_height; + unsigned int m_depth; + + unsigned int m_flags; + + unsigned int m_encodingHost; + unsigned int m_encodingDevice; + + CUDA_ARRAY3D_DESCRIPTOR m_descArray3D; + size_t m_sizeBytesPerElement; + + CUDA_RESOURCE_DESC m_resourceDescription; // For the final texture object creation. + + CUDA_TEXTURE_DESC m_textureDescription; // This contains all texture parameters which can be set individually or as a whole. + + // Note that the CUarray or CUmipmappedArray are shared among peer devices, not the texture object! + // This needs to be created per device, which happens in the two create() functions. + CUtexObject m_textureObject; + + // Only one of these is ever used per texture. + CUarray m_d_array; + CUmipmappedArray m_d_mipmappedArray; + + // How much memory the CUarray or CUmipmappedArray required in bytes input to cuMemcpy3d() + // (without m_deviceAttribute.textureAlignment or potential row padding on the device). + size_t m_sizeBytesArray; + + // Specific to spherical environment map. + CUdeviceptr m_d_envCDF_U; + CUdeviceptr m_d_envCDF_V; + float m_integral; +}; + +#endif // TEXTURE_H diff --git a/apps/bench_shared/inc/Timer.h b/apps/bench_shared/inc/Timer.h new file mode 100644 index 00000000..2c13625d --- /dev/null +++ b/apps/bench_shared/inc/Timer.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// This code is part of the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + +#pragma once + +#ifndef TIMER_H +#define TIMER_H + +#if defined(_WIN32) + #include +#else + #include +#endif + + +/*! \brief A simple timer class. + * This timer class can be used on Windows and Linux systems to + * measure time intervals in seconds. + * The timer can be started and stopped several times and accumulates + * time elapsed between the start() and stop() calls. */ +class Timer +{ +public: + //! Default constructor. Constructs a Timer, but does not start it yet. + Timer(); + + //! Default destructor. + ~Timer(); + + //! Starts the timer. + void start(); + + //! Stops the timer. + void stop(); + + //! Resets the timer. + void reset(); + + //! Resets the timer and starts it. + void restart(); + + //! Returns the current time in seconds. + double getTime() const; + + //! Return whether the timer is still running. + bool isRunning() const { return m_running; } + +private: +#if defined(_WIN32) + typedef LARGE_INTEGER Time; +#else + typedef timeval Time; +#endif + +private: + double calcDuration(Time begin, Time end) const; + +private: +#if defined(_WIN32) + LARGE_INTEGER m_freq; +#endif + Time m_begin; + bool m_running; + double m_seconds; +}; + +#endif // TIMER_H diff --git a/apps/bench_shared/inc/TonemapperGUI.h b/apps/bench_shared/inc/TonemapperGUI.h new file mode 100644 index 00000000..843248e4 --- /dev/null +++ b/apps/bench_shared/inc/TonemapperGUI.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TONEMAPPER_GUI_H +#define TONEMAPPER_GUI_H + +struct TonemapperGUI +{ + float gamma; + float whitePoint; + float colorBalance[3]; + float burnHighlights; + float crushBlacks; + float saturation; + float brightness; +}; + +#endif // TONEMAPPER_GUI_H diff --git a/apps/bench_shared/shaders/anyhit.cu b/apps/bench_shared/shaders/anyhit.cu new file mode 100644 index 00000000..b51dc47d --- /dev/null +++ b/apps/bench_shared/shaders/anyhit.cu @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +//#include "vertex_attributes.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +// One anyhit program for the radiance ray for all materials with cutout opacity! +extern "C" __global__ void __anyhit__radiance_cutout() +{ + GeometryInstanceData* theData = reinterpret_cast(optixGetSbtDataPointer()); + + const MaterialDefinition& material = sysData.materialDefinitions[theData->idMaterial]; + + if (material.textureCutout != 0) + { + // Cast the CUdeviceptr to the actual format for Triangles geometry. + const uint3* indices = reinterpret_cast(theData->indices); + const TriangleAttributes* attributes = reinterpret_cast(theData->attributes); + + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const uint3 tri = indices[thePrimitiveIndex]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + const float3 texcoord = attributes[tri.x].texcoord * alpha + + attributes[tri.y].texcoord * theBarycentrics.x + + attributes[tri.z].texcoord * theBarycentrics.y; + + const float opacity = intensity(make_float3(tex2D(material.textureCutout, texcoord.x, texcoord.y))); + + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Stochastic alpha test to get an alpha blend effect. + if (opacity < 1.0f && opacity <= rng(thePrd->seed)) // No need to calculate an expensive random number if the test is going to fail anyway. + { + optixIgnoreIntersection(); + } + } +} + + +// The shadow ray program for all materials with no cutout opacity. +extern "C" __global__ void __anyhit__shadow() +{ + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->flags |= FLAG_SHADOW; // Visbility check failed. + + optixTerminateRay(); +} + + +extern "C" __global__ void __anyhit__shadow_cutout() // For the radiance ray type. +{ + GeometryInstanceData* theData = reinterpret_cast(optixGetSbtDataPointer()); + + const MaterialDefinition& material = sysData.materialDefinitions[theData->idMaterial]; + + float opacity = 1.0f; + + if (material.textureCutout != 0) + { + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + const uint3* indices = reinterpret_cast(theData->indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData->attributes); + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + const float3 texcoord = attributes[tri.x].texcoord * alpha + + attributes[tri.y].texcoord * theBarycentrics.x + + attributes[tri.z].texcoord * theBarycentrics.y; + + opacity = intensity(make_float3(tex2D(material.textureCutout, texcoord.x, texcoord.y))); + } + + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Stochastic alpha test to get an alpha blend effect. + if (opacity < 1.0f && opacity <= rng(thePrd->seed)) // No need to calculate an expensive random number if the test is going to fail anyway. + { + optixIgnoreIntersection(); + } + else + { + thePrd->flags |= FLAG_SHADOW; + optixTerminateRay(); + } +} diff --git a/apps/bench_shared/shaders/bxdf_diffuse.cu b/apps/bench_shared/shaders/bxdf_diffuse.cu new file mode 100644 index 00000000..d3350730 --- /dev/null +++ b/apps/bench_shared/shaders/bxdf_diffuse.cu @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +__forceinline__ __device__ void alignVector(const float3& axis, float3& w) +{ + // Align w with axis. + const float s = copysignf(1.0f, axis.z); + w.z *= s; + const float3 h = make_float3(axis.x, axis.y, axis.z + s); + const float k = dot(w, h) / (1.0f + fabsf(axis.z)); + w = k * h - w; +} + +__forceinline__ __device__ void unitSquareToCosineHemisphere(const float2 sample, const float3& axis, float3& w, float& pdf) +{ + // Choose a point on the local hemisphere coordinates about +z. + const float theta = 2.0f * M_PIf * sample.x; + const float r = sqrtf(sample.y); + w.x = r * cosf(theta); + w.y = r * sinf(theta); + w.z = 1.0f - w.x * w.x - w.y * w.y; + w.z = (0.0f < w.z) ? sqrtf(w.z) : 0.0f; + + pdf = w.z * M_1_PIf; + + // Align with axis. + alignVector(axis, w); +} + +// BRDF Diffuse (Lambert) + +extern "C" __device__ void __direct_callable__sample_brdf_diffuse(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Cosine weighted hemisphere sampling for Lambert material. + unitSquareToCosineHemisphere(rng2(prd->seed), state.normal, prd->wi, prd->pdf); + + if (prd->pdf <= 0.0f || dot(prd->wi, state.normalGeo) <= 0.0f) + { + prd->flags |= FLAG_TERMINATE; + return; + } + + // This would be the universal implementation for an arbitrary sampling of a diffuse surface. + // prd->f_over_pdf = state.albedo * (M_1_PIf * fabsf(dot(prd->wi, state.normal)) / prd->pdf); + + // PERF Since the cosine-weighted hemisphere distribution is a perfect importance-sampling of the Lambert material, + // the whole term ((M_1_PIf * fabsf(dot(prd->wi, state.normal)) / prd->pdf) is always 1.0f here! + prd->f_over_pdf = state.albedo; + + prd->flags |= FLAG_DIFFUSE; // Direct lighting will be done with multiple importance sampling. +} + +// The parameter wiL is the lightSample.direction (direct lighting), not the next ray segment's direction prd.wi (indirect lighting). +extern "C" __device__ float4 __direct_callable__eval_brdf_diffuse(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +{ + const float3 f = state.albedo * M_1_PIf; + const float pdf = fmaxf(0.0f, dot(wiL, state.normal) * M_1_PIf); + + return make_float4(f, pdf); +} + diff --git a/apps/bench_shared/shaders/bxdf_ggx_smith.cu b/apps/bench_shared/shaders/bxdf_ggx_smith.cu new file mode 100644 index 00000000..a34a9f21 --- /dev/null +++ b/apps/bench_shared/shaders/bxdf_ggx_smith.cu @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + +// "Microfacet Models for Refraction through Rough Surfaces" - Walter, Marschner, Li, Torrance. 2007 +// "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" - Eric Heitz + +// This function evaluates a Fresnel dielectric function when the transmitting cosine ("cost") +// is unknown and the incident index of refraction is assumed to be 1.0f. +// \param et The transmitted index of refraction. +// \param costIn The cosine of the angle between the incident direction and normal direction. +__forceinline__ __device__ float evaluateFresnelDielectric(const float et, const float cosIn) +{ + const float cosi = fabsf(cosIn); + + float sint = 1.0f - cosi * cosi; + sint = (0.0f < sint) ? sqrtf(sint) / et : 0.0f; + + // Handle total internal reflection. + if (1.0f < sint) + { + return 1.0f; + } + + float cost = 1.0f - sint * sint; + cost = (0.0f < cost) ? sqrtf(cost) : 0.0f; + + const float et_cosi = et * cosi; + const float et_cost = et * cost; + + const float rPerpendicular = (cosi - et_cost) / (cosi + et_cost); + const float rParallel = (et_cosi - cost) / (et_cosi + cost); + + const float result = (rParallel * rParallel + rPerpendicular * rPerpendicular) * 0.5f; + + return (result <= 1.0f) ? result : 1.0f; +} + + +// Optimized version to calculate D and PDF reusing shared calculations. +__forceinline__ __device__ float2 distribution_d_pdf(const float ax, const float ay, const float3& wm) +{ + if (DENOMINATOR_EPSILON < wm.z) // Heaviside function: X_plus(wm * wg). (wm is in tangent space.) + { + const float cosThetaSqr = wm.z * wm.z; + const float tanThetaSqr = (1.0f - cosThetaSqr) / cosThetaSqr; + + const float phiM = atan2f(wm.y, wm.x); + const float cosPhiM = cosf(phiM); + const float sinPhiM = sinf(phiM); + + const float term = 1.0f + tanThetaSqr * ((cosPhiM * cosPhiM) / (ax * ax) + (sinPhiM * sinPhiM) / (ay * ay)); + + const float d = 1.0f / (M_PIf * ax * ay * cosThetaSqr * cosThetaSqr * term * term); // Heitz, Formula (85) + const float pdf = d * wm.z; // PDF with respect to the half-direction. + + return make_float2(d, pdf); + } + return make_float2(0.0f); +} + +// Return a sample direction in local tangent space coordinates. +__forceinline__ __device__ float3 distribution_sample(const float ax, const float ay, const float u1, const float u2) +{ + // Made isotropic to ay. Output vector scales .x accordingly. + const float theta = atanf(ay * sqrtf(u1) / sqrtf(1.0f - u1)); // Walter, Formula (35). + const float phi = 2.0f * M_PIf * u2; // Walter, Formula (36). + const float sinTheta = sinf(theta); + return normalize(make_float3(cosf(phi) * sinTheta * ax / ay, // Heitz, Formula (77) + sinf(phi) * sinTheta, + cosf(theta))); +} + +// "Microfacet Models for Refraction through Rough Surfaces" - Walter, Marschner, Li, Torrance. +// PERF Using this because it's faster than the approximation below. +__forceinline__ __device__ float smith_G1(const float alpha, const float3& w, const float3& wm) +{ + const float w_wm = dot(w, wm); + if (w_wm * w.z <= 0.0f) // X_plus(v * m / v * n) from Walter, Formula (34). // PERF Checking the sign with a multiplication here. + { + return 0.0f; + } + const float cosThetaSqr = w.z * w.z; + const float sinThetaSqr = 1.0f - cosThetaSqr; + //const float tanTheta = (0.0f < sinThetaSqr) ? sqrtf(sinThetaSqr) / w.z : 0.0f; // PERF Remove the sqrtf() by calculating tanThetaSqr here + //const float invA = alpha * tanTheta; // because this is squared below: invASqr = alpha * alpha * tanThetaSqr; + //const float lambda = (-1.0f + sqrtf(1.0f + invA * invA)) * 0.5f; // Heitz, Formula (86) + //return 1.0f / (1.0f + lambda); // Heitz, below Formula (69) + const float tanThetaSqr = (0.0f < sinThetaSqr) ? sinThetaSqr / cosThetaSqr : 0.0f; + const float invASqr = alpha * alpha * tanThetaSqr; + return 2.0f / (1.0f + sqrtf(1.0f + invASqr)); // Optimized version is Walter, Formula (34) +} + +// Approximation from "Microfacet Models for Refraction through Rough Surfaces" - Walter, Marschner, Li, Torrance. +//__forceinline__ __device__ float smith_G1(const float alpha, const float3& w, const float3& wm) +//{ +// const float w_wm = optix::dot(w, wm); +// if (w_wm * w.z <= 0.0f) // X_plus(v * m / v * n) from Walter, Formula (34). // PERF Checking the sign with a multiplication here. +// { +// return 0.0f; +// } +// const float t = 1.0f - w.z * w.z; +// const float tanTheta = (0.0f < t) ? sqrtf(t) / w.z : 0.0f; +// if (tanTheta == 0.0f) +// { +// return 1.0f; +// } +// const float a = 1.0f / (tanTheta * alpha); +// if (1.6f <= a) +// { +// return 1.0f; +// } +// const float aSqr = a * a; +// return (3.535f * a + 2.181f * aSqr) / (1.0f + 2.276f * a + 2.577f * aSqr); // Walter, Formula (27) used for Heitz, Formula (83) +//} + +__forceinline__ __device__ float distribution_G(const float ax, const float ay, const float3& wo, const float3& wi, const float3& wm) +{ + float phi = atan2f(wo.y, wo.x); + float c = cosf(phi); + float s = sinf(phi); + float alpha = sqrtf(c * c * ax * ax + s * s * ay * ay); // Heitz, Formula (80) for wo + + const float g = smith_G1(alpha, wo, wm); + + phi = atan2f(wi.y, wi.x); + c = cosf(phi); + s = sinf(phi); + alpha = sqrtf(c * c * ax * ax + s * s * ay * ay); // Heitz, Formula (80) for wi. + + return g * smith_G1(alpha, wi, wm); +} + +// ########## BRDF GGX with Smith shadowing + +extern "C" __device__ void __direct_callable__sample_brdf_ggx_smith(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Sample a microfacet normal in local space, which effectively is a tangent space coordinate. + const float2 sample = rng2(prd->seed); + + const float3 wm = distribution_sample(material.roughness.x, + material.roughness.y, + sample.x, + sample.y); + + const TBN tangentSpace(state.tangent, state.normal); // Tangent space transformation, handles anisotropic rotation. + + const float3 wh = tangentSpace.transformToWorld(wm); // wh is the microfacet normal in world space coordinates! + + prd->wi = reflect(-prd->wo, wh); + + if (dot(prd->wi, state.normalGeo) <= 0.0f) // Do not sample opaque materials below the geometric surface. + { + prd->flags |= FLAG_TERMINATE; + return; + } + + const float3 wo = tangentSpace.transformToLocal(prd->wo); + const float3 wi = tangentSpace.transformToLocal(prd->wi); + + const float wi_wh = dot(prd->wi, wh); + + if (wo.z <= 0.0f || wi.z <= 0.0f || wi_wh <= 0.0f) + { + prd->flags |= FLAG_TERMINATE; + return; + } + + const float2 D_PDF = distribution_d_pdf(material.roughness.x, + material.roughness.y, + wm); + if (D_PDF.y <= 0.0f) + { + prd->flags |= FLAG_TERMINATE; + return; + } + + const float G = distribution_G(material.roughness.x, + material.roughness.y, + wo, wi, wm); + + // Watch out: PBRT2 puts the factor 1.0f / (4.0f * cosThetaH) into the pdf() functions. + // This is the density function with respect to the light vector. + prd->pdf = D_PDF.y / (4.0f * wi_wh); + //prd->f_over_pdf = state.albedo * (fabsf(dot(prd->wi, state->normal)) * D_PDF.x * G / (4.0f * wo.z * wi.z * prd->pdf)); + prd->f_over_pdf = state.albedo * (G * D_PDF.x * wi_wh / (D_PDF.y * wo.z)); // Optimized version with all factors canceled out. + + prd->flags |= FLAG_DIFFUSE; // Can handle direct lighting. +} + +// When reaching this function, the roughness values are clamped to a minimal working value already, +// so that anisotropic roughness can simply be calculated without additional checks! +extern "C" __device__ float4 __direct_callable__eval_brdf_ggx_smith(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +{ + const TBN tangentSpace(state.tangent, state.normal); // Tangent space transformation, handles anisotropic rotation. + + const float3 wo = tangentSpace.transformToLocal(prd->wo); + const float3 wi = tangentSpace.transformToLocal(wiL); + + if (wo.z <= 0.0f || wi.z <= 0.0f) // Either vector on the other side of the node.normal hemisphere? + { + return make_float4(0.0f); + } + + float3 wm = wo + wi; // The half-vector is the microfacet normal, in tangent space + if (isNull(wm)) // Collinear in opposing directions? + { + return make_float4(0.0f); + } + + wm = normalize(wm); + + const float2 D_PDF = distribution_d_pdf(material.roughness.x, + material.roughness.y, + wm); + + const float G = distribution_G(material.roughness.x, + material.roughness.y, + wo, wi, wm); + + const float3 f = state.albedo * (D_PDF.x * G / (4.0f * wo.z * wi.z)); + + // Watch out: PBRT2 puts the factor 1.0f / (4.0f * cosThetaH) into the pdf() functions. + // This is the density function with respect to the light vector. + const float pdf = D_PDF.y / (4.0f * dot(wi, wm)); + + return make_float4(f, pdf); +} + +// ########## BSDF GGX with Smith shadowing + +extern "C" __device__ void __direct_callable__sample_bsdf_ggx_smith(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Return the current material's absorption coefficient and ior to the integrator to be able to support nested materials. + prd->absorption_ior = make_float4(material.absorption, material.ior); + + // Need to figure out here which index of refraction to use if the ray is already inside some refractive medium. + // This needs to happen with the original FLAG_FRONTFACE condition to find out from which side of the geometry we're looking! + // ior.xy are the current volume's IOR and the surrounding volume's IOR. + // Thin-walled materials have no volume, always use the frontface eta for them! + const float eta = (prd->flags & (FLAG_FRONTFACE | FLAG_THINWALLED)) + ? prd->absorption_ior.w / prd->ior.x + : prd->ior.y / prd->absorption_ior.w; + + // Sample a microfacet normal in local space, which effectively is a tangent space coordinate. + const float2 sample = rng2(prd->seed); + + const float3 wm = distribution_sample(material.roughness.x, + material.roughness.y, + sample.x, + sample.y); + + const TBN tangentSpace(state.tangent, state.normal); // Tangent space transformation, handles anisotropic rotation. + + const float3 wh = tangentSpace.transformToWorld(wm); // wh is the microfacet normal in world space coordinates! + + + const float3 R = reflect(-prd->wo, wh); + + float reflective = 1.0f; + if (refract(prd->wi, -prd->wo, wh, eta)) + { + if (prd->flags & FLAG_THINWALLED) + { + // DAR FIXME The resulting vector isn't necessarily on the other side of the geometric normal, but should be! + prd->wi = reflect(R, state.normal); // Flip the vector to the other side of the normal. + } + // Note, not using fabs() on the cosine to get the refract side correct. + // Total internal reflection will leave this reflection probability at 1.0f. + reflective = evaluateFresnelDielectric(eta, dot(prd->wo, wh)); + } + + const float pseudo = rng(prd->seed); + if (pseudo < reflective) + { + prd->wi = R; // Fresnel reflection or total internal reflection. + } + else if (!(prd->flags & FLAG_THINWALLED)) // Only non-thinwalled materials have a volume and transmission events. + { + prd->flags |= FLAG_TRANSMISSION; + } + + // No Fresnel factor here. The probability to pick one or the other side took care of that. + prd->f_over_pdf = state.albedo; + prd->pdf = 1.0f; // Not 0.0f to make sure the path is not terminated. Otherwise unused for specular events. +} + +//extern "C" __device__ float4 __direct_callable__eval_bsdf_ggx_smith(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +//{ +// // The implementation handles this as specular continuation. Can reuse the eval_brdf_specular() implementation. +// return make_float4(0.0f); +//} diff --git a/apps/bench_shared/shaders/bxdf_specular.cu b/apps/bench_shared/shaders/bxdf_specular.cu new file mode 100644 index 00000000..f325eeec --- /dev/null +++ b/apps/bench_shared/shaders/bxdf_specular.cu @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + +// This function evaluates a Fresnel dielectric function when the transmitting cosine ("cost") +// is unknown and the incident index of refraction is assumed to be 1.0f. +// \param et The transmitted index of refraction. +// \param costIn The cosine of the angle between the incident direction and normal direction. +__forceinline__ __device__ float evaluateFresnelDielectric(const float et, const float cosIn) +{ + const float cosi = fabsf(cosIn); + + float sint = 1.0f - cosi * cosi; + sint = (0.0f < sint) ? sqrtf(sint) / et : 0.0f; + + // Handle total internal reflection. + if (1.0f < sint) + { + return 1.0f; + } + + float cost = 1.0f - sint * sint; + cost = (0.0f < cost) ? sqrtf(cost) : 0.0f; + + const float et_cosi = et * cosi; + const float et_cost = et * cost; + + const float rPerpendicular = (cosi - et_cost) / (cosi + et_cost); + const float rParallel = (et_cosi - cost) / (et_cosi + cost); + + const float result = (rParallel * rParallel + rPerpendicular * rPerpendicular) * 0.5f; + + return (result <= 1.0f) ? result : 1.0f; +} + +// ########## BRDF Specular (tinted mirror) + +extern "C" __device__ void __direct_callable__sample_brdf_specular(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + prd->wi = reflect(-prd->wo, state.normal); + + if (dot(prd->wi, state.normalGeo) <= 0.0f) // Do not sample opaque materials below the geometric surface. + { + prd->flags |= FLAG_TERMINATE; + return; + } + + prd->f_over_pdf = state.albedo; + prd->pdf = 1.0f; // Not 0.0f to make sure the path is not terminated. Otherwise unused for specular events. +} + +// This function will be used for all specular materials. +// This is actually never reached in this simply material system, because the FLAG_DIFFUSE flag is not set when a specular BSDF is has been sampled. +extern "C" __device__ float4 __direct_callable__eval_brdf_specular(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +{ + return make_float4(0.0f); +} + +// ########## BSDF Specular (glass etc.) + +extern "C" __device__ void __direct_callable__sample_bsdf_specular(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Return the current material's absorption coefficient and ior to the integrator to be able to support nested materials. + prd->absorption_ior = make_float4(material.absorption, material.ior); + + // Need to figure out here which index of refraction to use if the ray is already inside some refractive medium. + // This needs to happen with the original FLAG_FRONTFACE condition to find out from which side of the geometry we're looking! + // ior.xy are the current volume's IOR and the surrounding volume's IOR. + // Thin-walled materials have no volume, always use the frontface eta for them! + const float eta = (prd->flags & (FLAG_FRONTFACE | FLAG_THINWALLED)) + ? prd->absorption_ior.w / prd->ior.x + : prd->ior.y / prd->absorption_ior.w; + + const float3 R = reflect(-prd->wo, state.normal); + + float reflective = 1.0f; + + if (refract(prd->wi, -prd->wo, state.normal, eta)) + { + if (prd->flags & FLAG_THINWALLED) + { + prd->wi = -prd->wo; // Straight through, no volume. + } + // Total internal reflection will leave this reflection probability at 1.0f. + reflective = evaluateFresnelDielectric(eta, dot(prd->wo, state.normal)); + } + + const float pseudo = rng(prd->seed); + if (pseudo < reflective) + { + prd->wi = R; // Fresnel reflection or total internal reflection. + } + else if (!(prd->flags & FLAG_THINWALLED)) // Only non-thinwalled materials have a volume and transmission events. + { + prd->flags |= FLAG_TRANSMISSION; + } + + // No Fresnel factor here. The probability to pick one or the other side took care of that. + prd->f_over_pdf = state.albedo; + prd->pdf = 1.0f; // Not 0.0f to make sure the path is not terminated. Otherwise unused for specular events. +} + +// PERF Same as every specular material. +//extern "C" __device__ float4 __direct_callable__eval_bsdf_specular(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +//{ +// return make_float4(0.0f); +//} + diff --git a/apps/bench_shared/shaders/camera_definition.h b/apps/bench_shared/shaders/camera_definition.h new file mode 100644 index 00000000..e0adf352 --- /dev/null +++ b/apps/bench_shared/shaders/camera_definition.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CAMERA_DEFINITION_H +#define CAMERA_DEFINITION_H + +struct CameraDefinition +{ + float3 P; + float3 U; + float3 V; + float3 W; +}; + +#endif // CAMERA_DEFINITION_H diff --git a/apps/bench_shared/shaders/closesthit.cu b/apps/bench_shared/shaders/closesthit.cu new file mode 100644 index 00000000..e56518cb --- /dev/null +++ b/apps/bench_shared/shaders/closesthit.cu @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +#include "vertex_attributes.h" +#include "function_indices.h" +#include "material_definition.h" +#include "light_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +// Get the 3x4 object to world transform and its inverse from a two-level hierarchy. +__forceinline__ __device__ void getTransforms(float4* mW, float4* mO) +{ + OptixTraversableHandle handle = optixGetTransformListHandle(0); + + const float4* tW = optixGetInstanceTransformFromHandle(handle); + const float4* tO = optixGetInstanceInverseTransformFromHandle(handle); + + mW[0] = tW[0]; + mW[1] = tW[1]; + mW[2] = tW[2]; + + mO[0] = tO[0]; + mO[1] = tO[1]; + mO[2] = tO[2]; +} + +// Functions to get the individual transforms in case only one of them is needed. + +__forceinline__ __device__ void getTransformObjectToWorld(float4* mW) +{ + OptixTraversableHandle handle = optixGetTransformListHandle(0); + + const float4* tW = optixGetInstanceTransformFromHandle(handle); + + mW[0] = tW[0]; + mW[1] = tW[1]; + mW[2] = tW[2]; +} + +__forceinline__ __device__ void getTransformWorldToObject(float4* mO) +{ + OptixTraversableHandle handle = optixGetTransformListHandle(0); + + const float4* tO = optixGetInstanceInverseTransformFromHandle(handle); + + mO[0] = tO[0]; + mO[1] = tO[1]; + mO[2] = tO[2]; +} + + +// Matrix3x4 * point. v.w == 1.0f +__forceinline__ __device__ float3 transformPoint(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z + m[0].w; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z + m[1].w; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z + m[2].w; + + return r; +} + +// Matrix3x4 * vector. v.w == 0.0f +__forceinline__ __device__ float3 transformVector(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z; + + return r; +} + +// InverseMatrix3x4^T * normal. v.w == 0.0f +// Get the inverse matrix as input and applies it as inverse transpose. +__forceinline__ __device__ float3 transformNormal(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + r.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + r.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + + return r; +} + + +extern "C" __global__ void __closesthit__radiance() +{ + GeometryInstanceData* theData = reinterpret_cast(optixGetSbtDataPointer()); + + // Cast the CUdeviceptr to the actual format for Triangles geometry. + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const uint3* indices = reinterpret_cast(theData->indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData->attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + const float3 ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + const float3 tg = attr0.tangent * alpha + attr1.tangent * theBarycentrics.x + attr2.tangent * theBarycentrics.y; + const float3 ns = attr0.normal * alpha + attr1.normal * theBarycentrics.x + attr2.normal * theBarycentrics.y; + + // DAR PERF This State lies in memory. It's more efficient to hold the data in registers. + // Problem is that more advanced material systems need the State all the time. + State state; // All in world space coordinates! + + state.texcoord = attr0.texcoord * alpha + attr1.texcoord * theBarycentrics.x + attr2.texcoord * theBarycentrics.y; + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(objectToWorld, worldToObject); + + state.normalGeo = normalize(transformNormal(worldToObject, ng)); + state.tangent = normalize(transformVector(objectToWorld, tg)); + state.normal = normalize(transformNormal(worldToObject, ns)); + + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->distance = optixGetRayTmax(); // Return the current path segment distance, needed for absorption calculations in the integrator. + + //thePrd->pos = optixGetWorldRayOrigin() + optixGetWorldRayDirection() * optixGetRayTmax(); + thePrd->pos += thePrd->wi * thePrd->distance; // DEBUG Check which version is more efficient. + + // Explicitly include edge-on cases as frontface condition! + // Keeps the material stack from overflowing at silhouettes. + // Prevents that silhouettes of thin-walled materials use the backface material. + // Using the true geometry normal attribute as originally defined on the frontface! + thePrd->flags |= (0.0f <= dot(thePrd->wo, state.normalGeo)) ? FLAG_FRONTFACE : 0; + + if ((thePrd->flags & FLAG_FRONTFACE) == 0) // Looking at the backface? + { + // Means geometric normal and shading normal are always defined on the side currently looked at. + // This gives the backfaces of opaque BSDFs a defined result. + state.normalGeo = -state.normalGeo; + state.tangent = -state.tangent; + state.normal = -state.normal; + // Explicitly DO NOT recalculate the frontface condition! + } + + thePrd->radiance = make_float3(0.0f); + + // When hitting a geometric light, evaluate the emission first, because this needs the previous diffuse hit's pdf. + const int idLight = theData->idLight; + + if (0 <= idLight && (thePrd->flags & FLAG_FRONTFACE)) // This material is emissive and we're looking at the front face. + { + const float cosTheta = dot(thePrd->wo, state.normalGeo); + if (DENOMINATOR_EPSILON < cosTheta) + { + const LightDefinition& light = sysData.lightDefinitions[idLight]; + + float3 emission = light.emission; + +#if USE_NEXT_EVENT_ESTIMATION + const float lightPdf = (thePrd->distance * thePrd->distance) / (light.area * cosTheta); // This assumes the light.area is greater than zero. + + // If it's an implicit light hit from a diffuse scattering event and the light emission was not returning a zero pdf (e.g. backface or edge on). + if ((thePrd->flags & FLAG_DIFFUSE) && DENOMINATOR_EPSILON < lightPdf) + { + // Scale the emission with the power heuristic between the initial BSDF sample pdf and this implicit light sample pdf. + emission *= powerHeuristic(thePrd->pdf, lightPdf); + } +#endif // USE_NEXT_EVENT_ESTIMATION + + thePrd->radiance = emission; + + // PERF End the path when hitting a light. Emissive materials with a non-black BSDF would normally just continue. + thePrd->flags |= FLAG_TERMINATE; + return; + } + } + + // Start fresh with the next BSDF sample. (Either of these values remaining zero is an end-of-path condition.) + // The pdf of the previous evene was needed for the emission calculation above. + thePrd->f_over_pdf = make_float3(0.0f); + thePrd->pdf = 0.0f; + + const MaterialDefinition& material = sysData.materialDefinitions[theData->idMaterial]; + + state.albedo = material.albedo; + + if (material.textureAlbedo != 0) + { + const float3 texColor = make_float3(tex2D(material.textureAlbedo, state.texcoord.x, state.texcoord.y)); + + // Modulate the incoming color with the texture. + state.albedo *= texColor; // linear color, resp. if the texture has been uint8 and readmode set to use sRGB, then sRGB. + //state.albedo *= powf(texColor, 2.2f); // sRGB gamma correction done manually. + } + + // Only the last diffuse hit is tracked for multiple importance sampling of implicit light hits. + thePrd->flags = (thePrd->flags & ~FLAG_DIFFUSE) | FLAG_HIT | material.flags; // FLAG_THINWALLED can be set directly from the material. + + // Sample a new path direction. + const int indexBSDF = NUM_LENS_SHADERS + NUM_LIGHT_TYPES + material.indexBSDF * 2; + + optixDirectCall(indexBSDF, material, state, thePrd); + +#if USE_NEXT_EVENT_ESTIMATION + // Direct lighting if the sampled BSDF was diffuse and any light is in the scene. + const int numLights = sysData.numLights; + if ((thePrd->flags & FLAG_DIFFUSE) && 0 < numLights) + { + // Sample one of many lights. + const float2 sample = rng2(thePrd->seed); // Use lower dimension samples for the position. (Irrelevant for the LCG). + + // The caller picks the light to sample. Make sure the index stays in the bounds of the sysData.lightDefinitions array. + const int indexLight = (1 < numLights) ? clamp(static_cast(floorf(rng(thePrd->seed) * numLights)), 0, numLights - 1) : 0; + + const LightDefinition& light = sysData.lightDefinitions[indexLight]; + + const int indexCallable = NUM_LENS_SHADERS + light.type; + + LightSample lightSample = optixDirectCall(indexCallable, light, thePrd->pos, sample); + + if (0.0f < lightSample.pdf) // Useful light sample? + { + // Evaluate the BSDF in the light sample direction. Normally cheaper than shooting rays. + // Returns BSDF f in .xyz and the BSDF pdf in .w + // BSDF eval function is one index after the sample fucntion. + const float4 bsdf_pdf = optixDirectCall(indexBSDF + 1, material, state, thePrd, lightSample.direction); + + if (0.0f < bsdf_pdf.w && isNotNull(make_float3(bsdf_pdf))) + { + // Pass the current payload registers through to the shadow ray. + unsigned int p0 = optixGetPayload_0(); + unsigned int p1 = optixGetPayload_1(); + + // Note that the sysData.sceneEpsilon is applied on both sides of the shadow ray [t_min, t_max] interval + // to prevent self-intersections with the actual light geometry in the scene. + optixTrace(sysData.topObject, + thePrd->pos, lightSample.direction, // origin, direction + sysData.sceneEpsilon, lightSample.distance - sysData.sceneEpsilon, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT, // The shadow ray type only uses anyhit programs. + RAYTYPE_SHADOW, NUM_RAYTYPES, RAYTYPE_SHADOW, + p0, p1); // Pass through thePrd to the shadow ray. It needs the seed and sets flags. + + if ((thePrd->flags & FLAG_SHADOW) == 0) // Shadow flag not set? + { + if (thePrd->flags & FLAG_VOLUME) // Supporting nested materials includes having lights inside a volume. + { + // Calculate the transmittance along the light sample's distance in case it's inside a volume. + // The light must be in the same volume or it would have been shadowed. + lightSample.emission *= expf(-lightSample.distance * thePrd->sigma_t); + } + + const float weightMis = powerHeuristic(lightSample.pdf, bsdf_pdf.w); + + thePrd->radiance += make_float3(bsdf_pdf) * lightSample.emission * (weightMis * dot(lightSample.direction, state.normal) / lightSample.pdf); + } + } + } + } +#endif // USE_NEXT_EVENT_ESTIMATION +} diff --git a/apps/bench_shared/shaders/compositor.cu b/apps/bench_shared/shaders/compositor.cu new file mode 100644 index 00000000..257ff4f3 --- /dev/null +++ b/apps/bench_shared/shaders/compositor.cu @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "config.h" + +#include "compositor_data.h" +#include "vector_math.h" + +// Compositor kernel to copy the tiles in the texelBuffer into the final outputBuffer location. +extern "C" __global__ void compositor(CompositorData* args) +{ + const unsigned int xLaunch = blockDim.x * blockIdx.x + threadIdx.x; + const unsigned int yLaunch = blockDim.y * blockIdx.y + threadIdx.y; + + if (yLaunch < args->resolution.y) + { + // First calculate block coordinates of this launch index. + // That is the launch index divided by the tile dimensions. (No operator>>() on vectors?) + const unsigned int xBlock = xLaunch >> args->tileShift.x; + const unsigned int yBlock = yLaunch >> args->tileShift.y; + + // Each device needs to start at a different column and each row should start with a different device. + const unsigned int xTile = xBlock * args->deviceCount + ((args->deviceIndex + yBlock) % args->deviceCount); + + // The horizontal pixel coordinate is: tile coordinate * tile width + launch index % tile width. + const unsigned int xPixel = xTile * args->tileSize.x + (xLaunch & (args->tileSize.x - 1)); // tileSize needs to be power-of-two for this modulo operation. + + if (xPixel < args->resolution.x) + { + const float4 *src = reinterpret_cast(args->tileBuffer); + float4 *dst = reinterpret_cast(args->outputBuffer); + + // The src location needs to be calculated with the original launch width, because gridDim.x * blockDim.x might be different. + dst[yLaunch * args->resolution.x + xPixel] = src[yLaunch * args->launchWidth + xLaunch]; // Copy one float4 per launch index. + } + } +} diff --git a/apps/bench_shared/shaders/compositor_data.h b/apps/bench_shared/shaders/compositor_data.h new file mode 100644 index 00000000..ec67ac34 --- /dev/null +++ b/apps/bench_shared/shaders/compositor_data.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef COMPOSITOR_DATA_H +#define COMPOSITOR_DATA_H + +#include + +struct CompositorData +{ + // 8 byte alignment + CUdeviceptr outputBuffer; + CUdeviceptr tileBuffer; + + int2 resolution; // The actual rendering resolution. Independent from the launch dimensions for some rendering strategies. + int2 tileSize; // Example: make_int2(8, 4) for 8x4 tiles. Must be a power of two to make the division a right-shift. + int2 tileShift; // Example: make_int2(3, 2) for the integer division by tile size. That actually makes the tileSize redundant. + + // 4 byte alignment + int launchWidth; // The orignal launch width. Needed to calculate the source data index. The compositor launch gridDim.x * blockDim.x might be different! + int deviceCount; // Number of devices doing the rendering. + int deviceIndex; // Device index to be able to distinguish the individual devices in a multi-GPU environment. +}; + +#endif // COMPOSITOR_DATA_H diff --git a/apps/bench_shared/shaders/config.h b/apps/bench_shared/shaders/config.h new file mode 100644 index 00000000..d99cbd9f --- /dev/null +++ b/apps/bench_shared/shaders/config.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// This header with defines is included in all shaders +// to be able to switch different code paths at a central location. +// Changing any setting here will rebuild the whole solution. + +#pragma once + +#ifndef CONFIG_H +#define CONFIG_H + +// Used to shoot rays without distance limit. +#define RT_DEFAULT_MAX 1.e27f + +// Scales the m_sceneEpsilonFactor to give the effective SystemData::sceneEpsilon. +#define SCENE_EPSILON_SCALE 1.0e-7f + +// Prevent that division by very small floating point values results in huge values, for example dividing by pdf. +#define DENOMINATOR_EPSILON 1.0e-6f + +// If both anisotropic roughness values fall below this threshold, the BSDF switches to specular. +#define MICROFACET_MIN_ROUGHNESS 0.0014142f + +// 0 == Brute force path tracing without next event estimation (direct lighting). // Debug setting to compare lighting results. +// 1 == Next event estimation per path vertex (direct lighting) and using MIS with power heuristic. // Default. +#define USE_NEXT_EVENT_ESTIMATION 1 + +// 0 == All debug features disabled. Code optimization level on maximum. (Benchmark only in this mode!) +// 1 == All debug features enabled. Code generated with full debug info. (Really only for debugging, big performance hit!) +#define USE_DEBUG_EXCEPTIONS 0 + +// 0 == Disable clock() usage and time view display. +// 1 == Enable clock() usage and time view display. +#define USE_TIME_VIEW 0 + +// The m_clockFactor GUI value is scaled by this. +// With a default of m_clockFactor = 1000 this means a million clocks will be value 1.0 in the alpha channel +// which is used as 1D texture coordinate inside the GLSL display shader and results in white in the temperature ramp texture. +#define CLOCK_FACTOR_SCALE 1.0e-9f + +// These defines are used in Application, Rasterizer, Raytracer and Device. This is the only header included by all. +#define INTEROP_MODE_OFF 0 +#define INTEROP_MODE_TEX 1 +#define INTEROP_MODE_PBO 2 + +#endif // CONFIG_H diff --git a/apps/bench_shared/shaders/exception.cu b/apps/bench_shared/shaders/exception.cu new file mode 100644 index 00000000..7eb65c12 --- /dev/null +++ b/apps/bench_shared/shaders/exception.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" + +extern "C" __constant__ SystemData sysData; + +extern "C" __global__ void __exception__all() +{ + //const uint3 theLaunchDim = optixGetLaunchDimensions(); + const uint3 theLaunchIndex = optixGetLaunchIndex(); + const int theExceptionCode = optixGetExceptionCode(); + + printf("Exception %d at (%u, %u)\n", theExceptionCode, theLaunchIndex.x, theLaunchIndex.y); + + // FIXME This only works for render strategies where the launch dimension matches the outputBuffer resolution. + //float4* buffer = reinterpret_cast(sysData.outputBuffer); + //const unsigned int index = theLaunchIndex.y * theLaunchDim.x + theLaunchIndex.x; + + //buffer[index] = make_float4(1000000.0f, 0.0f, 1000000.0f, 1.0f); // super magenta +} diff --git a/apps/bench_shared/shaders/function_indices.h b/apps/bench_shared/shaders/function_indices.h new file mode 100644 index 00000000..2bef321a --- /dev/null +++ b/apps/bench_shared/shaders/function_indices.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef FUNCTION_INDICES_H +#define FUNCTION_INDICES_H + +enum RayType +{ + RAYTYPE_RADIANCE = 0, + RAYTYPE_SHADOW = 1, + + NUM_RAYTYPES = 2 +}; + +enum LensShader +{ + LENS_SHADER_PINHOLE = 0, + LENS_SHADER_FISHEYE = 1, + LENS_SHADER_SPHERE = 2, + + NUM_LENS_SHADERS = 3 +}; + +enum FunctionIndex +{ + INDEX_BRDF_DIFFUSE = 0, + INDEX_BRDF_SPECULAR = 1, + INDEX_BSDF_SPECULAR = 2, + INDEX_BRDF_GGX_SMITH = 3, + INDEX_BSDF_GGX_SMITH = 4, + + NUM_BSDF_INDICES = 5 +}; + +#endif // FUNCTION_INDICES_H diff --git a/apps/bench_shared/shaders/lens_shader.cu b/apps/bench_shared/shaders/lens_shader.cu new file mode 100644 index 00000000..f499103d --- /dev/null +++ b/apps/bench_shared/shaders/lens_shader.cu @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "shader_common.h" + +extern "C" __constant__ SystemData sysData; + +// Note that all these lens shaders return the primary ray origin and direction in world space! + +extern "C" __device__ void __direct_callable__pinhole(const float2 screen, const float2 pixel, const float2 sample, + float3& origin, float3& direction) +{ + const float2 fragment = pixel + sample; // Jitter the sub-pixel location + const float2 ndc = (fragment / screen) * 2.0f - 1.0f; // Normalized device coordinates in range [-1, 1]. + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + origin = camera.P; + direction = normalize(camera.U * ndc.x + + camera.V * ndc.y + + camera.W); +} + + +extern "C" __device__ void __direct_callable__fisheye(const float2 screen, const float2 pixel, const float2 sample, + float3& origin, float3& direction) +{ + const float2 fragment = pixel + sample; // x, y + + // Implement a fisheye projection with 180 degrees angle across the image diagonal (=> all pixels rendered, not a circular fisheye). + const float2 center = screen * 0.5f; + const float2 uv = (fragment - center) / length(center); // uv components are in the range [0, 1]. Both 1 in the corners of the image! + const float z = cosf(length(uv) * 0.7071067812f * 0.5f * M_PIf); // Scale by 1.0f / sqrtf(2.0f) to get length into the range [0, 1] + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + const float3 U = normalize(camera.U); + const float3 V = normalize(camera.V); + const float3 W = normalize(camera.W); + + origin = camera.P; + direction = normalize(uv.x * U + uv.y * V + z * W); +} + + +extern "C" __device__ void __direct_callable__sphere(const float2 screen, const float2 pixel, const float2 sample, + float3& origin, float3& direction) +{ + const float2 uv = (pixel + sample) / screen; // "texture coordinates" + + // Convert the 2D index into a direction. + const float phi = uv.x * 2.0f * M_PIf; + const float theta = uv.y * M_PIf; + + const float sinTheta = sinf(theta); + + const float3 v = make_float3(-sinf(phi) * sinTheta, + -cosf(theta), + -cosf(phi) * sinTheta); + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + const float3 U = normalize(camera.U); + const float3 V = normalize(camera.V); + const float3 W = normalize(camera.W); + + origin = camera.P; + direction = normalize(v.x * U + v.y * V + v.z * W); +} diff --git a/apps/bench_shared/shaders/light_definition.h b/apps/bench_shared/shaders/light_definition.h new file mode 100644 index 00000000..15267742 --- /dev/null +++ b/apps/bench_shared/shaders/light_definition.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef LIGHT_DEFINITION_H +#define LIGHT_DEFINITION_H + +enum LightType +{ + LIGHT_ENVIRONMENT = 0, // constant color or spherical environment map. + LIGHT_PARALLELOGRAM = 1, // Parallelogram area light. + + NUM_LIGHT_TYPES = 2 +}; + +struct LightDefinition +{ + LightType type; // Constant or spherical environment, rectangle (parallelogram). + + // Rectangle lights are defined in world coordinates as footpoint and two vectors spanning a parallelogram. + // All in world coordinates with no scaling. + float3 position; + float3 vecU; + float3 vecV; + float3 normal; + float area; + float3 emission; + + // Manual padding to float4 alignment goes here. + float unused0; + float unused1; + float unused2; +}; + +struct LightSample +{ + float3 position; + float distance; + float3 direction; + float3 emission; + float pdf; +}; + +#endif // LIGHT_DEFINITION_H diff --git a/apps/bench_shared/shaders/light_sample.cu b/apps/bench_shared/shaders/light_sample.cu new file mode 100644 index 00000000..1ec877e9 --- /dev/null +++ b/apps/bench_shared/shaders/light_sample.cu @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" + +#include "shader_common.h" + +extern "C" __constant__ SystemData sysData; + + +__forceinline__ __device__ void unitSquareToSphere(const float u, const float v, float3& p, float& pdf) +{ + p.z = 1.0f - 2.0f * u; + float r = 1.0f - p.z * p.z; + r = (0.0f < r) ? sqrtf(r) : 0.0f; + + const float phi = v * 2.0f * M_PIf; + p.x = r * cosf(phi); + p.y = r * sinf(phi); + + pdf = 0.25f * M_1_PIf; // == 1.0f / (4.0f * M_PIf) +} + +// Note that all light sampling routines return lightSample.direction and lightSample.distance in world space! + +extern "C" __device__ LightSample __direct_callable__light_env_constant(const LightDefinition& light, const float3 point, const float2 sample) +{ + LightSample lightSample; + + unitSquareToSphere(sample.x, sample.y, lightSample.direction, lightSample.pdf); + + // Environment lights do not set the light sample position! + lightSample.distance = RT_DEFAULT_MAX; // Environment light. + + // Explicit light sample. White scaled by inverse probabilty to hit this light. + // FIXME Could use the sysData.lightDefinitions[0].emission for different colors. + lightSample.emission = make_float3(sysData.numLights); + + return lightSample; +} + +extern "C" __device__ LightSample __direct_callable__light_env_sphere(const LightDefinition& light, const float3 point, const float2 sample) +{ + LightSample lightSample; + + // Importance-sample the spherical environment light direction. + + // Note that the marginal CDF is one bigger than the texture height. As index this is the 1.0f at the end of the CDF. + const unsigned int sizeV = sysData.envHeight; + + unsigned int ilo = 0; // Use this for full spherical lighting. (This matches the result of indirect environment lighting.) + unsigned int ihi = sizeV; // Index on the last entry containing 1.0f. Can never be reached with the sample in the range [0.0f, 1.0f). + + const float* cdfV = sysData.envCDF_V; + + // Binary search the row index to look up. + while (ilo != ihi - 1) // When a pair of limits have been found, the lower index indicates the cell to use. + { + const unsigned int i = (ilo + ihi) >> 1; + if (sample.y < cdfV[i]) // If the cdf is greater than the sample, use that as new higher limit. + { + ihi = i; + } + else // If the sample is greater than or equal to the CDF value, use that as new lower limit. + { + ilo = i; + } + } + + const unsigned int vIdx = ilo; // This is the row we found. + + // Note that the horizontal CDF is one bigger than the texture width. As index this is the 1.0f at the end of the CDF. + const unsigned int sizeU = sysData.envWidth; // Note that the horizontal CDFs are one bigger than the texture width. + + // Binary search the column index to look up. + ilo = 0; + ihi = sizeU; // Index on the last entry containing 1.0f. Can never be reached with the sample in the range [0.0f, 1.0f). + + // Pointer to the indexY row! + const float* cdfU = &sysData.envCDF_U[vIdx * (sizeU + 1)]; // Horizontal CDF is one bigger then the texture width! + + while (ilo != ihi - 1) // When a pair of limits have been found, the lower index indicates the cell to use. + { + const unsigned int i = (ilo + ihi) >> 1; + if (sample.x < cdfU[i]) // If the CDF value is greater than the sample, use that as new higher limit. + { + ihi = i; + } + else // If the sample is greater than or equal to the CDF value, use that as new lower limit. + { + ilo = i; + } + } + + const unsigned int uIdx = ilo; // The column result. + + // Continuous sampling of the CDF. + const float cdfLowerU = cdfU[uIdx]; + const float cdfUpperU = cdfU[uIdx + 1]; + const float du = (sample.x - cdfLowerU) / (cdfUpperU - cdfLowerU); + + const float cdfLowerV = cdfV[vIdx]; + const float cdfUpperV = cdfV[vIdx + 1]; + const float dv = (sample.y - cdfLowerV) / (cdfUpperV - cdfLowerV); + + // Texture lookup coordinates. + const float u = (float(uIdx) + du) / float(sizeU); + const float v = (float(vIdx) + dv) / float(sizeV); + + // Light sample direction vector polar coordinates. This is where the environment rotation happens! + // DAR FIXME Use a light.matrix to rotate the resulting vector instead. + const float phi = (u - sysData.envRotation) * 2.0f * M_PIf; + const float theta = v * M_PIf; // theta == 0.0f is south pole, theta == M_PIf is north pole. + + const float sinTheta = sinf(theta); + // The miss program places the 1->0 seam at the positive z-axis and looks from the inside. + lightSample.direction = make_float3(-sinf(phi) * sinTheta, // Starting on positive z-axis going around clockwise (to negative x-axis). + -cosf(theta), // From south pole to north pole. + cosf(phi) * sinTheta); // Starting on positive z-axis. + + // Note that environment lights do not set the light sample position! + lightSample.distance = RT_DEFAULT_MAX; // Environment light. + + const float3 emission = make_float3(tex2D(sysData.envTexture, u, v)); + // Explicit light sample. The returned emission must be scaled by the inverse probability to select this light. + lightSample.emission = emission * sysData.numLights; + // For simplicity we pretend that we perfectly importance-sampled the actual texture-filtered environment map + // and not the Gaussian-smoothed one used to actually generate the CDFs and uniform sampling in the texel. + lightSample.pdf = intensity(emission) / sysData.envIntegral; + + return lightSample; +} + +extern "C" __device__ LightSample __direct_callable__light_parallelogram(const LightDefinition& light, const float3 point, const float2 sample) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; // Default return, invalid light sample (backface, edge on, or too near to the surface) + + lightSample.position = light.position + light.vecU * sample.x + light.vecV * sample.y; // The light sample position in world coordinates. + lightSample.direction = lightSample.position - point; // Sample direction from surface point to light sample position. + lightSample.distance = length(lightSample.direction); + if (DENOMINATOR_EPSILON < lightSample.distance) + { + lightSample.direction /= lightSample.distance; // Normalized direction to light. + + const float cosTheta = dot(-lightSample.direction, light.normal); + if (DENOMINATOR_EPSILON < cosTheta) // Only emit light on the front side. + { + // Explicit light sample, must scale the emission by inverse probabilty to hit this light. + lightSample.emission = light.emission * float(sysData.numLights); + lightSample.pdf = (lightSample.distance * lightSample.distance) / (light.area * cosTheta); // Solid angle pdf. Assumes light.area != 0.0f. + } + } + + return lightSample; +} diff --git a/apps/bench_shared/shaders/material_definition.h b/apps/bench_shared/shaders/material_definition.h new file mode 100644 index 00000000..35eaf53f --- /dev/null +++ b/apps/bench_shared/shaders/material_definition.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MATERIAL_DEFINITION_H +#define MATERIAL_DEFINITION_H + +#include "function_indices.h" + +// Just some hardcoded material parameter system which allows to show a few fundamental BSDFs. +struct MaterialDefinition +{ + // 8 byte alignment. + cudaTextureObject_t textureAlbedo; // Modulates albedo when valid. + cudaTextureObject_t textureCutout; // RGB intensity defines surface cutout when valid, normally used with thin-walled. + + float2 roughness; // Anisotropic roughness values. + + // 4 byte alignment. + FunctionIndex indexBSDF; // BSDF index to use in the closest hit program. + + float3 albedo; // Albedo, tint, throughput change for specular surfaces. Pick your meaning. + float3 absorption; // Absorption coefficient. + float ior; // Index of refraction. + unsigned int flags; // Thin-walled on/off + + // Manual padding to 16-byte alignment goes here. + int pad0; + //int pad1; + //int pad3; + //int pad4; +}; + +#endif // MATERIAL_DEFINITION_H diff --git a/apps/bench_shared/shaders/miss.cu b/apps/bench_shared/shaders/miss.cu new file mode 100644 index 00000000..ec912e49 --- /dev/null +++ b/apps/bench_shared/shaders/miss.cu @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "light_definition.h" +#include "shader_common.h" +#include "system_data.h" + +extern "C" __constant__ SystemData sysData; + +// Not actually a light. Never appears inside the sysLightDefinitions. +extern "C" __global__ void __miss__env_null() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->radiance = make_float3(0.0f); + thePrd->flags |= FLAG_TERMINATE; +} + + +extern "C" __global__ void __miss__env_constant() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + +#if USE_NEXT_EVENT_ESTIMATION + // If the last surface intersection was a diffuse which was directly lit with multiple importance sampling, + // then calculate light emission with multiple importance sampling as well. + const float weightMIS = (thePrd->flags & FLAG_DIFFUSE) ? powerHeuristic(thePrd->pdf, 0.25f * M_1_PIf) : 1.0f; + thePrd->radiance = make_float3(weightMIS); // Constant white emission multiplied by MIS weight. +#else + thePrd->radiance = make_float3(1.0f); // Constant white emission. +#endif + + thePrd->flags |= FLAG_TERMINATE; +} + + +extern "C" __global__ void __miss__env_sphere() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + const float3 R = thePrd->wi; // theRay.direction; + // The seam u == 0.0 == 1.0 is in positive z-axis direction. + // Compensate for the environment rotation done inside the direct lighting. + const float u = (atan2f(R.x, -R.z) + M_PIf) * 0.5f * M_1_PIf + sysData.envRotation; // DAR FIXME Use a light.matrix to rotate the environment. + const float theta = acosf(-R.y); // theta == 0.0f is south pole, theta == M_PIf is north pole. + const float v = theta * M_1_PIf; // Texture is with origin at lower left, v == 0.0f is south pole. + + const float3 emission = make_float3(tex2D(sysData.envTexture, u, v)); + +#if USE_NEXT_EVENT_ESTIMATION + float weightMIS = 1.0f; + // If the last surface intersection was a diffuse event which was directly lit with multiple importance sampling, + // then calculate light emission with multiple importance sampling for this implicit light hit as well. + if (thePrd->flags & FLAG_DIFFUSE) + { + // For simplicity we pretend that we perfectly importance-sampled the actual texture-filtered environment map + // and not the Gaussian smoothed one used to actually generate the CDFs. + const float pdfLight = intensity(emission) / sysData.envIntegral; + weightMIS = powerHeuristic(thePrd->pdf, pdfLight); + } + thePrd->radiance = emission * weightMIS; +#else + thePrd->radiance = emission; +#endif + + thePrd->flags |= FLAG_TERMINATE; +} diff --git a/apps/bench_shared/shaders/per_ray_data.h b/apps/bench_shared/shaders/per_ray_data.h new file mode 100644 index 00000000..57402982 --- /dev/null +++ b/apps/bench_shared/shaders/per_ray_data.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef PER_RAY_DATA_H +#define PER_RAY_DATA_H + +#include "config.h" + +#include + + +#define MATERIAL_STACK_EMPTY -1 +#define MATERIAL_STACK_FIRST 0 +#define MATERIAL_STACK_LAST 3 +#define MATERIAL_STACK_SIZE 4 + +// Set when reaching a closesthit program. Unused in this demo +#define FLAG_HIT 0x00000001 +// Set when reaching the __anyhit__shadow program. Indicates visibility test failed. +#define FLAG_SHADOW 0x00000002 + +// Set by BSDFs which support direct lighting. Not set means specular interaction. Cleared in the closesthit program. +// Used to decide when to do direct lighting and multuiple importance sampling on implicit light hits. +#define FLAG_DIFFUSE 0x00000004 + +// Set if (0.0f <= wo_dot_ng), means looking onto the front face. (Edge-on is explicitly handled as frontface for the material stack.) +#define FLAG_FRONTFACE 0x00000010 +// Pass down material.flags through to the BSDFs. +#define FLAG_THINWALLED 0x00000020 + +// FLAG_TRANSMISSION is set if there is a transmission. (Can't happen when FLAG_THINWALLED is set.) +#define FLAG_TRANSMISSION 0x00000100 + +// Set if the material stack is not empty. +#define FLAG_VOLUME 0x00001000 + +// Highest bit set means terminate path. +#define FLAG_TERMINATE 0x80000000 + +// Keep flags active in a path segment which need to be tracked along the path. +// In this case only the last surface interaction is kept. +// It's needed to track the last bounce's diffuse state in case a ray hits a light implicitly for multiple importance sampling. +// FLAG_DIFFUSE is reset in the closesthit program. +#define FLAG_CLEAR_MASK FLAG_DIFFUSE + +// Currently only containing some vertex attributes in world coordinates. +struct State +{ + float3 normalGeo; + float3 tangent; + float3 normal; + float3 texcoord; + float3 albedo; // PERF Added albedo to the state to allow modulation with an optional texture once before BSDF sampling and evaluation. +}; + +// Note that the fields are ordered by CUDA alignment restrictions. +struct PerRayData +{ + // 16-byte alignment + float4 absorption_ior; // The absorption coefficient and IOR of the currently hit material. + + // 8-byte alignment + float2 ior; // .x = IOR the ray currently is inside, .y = the IOR of the surrounding volume. The IOR of the current material is in absorption_ior.w! + + // 4-byte alignment + float3 pos; // Current surface hit point or volume sample point, in world space + float distance; // Distance from the ray origin to the current position, in world space. Needed for absorption of nested materials. + + float3 wo; // Outgoing direction, to observer, in world space. + float3 wi; // Incoming direction, to light, in world space. + + float3 radiance; // Radiance along the current path segment. + + unsigned int flags; // Bitfield with flags. See FLAG_* defines above for its contents. + + float3 f_over_pdf; // BSDF sample throughput, pre-multiplied f_over_pdf = bsdf.f * fabsf(dot(wi, ns) / bsdf.pdf; + float pdf; // The last BSDF sample's pdf, tracked for multiple importance sampling. + + float3 sigma_t; // The current volume's extinction coefficient. (Only absorption in this implementation.) + float opacity; // Cutout opacity result. + + unsigned int seed; // Random number generator input. +}; + + +// Alias the PerRayData pointer and an uint2 for the payload split and merge operations. This generates only move instructions. +typedef union +{ + PerRayData* ptr; + uint2 dat; +} Payload; + +__forceinline__ __device__ uint2 splitPointer(PerRayData* ptr) +{ + Payload payload; + + payload.ptr = ptr; + + return payload.dat; +} + +__forceinline__ __device__ PerRayData* mergePointer(unsigned int p0, unsigned int p1) +{ + Payload payload; + + payload.dat.x = p0; + payload.dat.y = p1; + + return payload.ptr; +} + +#endif // PER_RAY_DATA_H diff --git a/apps/bench_shared/shaders/random_number_generators.h b/apps/bench_shared/shaders/random_number_generators.h new file mode 100644 index 00000000..0670ff67 --- /dev/null +++ b/apps/bench_shared/shaders/random_number_generators.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RANDOM_NUMBER_GENERATORS_H +#define RANDOM_NUMBER_GENERATORS_H + +#include "config.h" + + +// Tiny Encryption Algorithm (TEA) to calculate a the seed per launch index and iteration. +// This results in a ton of integer instructions! Use the smallest N necessary. +template +__forceinline__ __device__ unsigned int tea(const unsigned int val0, const unsigned int val1) +{ + unsigned int v0 = val0; + unsigned int v1 = val1; + unsigned int s0 = 0; + + for (unsigned int n = 0; n < N; ++n) + { + s0 += 0x9e3779b9; + v0 += ((v1 << 4) + 0xA341316C) ^ (v1 + s0) ^ ((v1 >> 5) + 0xC8013EA4); + v1 += ((v0 << 4) + 0xAD90777D) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7E95761E); + } + return v0; +} + +// Just do one LCG step. The new random unsigned int number is in the referenced argument. +__forceinline__ __device__ void lcg(unsigned int& previous) +{ + previous = previous * 1664525u + 1013904223u; +} + + +// Return a random sample in the range [0, 1) with a simple Linear Congruential Generator. +__forceinline__ __device__ float rng(unsigned int& previous) +{ + previous = previous * 1664525u + 1013904223u; + + return float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + // return float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits +} + +// Convenience function to generate a 2D unit square sample. +__forceinline__ __device__ float2 rng2(unsigned int& previous) +{ + float2 s; + + previous = previous * 1664525u + 1013904223u; + s.x = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + //s.x = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + s.y = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + //s.y = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + return s; +} + +#endif // RANDOM_NUMBER_GENERATORS_H diff --git a/apps/bench_shared/shaders/raygeneration.cu b/apps/bench_shared/shaders/raygeneration.cu new file mode 100644 index 00000000..6ebca83e --- /dev/null +++ b/apps/bench_shared/shaders/raygeneration.cu @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +__forceinline__ __device__ float3 integrator(PerRayData& prd) +{ + // This renderer supports nested volumes. Four levels is plenty enough for most cases. + // The absorption coefficient and IOR of the volume the ray is currently inside. + float4 absorptionStack[MATERIAL_STACK_SIZE]; // .xyz == absorptionCoefficient (sigma_a), .w == index of refraction + + int stackIdx = MATERIAL_STACK_EMPTY; // Start with empty nested materials stack. + + // Russian Roulette path termination after a specified number of bounces needs the current depth. + int depth = 0; // Path segment index. Primary ray is 0. + + float3 radiance = make_float3(0.0f); // Start with black. + float3 throughput = make_float3(1.0f); // The throughput for the next radiance, starts with 1.0f. + + // Assumes that the primary ray starts in vacuum. + prd.absorption_ior = make_float4(0.0f, 0.0f, 0.0f, 1.0f); // No absorption and IOR == 1.0f + prd.sigma_t = make_float3(0.0f); // No extinction. + prd.flags = 0; + + while (depth < sysData.pathLengths.y) + { + prd.wo = -prd.wi; // Direction to observer. + prd.ior = make_float2(1.0f); // Reset the volume IORs. + prd.distance = RT_DEFAULT_MAX; // Shoot the next ray with maximum length. + prd.flags &= FLAG_CLEAR_MASK; // Clear all non-persistent flags. In this demo only the last diffuse surface interaction stays. + + // Handle volume absorption of nested materials. + if (MATERIAL_STACK_FIRST <= stackIdx) // Inside a volume? + { + prd.flags |= FLAG_VOLUME; // Indicate that we're inside a volume. => At least absorption calculation needs to happen. + prd.sigma_t = make_float3(absorptionStack[stackIdx]); // There is only volume absorption in this demo, no volume scattering. + prd.ior.x = absorptionStack[stackIdx].w; // The IOR of the volume we're inside. Needed for eta calculations in transparent materials. + if (MATERIAL_STACK_FIRST <= stackIdx - 1) + { + prd.ior.y = absorptionStack[stackIdx - 1].w; // The IOR of the surrounding volume. Needed when potentially leaving a volume to calculate eta in transparent materials. + } + } + + // Put payload pointer into two unsigned integers. Actually const, but that's not what optixTrace() expects. + uint2 payload = splitPointer(&prd); + + // Note that the primary rays (or volume scattering miss cases) wouldn't normally offset the ray t_min by sysSceneEpsilon. Keep it simple here. + optixTrace(sysData.topObject, + prd.pos, prd.wi, // origin, direction + sysData.sceneEpsilon, prd.distance, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_NONE, + RAYTYPE_RADIANCE, NUM_RAYTYPES, RAYTYPE_RADIANCE, + payload.x, payload.y); + + // This renderer supports nested volumes. + if (prd.flags & FLAG_VOLUME) // We're inside a volume? + { + // The transmittance along the current path segment inside a volume needs to attenuate + // the ray throughput with the extinction before it modulates the radiance of the hitpoint. + throughput *= expf(-prd.distance * prd.sigma_t); + } + + radiance += throughput * prd.radiance; + + // Path termination by miss shader or sample() routines. + // If terminate is true, f_over_pdf and pdf might be undefined. + if ((prd.flags & FLAG_TERMINATE) || prd.pdf <= 0.0f || isNull(prd.f_over_pdf)) + { + break; + } + + // PERF f_over_pdf already contains the proper throughput adjustment for diffuse materials: f * (fabsf(dot(prd.wi, state.normal)) / prd.pdf); + throughput *= prd.f_over_pdf; + + // Unbiased Russian Roulette path termination. + if (sysData.pathLengths.x <= depth) // Start termination after a minimum number of bounces. + { + const float probability = fmaxf(throughput); // DEBUG Other options: // intensity(throughput); // fminf(0.5f, intensity(throughput)); + if (probability < rng(prd.seed)) // Paths with lower probability to continue are terminated earlier. + { + break; + } + throughput /= probability; // Path isn't terminated. Adjust the throughput so that the average is right again. + } + + // Adjust the material volume stack if the geometry is not thin-walled but a border between two volumes and + // the outgoing ray direction was a transmission. + if ((prd.flags & (FLAG_THINWALLED | FLAG_TRANSMISSION)) == FLAG_TRANSMISSION) + { + // Transmission. + if (prd.flags & FLAG_FRONTFACE) // Entered a new volume? + { + // Push the entered material's volume properties onto the volume stack. + //rtAssert((stackIdx < MATERIAL_STACK_LAST), 1); // Overflow? + stackIdx = min(stackIdx + 1, MATERIAL_STACK_LAST); + absorptionStack[stackIdx] = prd.absorption_ior; + } + else // Exited the current volume? + { + // Pop the top of stack material volume. + // This assert fires and is intended because I tuned the frontface checks so that there are more exits than enters at silhouettes. + //rtAssert((MATERIAL_STACK_EMPTY < stackIdx), 0); // Underflow? + stackIdx = max(stackIdx - 1, MATERIAL_STACK_EMPTY); + } + } + + ++depth; // Next path segment. + } + + return radiance; +} + + +__forceinline__ __device__ unsigned int distribute(const uint2 launchIndex) +{ + // First calculate block coordinates of this launch index. + // That is the launch index divided by the tile dimensions. (No operator>>() on vectors?) + const unsigned int xBlock = launchIndex.x >> sysData.tileShift.x; + const unsigned int yBlock = launchIndex.y >> sysData.tileShift.y; + + // Each device needs to start at a different column and each row should start with a different device. + const unsigned int xTile = xBlock * sysData.deviceCount + ((sysData.deviceIndex + yBlock) % sysData.deviceCount); + + // The horizontal pixel coordinate is: tile coordinate * tile width + launch index % tile width. + return xTile * sysData.tileSize.x + (launchIndex.x & (sysData.tileSize.x - 1)); // tileSize needs to be power-of-two for this modulo operation. +} + +// The sample position is calculated via a fixed rotated grid. +// This will result in perfectly stratified camera sample positions when all samples per pixel are rendered. +__forceinline__ __device__ float2 sampleRotatedGrid(const unsigned int iteration) +{ + const unsigned int samples = sysData.samplesSqrt; + if (samples == 1) + { + return make_float2(0.5f); + } + + const float invSamples = 1.0f / float(samples); + const float invSamplesMinusOne = 1.0f / float(samples - 1); // This is why samples == 1 needs a special case. + + // Convert the linear iteration index into a 2D grid. + const float x = float(iteration % samples); + const float y = float(iteration / samples); // % samples; // The modulo here is not necessary because iterationIndex < samplesSqrt^2. + + // Calculate the sub-pixel cell offset. Used to interpolate the rotated grid sample positions. + // Maximum shift away from the center is half a cell, minus half a cell divided by the number of samples in each dimension. + float delta = 0.5f * invSamples; + delta -= delta * invSamples; + + // Cell center + per row offset. + // Horizontal sample positions start to shift to the left in the bottom row. + // Odd sizes will have one sample exactly in the pixel center (0.5f, 0.5f). + const float u = (x + 0.5f) * invSamples + lerp(-delta, delta, y * invSamplesMinusOne); + + // Cell center + per column offset. + // Note that the shift is to the opposite direction here to get a rotated grid,. + // Vertical sample positions start to shift to the top in left column. + const float v = (y + 0.5f) * invSamples + lerp(delta, -delta, x * invSamplesMinusOne); + + return make_float2(u, v); +} + + +extern "C" __global__ void __raygen__path_tracer_local_copy() +{ +#if USE_TIME_VIEW + clock_t clockBegin = clock(); +#endif + + const uint2 theLaunchIndex = make_uint2(optixGetLaunchIndex()); + + unsigned int launchColumn = theLaunchIndex.x; + + if (1 < sysData.deviceCount) // Multi-GPU distribution required? + { + launchColumn = distribute(theLaunchIndex); // Calculate mapping from launch index to pixel index. + if (sysData.resolution.x <= launchColumn) // Check if the launchColumn is outside the resolution. + { + return; + } + } + + PerRayData prd; + + const uint2 theLaunchDim = make_uint2(optixGetLaunchDimensions()); // For multi-GPU tiling this is (resolution + deviceCount - 1) / deviceCount. + + // Initialize the random number generator seed from the linear pixel index and the iteration index. + prd.seed = tea<4>(theLaunchIndex.y * theLaunchDim.x + launchColumn, sysData.iterationIndex); // PERF This template really generates a lot of instructions. + + // Decoupling the pixel coordinates from the screen size will allow for partial rendering algorithms. + // Resolution is the actual full rendering resolution and for the single GPU strategy, theLaunchDim == resolution. + const float2 screen = make_float2(sysData.resolution); // == theLaunchDim for rendering strategy RS_SINGLE_GPU. + const float2 pixel = make_float2(launchColumn, theLaunchIndex.y); + const float2 sample = sampleRotatedGrid(sysData.iterationIndex); // or rng2(prd.seed); + + // Lens shaders + optixDirectCall(sysData.lensShader, screen, pixel, sample, prd.pos, prd.wi); + + float3 radiance = integrator(prd); + +#if USE_DEBUG_EXCEPTIONS + // DAR DEBUG Highlight numerical errors. + if (isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z)) + { + radiance = make_float3(1000000.0f, 0.0f, 0.0f); // super red + } + else if (isinf(radiance.x) || isinf(radiance.y) || isinf(radiance.z)) + { + radiance = make_float3(0.0f, 1000000.0f, 0.0f); // super green + } + else if (radiance.x < 0.0f || radiance.y < 0.0f || radiance.z < 0.0f) + { + radiance = make_float3(0.0f, 0.0f, 1000000.0f); // super blue + } +#else + // NaN values will never go away. Filter them out before they can arrive in the output buffer. + // This only has an effect if the debug coloring above is off! + if (!(isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z))) +#endif + { + // The texelBuffer is a CUdeviceptr to allow different formats. + float4* buffer = reinterpret_cast(sysData.texelBuffer); // This is a per device launch sized buffer in this renderer strategy. + + // This renderer write the results into individual launch sized local buffers and composites them in a separate native CUDA kernel. + const unsigned int index = theLaunchIndex.y * theLaunchDim.x + theLaunchIndex.x; + +#if USE_TIME_VIEW + clock_t clockEnd = clock(); + const float alpha = (clockEnd - clockBegin) * sysData.clockScale; + + float4 result = make_float4(radiance, alpha); + + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + result = lerp(dst, result, 1.0f / float(sysData.iterationIndex + 1)); // Accumulate the alpha as well. + } + buffer[index] = result; +#else + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + radiance = lerp(make_float3(dst), radiance, 1.0f / float(sysData.iterationIndex + 1)); // Only accumulate the radiance, alpha stays 1.0f. + } + buffer[index] = make_float4(radiance, 1.0f); +#endif + } +} + diff --git a/apps/bench_shared/shaders/shader_common.h b/apps/bench_shared/shaders/shader_common.h new file mode 100644 index 00000000..96f607cd --- /dev/null +++ b/apps/bench_shared/shaders/shader_common.h @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SHADER_COMMON_H +#define SHADER_COMMON_H + +#include "config.h" + +#include "vector_math.h" + + +/** +* Calculates refraction direction +* r : refraction vector +* i : incident vector +* n : surface normal +* ior : index of refraction ( n2 / n1 ) +* returns false in case of total internal reflection, in that case r is initialized to (0,0,0). +*/ +__forceinline__ __host__ __device__ bool refract(float3& r, const float3& i, const float3& n, const float ior) +{ + float3 nn = n; + float negNdotV = dot(i, nn); + float eta; + + if (negNdotV > 0.0f) + { + eta = ior; + nn = -n; + negNdotV = -negNdotV; + } + else + { + eta = 1.f / ior; + } + + const float k = 1.f - eta * eta * (1.f - negNdotV * negNdotV); + + if (k < 0.0f) + { + // Initialize this value, so that r always leaves this function initialized. + r = make_float3(0.f); + return false; + } + else + { + r = normalize(eta * i - (eta * negNdotV + sqrtf(k)) * nn); + return true; + } +} + + + +// Tangent-Bitangent-Normal orthonormal space. +struct TBN +{ + // Default constructor to be able to include it into other structures when needed. + __forceinline__ __host__ __device__ TBN() + { + } + + __forceinline__ __host__ __device__ TBN(const float3& n) + : normal(n) + { + if (fabsf(normal.z) < fabsf(normal.x)) + { + tangent.x = normal.z; + tangent.y = 0.0f; + tangent.z = -normal.x; + } + else + { + tangent.x = 0.0f; + tangent.y = normal.z; + tangent.z = -normal.y; + } + tangent = normalize(tangent); + bitangent = cross(normal, tangent); + } + + // Constructor for cases where tangent, bitangent, and normal are given as ortho-normal basis. + __forceinline__ __host__ __device__ TBN(const float3& t, const float3& b, const float3& n) + : tangent(t) + , bitangent(b) + , normal(n) + { + } + + // Normal is kept, tangent and bitangent are calculated. + // Normal must be normalized. + // Must not be used with degenerated vectors! + __forceinline__ __host__ __device__ TBN(const float3& tangent_reference, const float3& n) + : normal(n) + { + bitangent = normalize(cross(normal, tangent_reference)); + tangent = cross(bitangent, normal); + } + + __forceinline__ __host__ __device__ void negate() + { + tangent = -tangent; + bitangent = -bitangent; + normal = -normal; + } + + __forceinline__ __host__ __device__ float3 transformToLocal(const float3& p) const + { + return make_float3(dot(p, tangent), + dot(p, bitangent), + dot(p, normal)); + } + + __forceinline__ __host__ __device__ float3 transformToWorld(const float3& p) const + { + return p.x * tangent + p.y * bitangent + p.z * normal; + } + + float3 tangent; + float3 bitangent; + float3 normal; +}; + +// FBN (Fiber, Bitangent, Normal) +// Special version of TBN (Tangent, Bitangent, Normal) ortho-normal basis generation for fiber geometry. +// The difference is that the TBN keeps the normal intact where the FBN keeps the tangent intact, which is the fiber orientation! +struct FBN +{ + // Default constructor to be able to include it into State. + __forceinline__ __host__ __device__ FBN() + { + } + + // Do not use these single argument constructors for anisotropic materials! + // There will be discontinuities on round objects when the FBN orientation flips. + // Tangent is kept, bitangent and normal are calculated. + // Tangent t must be normalized. + __forceinline__ __host__ __device__ FBN(const float3& t) // t == fiber orientation + : tangent(t) + { + if (fabsf(tangent.z) < fabsf(tangent.x)) + { + bitangent.x = -tangent.y; + bitangent.y = tangent.x; + bitangent.z = 0.0f; + } + else + { + bitangent.x = 0.0f; + bitangent.y = -tangent.z; + bitangent.z = tangent.y; + } + + bitangent = normalize(bitangent); + normal = cross(tangent, bitangent); + } + + // Constructor for cases where tangent, bitangent, and normal are given as ortho-normal basis. + __forceinline__ __host__ __device__ FBN(const float3& t, const float3& b, const float3& n) // t == fiber orientation + : tangent(t) + , bitangent(b) + , normal(n) + { + } + + // Tangent is kept, bitangent and normal are calculated. + // Tangent t must be normalized. + // Must not be used with degenerated vectors! + __forceinline__ __host__ __device__ FBN(const float3& t, const float3& n) + : tangent(t) + { + bitangent = normalize(cross(n, tangent)); + normal = cross(tangent, bitangent); + } + + __forceinline__ __host__ __device__ void negate() + { + tangent = -tangent; + bitangent = -bitangent; + normal = -normal; + } + + __forceinline__ __host__ __device__ float3 transformToLocal(const float3& p) const + { + return make_float3(dot(p, tangent), + dot(p, bitangent), + dot(p, normal)); + } + + __forceinline__ __host__ __device__ float3 transformToWorld(const float3& p) const + { + return p.x * tangent + p.y * bitangent + p.z * normal; + } + + float3 tangent; + float3 bitangent; + float3 normal; +}; + + + +__forceinline__ __host__ __device__ float luminance(const float3& rgb) +{ + const float3 ntsc_luminance = { 0.30f, 0.59f, 0.11f }; + return dot(rgb, ntsc_luminance); +} + +__forceinline__ __host__ __device__ float intensity(const float3& rgb) +{ + return (rgb.x + rgb.y + rgb.z) * 0.3333333333f; +} + +__forceinline__ __host__ __device__ float cube(const float x) +{ + return x * x * x; +} + +__forceinline__ __host__ __device__ bool isNull(const float3& v) +{ + return (v.x == 0.0f && v.y == 0.0f && v.z == 0.0f); +} + +__forceinline__ __host__ __device__ bool isNotNull(const float3& v) +{ + return (v.x != 0.0f || v.y != 0.0f || v.z != 0.0f); +} + +// Used for Multiple Importance Sampling. +__forceinline__ __host__ __device__ float powerHeuristic(const float a, const float b) +{ + const float t = a * a; + return t / (t + b * b); +} + +__forceinline__ __host__ __device__ float balanceHeuristic(const float a, const float b) +{ + return a / (a + b); +} + + +#endif // SHADER_COMMON_H diff --git a/apps/bench_shared/shaders/system_data.h b/apps/bench_shared/shaders/system_data.h new file mode 100644 index 00000000..91b47a3d --- /dev/null +++ b/apps/bench_shared/shaders/system_data.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SYSTEM_DATA_H +#define SYSTEM_DATA_H + +#include "config.h" + +#include "camera_definition.h" +#include "light_definition.h" +#include "material_definition.h" +#include "vertex_attributes.h" + + +struct SystemData +{ + // 16 byte alignment + //int4 rect; // DAR Unused, not implementing a tile renderer. + + // 8 byte alignment + OptixTraversableHandle topObject; + + // The accumulated linear color space output buffer. + // This is always sized to the resolution, not always matching the launch dimension. + // Using a CUdeviceptr here to allow for different buffer formats without too many casts. + CUdeviceptr outputBuffer; + // These buffers are used differently among the rendering strategies. + CUdeviceptr tileBuffer; + CUdeviceptr texelBuffer; + + CameraDefinition* cameraDefinitions; // Currently only one camera in the array. (Allows camera motion blur in the future.) + LightDefinition* lightDefinitions; + MaterialDefinition* materialDefinitions; + + cudaTextureObject_t envTexture; // DAR FIXME Move these into the LightDefinition. + + float* envCDF_U; // 2D, size (envWidth + 1) * envHeight + float* envCDF_V; // 1D, size (envHeight + 1) + + int2 resolution; // The actual rendering resolution. Independent from the launch dimensions for some rendering strategies. + int2 tileSize; // Example: make_int2(8, 4) for 8x4 tiles. Must be a power of two to make the division a right-shift. + int2 tileShift; // Example: make_int2(3, 2) for the integer division by tile size. That actually makes the tileSize redundant. + int2 pathLengths; // .x = min path length before Russian Roulette kicks in, .y = maximum path length + + // 4 byte alignment + int deviceCount; // Number of devices doing the rendering. + int deviceIndex; // Device index to be able to distinguish the individual devices in a multi-GPU environment. + //int distribution; // Indicate if the tile distribution inside the ray generation program should be used. // FIXME Put booleans into bitfield if there are more. Redundant, the deviceCount is what really matters. + int iterationIndex; + int samplesSqrt; + + float sceneEpsilon; + float clockScale; // Only used with USE_TIME_VIEW. + + int lensShader; // Camera type. + + int numCameras; + int numMaterials; + int numLights; + + unsigned int envWidth; // The original size of the environment texture. + unsigned int envHeight; + float envIntegral; + float envRotation; +}; + + +// SBT Record data for the hit group. This is used on the device to calculate attributes! +struct GeometryInstanceData +{ + // Using CUdeviceptr here to be able to handle different attribute and index formats for Triangles, Hair, and Curves. + CUdeviceptr attributes; + CUdeviceptr indices; + int idMaterial; + int idLight; // Negative means not a light. +}; + +#endif // SYSTEM_DATA_H diff --git a/apps/bench_shared/shaders/vector_math.h b/apps/bench_shared/shaders/vector_math.h new file mode 100644 index 00000000..3ea0b339 --- /dev/null +++ b/apps/bench_shared/shaders/vector_math.h @@ -0,0 +1,2958 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef VECTOR_MATH_H +#define VECTOR_MATH_H + +#include "config.h" + +#include + + +#if defined(__CUDACC__) || defined(__CUDABE__) +#define VECTOR_MATH_API __forceinline__ __host__ __device__ +#else +#include +#define VECTOR_MATH_API inline +#endif + + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288419716939937510 +#endif +#ifndef M_Ef +#define M_Ef 2.71828182845904523536f +#endif +#ifndef M_LOG2Ef +#define M_LOG2Ef 1.44269504088896340736f +#endif +#ifndef M_LOG10Ef +#define M_LOG10Ef 0.434294481903251827651f +#endif +#ifndef M_LN2f +#define M_LN2f 0.693147180559945309417f +#endif +#ifndef M_LN10f +#define M_LN10f 2.30258509299404568402f +#endif +#ifndef M_PIf +#define M_PIf 3.14159265358979323846f +#endif +#ifndef M_PI_2f +#define M_PI_2f 1.57079632679489661923f +#endif +#ifndef M_PI_4f +#define M_PI_4f 0.785398163397448309616f +#endif +#ifndef M_1_PIf +#define M_1_PIf 0.318309886183790671538f +#endif +#ifndef M_2_PIf +#define M_2_PIf 0.636619772367581343076f +#endif +#ifndef M_2_SQRTPIf +#define M_2_SQRTPIf 1.12837916709551257390f +#endif +#ifndef M_SQRT2f +#define M_SQRT2f 1.41421356237309504880f +#endif +#ifndef M_SQRT1_2f +#define M_SQRT1_2f 0.707106781186547524401f +#endif + +#if !defined(__CUDACC__) + +VECTOR_MATH_API int max(int a, int b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API int min(int a, int b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API long long max(long long a, long long b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API long long min(long long a, long long b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API unsigned int max(unsigned int a, unsigned int b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API unsigned int min(unsigned int a, unsigned int b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API unsigned long long max(unsigned long long a, unsigned long long b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API unsigned long long min(unsigned long long a, unsigned long long b) +{ + return (a < b) ? a : b; +} + +#endif + +/** lerp */ +VECTOR_MATH_API float lerp(const float a, const float b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float bilerp(const float x00, const float x10, const float x01, const float x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp */ +VECTOR_MATH_API float clamp(const float f, const float a, const float b) +{ + return fmaxf(a, fminf(f, b)); +} + + +/* float2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float2 make_float2(const float s) +{ + return make_float2(s, s); +} +VECTOR_MATH_API float2 make_float2(const int2& a) +{ + return make_float2(float(a.x), float(a.y)); +} +VECTOR_MATH_API float2 make_float2(const uint2& a) +{ + return make_float2(float(a.x), float(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float2 operator-(const float2& a) +{ + return make_float2(-a.x, -a.y); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float2 fminf(const float2& a, const float2& b) +{ + return make_float2(fminf(a.x, b.x), fminf(a.y, b.y)); +} +VECTOR_MATH_API float fminf(const float2& a) +{ + return fminf(a.x, a.y); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float2 fmaxf(const float2& a, const float2& b) +{ + return make_float2(fmaxf(a.x, b.x), fmaxf(a.y, b.y)); +} +VECTOR_MATH_API float fmaxf(const float2& a) +{ + return fmaxf(a.x, a.y); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float2 operator+(const float2& a, const float2& b) +{ + return make_float2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API float2 operator+(const float2& a, const float b) +{ + return make_float2(a.x + b, a.y + b); +} +VECTOR_MATH_API float2 operator+(const float a, const float2& b) +{ + return make_float2(a + b.x, a + b.y); +} +VECTOR_MATH_API void operator+=(float2& a, const float2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float2 operator-(const float2& a, const float2& b) +{ + return make_float2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API float2 operator-(const float2& a, const float b) +{ + return make_float2(a.x - b, a.y - b); +} +VECTOR_MATH_API float2 operator-(const float a, const float2& b) +{ + return make_float2(a - b.x, a - b.y); +} +VECTOR_MATH_API void operator-=(float2& a, const float2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float2 operator*(const float2& a, const float2& b) +{ + return make_float2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API float2 operator*(const float2& a, const float s) +{ + return make_float2(a.x * s, a.y * s); +} +VECTOR_MATH_API float2 operator*(const float s, const float2& a) +{ + return make_float2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(float2& a, const float2& s) +{ + a.x *= s.x; + a.y *= s.y; +} +VECTOR_MATH_API void operator*=(float2& a, const float s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float2 operator/(const float2& a, const float2& b) +{ + return make_float2(a.x / b.x, a.y / b.y); +} +VECTOR_MATH_API float2 operator/(const float2& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float2 operator/(const float s, const float2& a) +{ + return make_float2(s / a.x, s / a.y); +} +VECTOR_MATH_API void operator/=(float2& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float2 lerp(const float2& a, const float2& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float2 bilerp(const float2& x00, const float2& x10, const float2& x01, const float2& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float2 clamp(const float2& v, const float a, const float b) +{ + return make_float2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API float2 clamp(const float2& v, const float2& a, const float2& b) +{ + return make_float2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float2& a, const float2& b) +{ + return a.x * b.x + a.y * b.y; +} + +/** length */ +VECTOR_MATH_API float length(const float2& v) +{ + return sqrtf(dot(v, v)); +} + +/** normalize */ +VECTOR_MATH_API float2 normalize(const float2& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float2 floor(const float2& v) +{ + return make_float2(::floorf(v.x), ::floorf(v.y)); +} + +/** reflect */ +VECTOR_MATH_API float2 reflect(const float2& i, const float2& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL */ +VECTOR_MATH_API float2 faceforward(const float2& n, const float2& i, const float2& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float2 expf(const float2& v) +{ + return make_float2(::expf(v.x), ::expf(v.y)); +} + +/** pow **/ +VECTOR_MATH_API float2 powf(const float2& v, const float e) +{ + return make_float2(::powf(v.x, e), ::powf(v.y, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float2 sqrtf(const float2& v) +{ + return make_float2(::sqrtf(v.x), ::sqrtf(v.y)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float2& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float2& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + + +/* float3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float3 make_float3(const float s) +{ + return make_float3(s, s, s); +} +VECTOR_MATH_API float3 make_float3(const float2& a) +{ + return make_float3(a.x, a.y, 0.0f); +} +VECTOR_MATH_API float3 make_float3(const int3& a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +VECTOR_MATH_API float3 make_float3(const uint3& a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float3 operator-(const float3& a) +{ + return make_float3(-a.x, -a.y, -a.z); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float3 fminf(const float3& a, const float3& b) +{ + return make_float3(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z)); +} +VECTOR_MATH_API float fminf(const float3& a) +{ + return fminf(fminf(a.x, a.y), a.z); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float3 fmaxf(const float3& a, const float3& b) +{ + return make_float3(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z)); +} +VECTOR_MATH_API float fmaxf(const float3& a) +{ + return fmaxf(fmaxf(a.x, a.y), a.z); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float3 operator+(const float3& a, const float3& b) +{ + return make_float3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API float3 operator+(const float3& a, const float b) +{ + return make_float3(a.x + b, a.y + b, a.z + b); +} +VECTOR_MATH_API float3 operator+(const float a, const float3& b) +{ + return make_float3(a + b.x, a + b.y, a + b.z); +} +VECTOR_MATH_API void operator+=(float3& a, const float3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float3 operator-(const float3& a, const float3& b) +{ + return make_float3(a.x - b.x, a.y - b.y, a.z - b.z); +} +VECTOR_MATH_API float3 operator-(const float3& a, const float b) +{ + return make_float3(a.x - b, a.y - b, a.z - b); +} +VECTOR_MATH_API float3 operator-(const float a, const float3& b) +{ + return make_float3(a - b.x, a - b.y, a - b.z); +} +VECTOR_MATH_API void operator-=(float3& a, const float3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float3 operator*(const float3& a, const float3& b) +{ + return make_float3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API float3 operator*(const float3& a, const float s) +{ + return make_float3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API float3 operator*(const float s, const float3& a) +{ + return make_float3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(float3& a, const float3& s) +{ + a.x *= s.x; + a.y *= s.y; + a.z *= s.z; +} +VECTOR_MATH_API void operator*=(float3& a, const float s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float3 operator/(const float3& a, const float3& b) +{ + return make_float3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API float3 operator/(const float3& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float3 operator/(const float s, const float3& a) +{ + return make_float3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(float3& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float3 lerp(const float3& a, const float3& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float3 bilerp(const float3& x00, const float3& x10, const float3& x01, const float3& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float3 clamp(const float3& v, const float a, const float b) +{ + return make_float3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API float3 clamp(const float3& v, const float3& a, const float3& b) +{ + return make_float3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float3& a, const float3& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +/** cross product */ +VECTOR_MATH_API float3 cross(const float3& a, const float3& b) +{ + return make_float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); +} + +/** length */ +VECTOR_MATH_API float length(const float3& v) +{ + return sqrtf(dot(v, v)); +} + +/** normalize */ +VECTOR_MATH_API float3 normalize(const float3& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float3 floor(const float3& v) +{ + return make_float3(::floorf(v.x), ::floorf(v.y), ::floorf(v.z)); +} + +/** reflect */ +VECTOR_MATH_API float3 reflect(const float3& i, const float3& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL */ +VECTOR_MATH_API float3 faceforward(const float3& n, const float3& i, const float3& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float3 expf(const float3& v) +{ + return make_float3(::expf(v.x), ::expf(v.y), ::expf(v.z)); +} + +/** pow **/ +VECTOR_MATH_API float3 powf(const float3& v, const float e) +{ + return make_float3(::powf(v.x, e), ::powf(v.y, e), ::powf(v.z, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float3 sqrtf(const float3& v) +{ + return make_float3(::sqrtf(v.x), ::sqrtf(v.y), ::sqrtf(v.z)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float3& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float3& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + +/* float4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float4 make_float4(const float s) +{ + return make_float4(s, s, s, s); +} +VECTOR_MATH_API float4 make_float4(const float3& a) +{ + return make_float4(a.x, a.y, a.z, 0.0f); +} +VECTOR_MATH_API float4 make_float4(const int4& a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +VECTOR_MATH_API float4 make_float4(const uint4& a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float4 operator-(const float4& a) +{ + return make_float4(-a.x, -a.y, -a.z, -a.w); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float4 fminf(const float4& a, const float4& b) +{ + return make_float4(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z), fminf(a.w, b.w)); +} +VECTOR_MATH_API float fminf(const float4& a) +{ + return fminf(fminf(a.x, a.y), fminf(a.z, a.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float4 fmaxf(const float4& a, const float4& b) +{ + return make_float4(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z), fmaxf(a.w, b.w)); +} +VECTOR_MATH_API float fmaxf(const float4& a) +{ + return fmaxf(fmaxf(a.x, a.y), fmaxf(a.z, a.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float4 operator+(const float4& a, const float4& b) +{ + return make_float4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API float4 operator+(const float4& a, const float b) +{ + return make_float4(a.x + b, a.y + b, a.z + b, a.w + b); +} +VECTOR_MATH_API float4 operator+(const float a, const float4& b) +{ + return make_float4(a + b.x, a + b.y, a + b.z, a + b.w); +} +VECTOR_MATH_API void operator+=(float4& a, const float4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float4 operator-(const float4& a, const float4& b) +{ + return make_float4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} +VECTOR_MATH_API float4 operator-(const float4& a, const float b) +{ + return make_float4(a.x - b, a.y - b, a.z - b, a.w - b); +} +VECTOR_MATH_API float4 operator-(const float a, const float4& b) +{ + return make_float4(a - b.x, a - b.y, a - b.z, a - b.w); +} +VECTOR_MATH_API void operator-=(float4& a, const float4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float4 operator*(const float4& a, const float4& s) +{ + return make_float4(a.x * s.x, a.y * s.y, a.z * s.z, a.w * s.w); +} +VECTOR_MATH_API float4 operator*(const float4& a, const float s) +{ + return make_float4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API float4 operator*(const float s, const float4& a) +{ + return make_float4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(float4& a, const float4& s) +{ + a.x *= s.x; a.y *= s.y; a.z *= s.z; a.w *= s.w; +} +VECTOR_MATH_API void operator*=(float4& a, const float s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float4 operator/(const float4& a, const float4& b) +{ + return make_float4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API float4 operator/(const float4& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float4 operator/(const float s, const float4& a) +{ + return make_float4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(float4& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float4 lerp(const float4& a, const float4& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float4 bilerp(const float4& x00, const float4& x10, const float4& x01, const float4& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float4 clamp(const float4& v, const float a, const float b) +{ + return make_float4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API float4 clamp(const float4& v, const float4& a, const float4& b) +{ + return make_float4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float4& a, const float4& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +/** length */ +VECTOR_MATH_API float length(const float4& r) +{ + return sqrtf(dot(r, r)); +} + +/** normalize */ +VECTOR_MATH_API float4 normalize(const float4& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float4 floor(const float4& v) +{ + return make_float4(::floorf(v.x), ::floorf(v.y), ::floorf(v.z), ::floorf(v.w)); +} + +/** reflect */ +VECTOR_MATH_API float4 reflect(const float4& i, const float4& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** +* Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL +*/ +VECTOR_MATH_API float4 faceforward(const float4& n, const float4& i, const float4& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float4 expf(const float4& v) +{ + return make_float4(::expf(v.x), ::expf(v.y), ::expf(v.z), ::expf(v.w)); +} + +/** pow */ +VECTOR_MATH_API float4 powf(const float4& v, const float e) +{ + return make_float4(::powf(v.x, e), ::powf(v.y, e), ::powf(v.z, e), ::powf(v.w, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float4 sqrtf(const float4& v) +{ + return make_float4(::sqrtf(v.x), ::sqrtf(v.y), ::sqrtf(v.z), ::sqrtf(v.w)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float4& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float4& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + + +/* int functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API int clamp(const int f, const int a, const int b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int1& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int1& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int2 make_int2(const int s) +{ + return make_int2(s, s); +} +VECTOR_MATH_API int2 make_int2(const float2& a) +{ + return make_int2(int(a.x), int(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int2 operator-(const int2& a) +{ + return make_int2(-a.x, -a.y); +} + +/** min */ +VECTOR_MATH_API int2 min(const int2& a, const int2& b) +{ + return make_int2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API int2 max(const int2& a, const int2& b) +{ + return make_int2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int2 operator+(const int2& a, const int2& b) +{ + return make_int2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(int2& a, const int2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int2 operator-(const int2& a, const int2& b) +{ + return make_int2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API int2 operator-(const int2& a, const int b) +{ + return make_int2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(int2& a, const int2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int2 operator*(const int2& a, const int2& b) +{ + return make_int2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API int2 operator*(const int2& a, const int s) +{ + return make_int2(a.x * s, a.y * s); +} +VECTOR_MATH_API int2 operator*(const int s, const int2& a) +{ + return make_int2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(int2& a, const int s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int2 clamp(const int2& v, const int a, const int b) +{ + return make_int2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API int2 clamp(const int2& v, const int2& a, const int2& b) +{ + return make_int2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int2& a, const int2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const int2& a, const int2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int2& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int2& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int3 make_int3(const int s) +{ + return make_int3(s, s, s); +} +VECTOR_MATH_API int3 make_int3(const float3& a) +{ + return make_int3(int(a.x), int(a.y), int(a.z)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int3 operator-(const int3& a) +{ + return make_int3(-a.x, -a.y, -a.z); +} + +/** min */ +VECTOR_MATH_API int3 min(const int3& a, const int3& b) +{ + return make_int3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API int3 max(const int3& a, const int3& b) +{ + return make_int3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int3 operator+(const int3& a, const int3& b) +{ + return make_int3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(int3& a, const int3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int3 operator-(const int3& a, const int3& b) +{ + return make_int3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(int3& a, const int3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int3 operator*(const int3& a, const int3& b) +{ + return make_int3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API int3 operator*(const int3& a, const int s) +{ + return make_int3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API int3 operator*(const int s, const int3& a) +{ + return make_int3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(int3& a, const int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API int3 operator/(const int3& a, const int3& b) +{ + return make_int3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API int3 operator/(const int3& a, const int s) +{ + return make_int3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API int3 operator/(const int s, const int3& a) +{ + return make_int3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(int3& a, const int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int3 clamp(const int3& v, const int a, const int b) +{ + return make_int3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API int3 clamp(const int3& v, const int3& a, const int3& b) +{ + return make_int3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int3& a, const int3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const int3& a, const int3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int3& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int3& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int4 make_int4(const int s) +{ + return make_int4(s, s, s, s); +} +VECTOR_MATH_API int4 make_int4(const float4& a) +{ + return make_int4((int) a.x, (int) a.y, (int) a.z, (int) a.w); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int4 operator-(const int4& a) +{ + return make_int4(-a.x, -a.y, -a.z, -a.w); +} + +/** min */ +VECTOR_MATH_API int4 min(const int4& a, const int4& b) +{ + return make_int4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} + +/** max */ +VECTOR_MATH_API int4 max(const int4& a, const int4& b) +{ + return make_int4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int4 operator+(const int4& a, const int4& b) +{ + return make_int4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(int4& a, const int4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int4 operator-(const int4& a, const int4& b) +{ + return make_int4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(int4& a, const int4& b) +{ + a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int4 operator*(const int4& a, const int4& b) +{ + return make_int4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API int4 operator*(const int4& a, const int s) +{ + return make_int4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API int4 operator*(const int s, const int4& a) +{ + return make_int4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(int4& a, const int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API int4 operator/(const int4& a, const int4& b) +{ + return make_int4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API int4 operator/(const int4& a, const int s) +{ + return make_int4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API int4 operator/(const int s, const int4& a) +{ + return make_int4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(int4& a, const int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int4 clamp(const int4& v, const int a, const int b) +{ + return make_int4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API int4 clamp(const int4& v, const int4& a, const int4& b) +{ + return make_int4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int4& a, const int4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const int4& a, const int4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int4& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int4& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* uint functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API unsigned int clamp(const unsigned int f, const unsigned int a, const unsigned int b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned int getByIndex(const uint1& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(uint1& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint2 make_uint2(const unsigned int s) +{ + return make_uint2(s, s); +} +VECTOR_MATH_API uint2 make_uint2(const float2& a) +{ + return make_uint2((unsigned int) a.x, (unsigned int) a.y); +} +/** @} */ + +/** min */ +VECTOR_MATH_API uint2 min(const uint2& a, const uint2& b) +{ + return make_uint2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API uint2 max(const uint2& a, const uint2& b) +{ + return make_uint2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API uint2 operator+(const uint2& a, const uint2& b) +{ + return make_uint2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(uint2& a, const uint2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint2 operator-(const uint2& a, const uint2& b) +{ + return make_uint2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API uint2 operator-(const uint2& a, const unsigned int b) +{ + return make_uint2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(uint2& a, const uint2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint2 operator*(const uint2& a, const uint2& b) +{ + return make_uint2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API uint2 operator*(const uint2& a, const unsigned int s) +{ + return make_uint2(a.x * s, a.y * s); +} +VECTOR_MATH_API uint2 operator*(const unsigned int s, const uint2& a) +{ + return make_uint2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(uint2& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint2 clamp(const uint2& v, const unsigned int a, const unsigned int b) +{ + return make_uint2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API uint2 clamp(const uint2& v, const uint2& a, const uint2& b) +{ + return make_uint2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint2& a, const uint2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const uint2& a, const uint2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned int getByIndex(const uint2& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(uint2& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint3 make_uint3(const unsigned int s) +{ + return make_uint3(s, s, s); +} +VECTOR_MATH_API uint3 make_uint3(const float3& a) +{ + return make_uint3((unsigned int) a.x, (unsigned int) a.y, (unsigned int) a.z); +} +/** @} */ + +/** min */ +VECTOR_MATH_API uint3 min(const uint3& a, const uint3& b) +{ + return make_uint3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API uint3 max(const uint3& a, const uint3& b) +{ + return make_uint3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API uint3 operator+(const uint3& a, const uint3& b) +{ + return make_uint3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(uint3& a, const uint3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint3 operator-(const uint3& a, const uint3& b) +{ + return make_uint3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(uint3& a, const uint3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint3 operator*(const uint3& a, const uint3& b) +{ + return make_uint3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API uint3 operator*(const uint3& a, const unsigned int s) +{ + return make_uint3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API uint3 operator*(const unsigned int s, const uint3& a) +{ + return make_uint3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(uint3& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API uint3 operator/(const uint3& a, const uint3& b) +{ + return make_uint3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API uint3 operator/(const uint3& a, const unsigned int s) +{ + return make_uint3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API uint3 operator/(const unsigned int s, const uint3& a) +{ + return make_uint3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(uint3& a, const unsigned int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint3 clamp(const uint3& v, const unsigned int a, const unsigned int b) +{ + return make_uint3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API uint3 clamp(const uint3& v, const uint3& a, const uint3& b) +{ + return make_uint3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint3& a, const uint3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const uint3& a, const uint3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API unsigned int getByIndex(const uint3& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(uint3& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint4 make_uint4(const unsigned int s) +{ + return make_uint4(s, s, s, s); +} +VECTOR_MATH_API uint4 make_uint4(const float4& a) +{ + return make_uint4((unsigned int) a.x, (unsigned int) a.y, (unsigned int) a.z, (unsigned int) a.w); +} +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API uint4 min(const uint4& a, const uint4& b) +{ + return make_uint4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API uint4 max(const uint4& a, const uint4& b) +{ + return make_uint4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API uint4 operator+(const uint4& a, const uint4& b) +{ + return make_uint4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(uint4& a, const uint4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint4 operator-(const uint4& a, const uint4& b) +{ + return make_uint4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(uint4& a, const uint4& b) +{ + a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint4 operator*(const uint4& a, const uint4& b) +{ + return make_uint4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API uint4 operator*(const uint4& a, const unsigned int s) +{ + return make_uint4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API uint4 operator*(const unsigned int s, const uint4& a) +{ + return make_uint4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(uint4& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API uint4 operator/(const uint4& a, const uint4& b) +{ + return make_uint4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API uint4 operator/(const uint4& a, const unsigned int s) +{ + return make_uint4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API uint4 operator/(const unsigned int s, const uint4& a) +{ + return make_uint4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(uint4& a, const unsigned int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint4 clamp(const uint4& v, const unsigned int a, const unsigned int b) +{ + return make_uint4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API uint4 clamp(const uint4& v, const uint4& a, const uint4& b) +{ + return make_uint4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint4& a, const uint4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const uint4& a, const uint4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API unsigned int getByIndex(const uint4& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(uint4& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + +/* long long functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API long long clamp(const long long f, const long long a, const long long b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong1& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong1& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong2 make_longlong2(const long long s) +{ + return make_longlong2(s, s); +} +VECTOR_MATH_API longlong2 make_longlong2(const float2& a) +{ + return make_longlong2(int(a.x), int(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong2 operator-(const longlong2& a) +{ + return make_longlong2(-a.x, -a.y); +} + +/** min */ +VECTOR_MATH_API longlong2 min(const longlong2& a, const longlong2& b) +{ + return make_longlong2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API longlong2 max(const longlong2& a, const longlong2& b) +{ + return make_longlong2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong2 operator+(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(longlong2& a, const longlong2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong2 operator-(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API longlong2 operator-(const longlong2& a, const long long b) +{ + return make_longlong2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(longlong2& a, const longlong2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong2 operator*(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API longlong2 operator*(const longlong2& a, const long long s) +{ + return make_longlong2(a.x * s, a.y * s); +} +VECTOR_MATH_API longlong2 operator*(const long long s, const longlong2& a) +{ + return make_longlong2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(longlong2& a, const long long s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong2 clamp(const longlong2& v, const long long a, const long long b) +{ + return make_longlong2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API longlong2 clamp(const longlong2& v, const longlong2& a, const longlong2& b) +{ + return make_longlong2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong2& a, const longlong2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const longlong2& a, const longlong2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong2& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong2& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong3 make_longlong3(const long long s) +{ + return make_longlong3(s, s, s); +} +VECTOR_MATH_API longlong3 make_longlong3(const float3& a) +{ + return make_longlong3((long long) a.x, (long long) a.y, (long long) a.z); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong3 operator-(const longlong3& a) +{ + return make_longlong3(-a.x, -a.y, -a.z); +} + +/** min */ +VECTOR_MATH_API longlong3 min(const longlong3& a, const longlong3& b) +{ + return make_longlong3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API longlong3 max(const longlong3& a, const longlong3& b) +{ + return make_longlong3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong3 operator+(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(longlong3& a, const longlong3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong3 operator-(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(longlong3& a, const longlong3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong3 operator*(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API longlong3 operator*(const longlong3& a, const long long s) +{ + return make_longlong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API longlong3 operator*(const long long s, const longlong3& a) +{ + return make_longlong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(longlong3& a, const long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API longlong3 operator/(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API longlong3 operator/(const longlong3& a, const long long s) +{ + return make_longlong3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API longlong3 operator/(const long long s, const longlong3& a) +{ + return make_longlong3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(longlong3& a, const long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong3 clamp(const longlong3& v, const long long a, const long long b) +{ + return make_longlong3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API longlong3 clamp(const longlong3& v, const longlong3& a, const longlong3& b) +{ + return make_longlong3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong3& a, const longlong3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const longlong3& a, const longlong3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong3& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong3& v, int i, int x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong4 make_longlong4(const long long s) +{ + return make_longlong4(s, s, s, s); +} +VECTOR_MATH_API longlong4 make_longlong4(const float4& a) +{ + return make_longlong4((long long) a.x, (long long) a.y, (long long) a.z, (long long) a.w); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong4 operator-(const longlong4& a) +{ + return make_longlong4(-a.x, -a.y, -a.z, -a.w); +} + +/** min */ +VECTOR_MATH_API longlong4 min(const longlong4& a, const longlong4& b) +{ + return make_longlong4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} + +/** max */ +VECTOR_MATH_API longlong4 max(const longlong4& a, const longlong4& b) +{ + return make_longlong4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong4 operator+(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(longlong4& a, const longlong4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong4 operator-(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(longlong4& a, const longlong4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong4 operator*(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API longlong4 operator*(const longlong4& a, const long long s) +{ + return make_longlong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API longlong4 operator*(const long long s, const longlong4& a) +{ + return make_longlong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(longlong4& a, const long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API longlong4 operator/(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API longlong4 operator/(const longlong4& a, const long long s) +{ + return make_longlong4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API longlong4 operator/(const long long s, const longlong4& a) +{ + return make_longlong4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(longlong4& a, const long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong4 clamp(const longlong4& v, const long long a, const long long b) +{ + return make_longlong4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API longlong4 clamp(const longlong4& v, const longlong4& a, const longlong4& b) +{ + return make_longlong4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong4& a, const longlong4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const longlong4& a, const longlong4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong4& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong4& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + +/* ulonglong functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API unsigned long long clamp(const unsigned long long f, const unsigned long long a, const unsigned long long b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong1& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(ulonglong1& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong2 make_ulonglong2(const unsigned long long s) +{ + return make_ulonglong2(s, s); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const float2& a) +{ + return make_ulonglong2((unsigned long long)a.x, (unsigned long long)a.y); +} +/** @} */ + +/** min */ +VECTOR_MATH_API ulonglong2 min(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API ulonglong2 max(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator+(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(ulonglong2& a, const ulonglong2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator-(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API ulonglong2 operator-(const ulonglong2& a, const unsigned long long b) +{ + return make_ulonglong2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(ulonglong2& a, const ulonglong2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator*(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API ulonglong2 operator*(const ulonglong2& a, const unsigned long long s) +{ + return make_ulonglong2(a.x * s, a.y * s); +} +VECTOR_MATH_API ulonglong2 operator*(const unsigned long long s, const ulonglong2& a) +{ + return make_ulonglong2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(ulonglong2& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong2 clamp(const ulonglong2& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API ulonglong2 clamp(const ulonglong2& v, const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong2& a, const ulonglong2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const ulonglong2& a, const ulonglong2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong2& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(ulonglong2& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong3 make_ulonglong3(const unsigned long long s) +{ + return make_ulonglong3(s, s, s); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const float3& a) +{ + return make_ulonglong3((unsigned long long)a.x, (unsigned long long)a.y, (unsigned long long)a.z); +} +/** @} */ + +/** min */ +VECTOR_MATH_API ulonglong3 min(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API ulonglong3 max(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator+(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(ulonglong3& a, const ulonglong3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator-(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(ulonglong3& a, const ulonglong3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator*(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API ulonglong3 operator*(const ulonglong3& a, const unsigned long long s) +{ + return make_ulonglong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API ulonglong3 operator*(const unsigned long long s, const ulonglong3& a) +{ + return make_ulonglong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(ulonglong3& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator/(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API ulonglong3 operator/(const ulonglong3& a, const unsigned long long s) +{ + return make_ulonglong3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API ulonglong3 operator/(const unsigned long long s, const ulonglong3& a) +{ + return make_ulonglong3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(ulonglong3& a, const unsigned long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong3 clamp(const ulonglong3& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API ulonglong3 clamp(const ulonglong3& v, const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong3& a, const ulonglong3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const ulonglong3& a, const ulonglong3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong3& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(ulonglong3& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long s) +{ + return make_ulonglong4(s, s, s, s); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const float4& a) +{ + return make_ulonglong4((unsigned long long)a.x, (unsigned long long)a.y, (unsigned long long)a.z, (unsigned long long)a.w); +} +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API ulonglong4 min(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API ulonglong4 max(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator+(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(ulonglong4& a, const ulonglong4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator-(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(ulonglong4& a, const ulonglong4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator*(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API ulonglong4 operator*(const ulonglong4& a, const unsigned long long s) +{ + return make_ulonglong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API ulonglong4 operator*(const unsigned long long s, const ulonglong4& a) +{ + return make_ulonglong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(ulonglong4& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator/(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API ulonglong4 operator/(const ulonglong4& a, const unsigned long long s) +{ + return make_ulonglong4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API ulonglong4 operator/(const unsigned long long s, const ulonglong4& a) +{ + return make_ulonglong4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(ulonglong4& a, const unsigned long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong4 clamp(const ulonglong4& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API ulonglong4 clamp(const ulonglong4& v, const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong4& a, const ulonglong4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const ulonglong4& a, const ulonglong4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong4& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(ulonglong4& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/******************************************************************************/ + +/** Narrowing functions +* @{ +*/ +VECTOR_MATH_API int2 make_int2(const int3& v0) +{ + return make_int2(v0.x, v0.y); +} +VECTOR_MATH_API int2 make_int2(const int4& v0) +{ + return make_int2(v0.x, v0.y); +} +VECTOR_MATH_API int3 make_int3(const int4& v0) +{ + return make_int3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API uint2 make_uint2(const uint3& v0) +{ + return make_uint2(v0.x, v0.y); +} +VECTOR_MATH_API uint2 make_uint2(const uint4& v0) +{ + return make_uint2(v0.x, v0.y); +} +VECTOR_MATH_API uint3 make_uint3(const uint4& v0) +{ + return make_uint3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API longlong2 make_longlong2(const longlong3& v0) +{ + return make_longlong2(v0.x, v0.y); +} +VECTOR_MATH_API longlong2 make_longlong2(const longlong4& v0) +{ + return make_longlong2(v0.x, v0.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const longlong4& v0) +{ + return make_longlong3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const ulonglong3& v0) +{ + return make_ulonglong2(v0.x, v0.y); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const ulonglong4& v0) +{ + return make_ulonglong2(v0.x, v0.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const ulonglong4& v0) +{ + return make_ulonglong3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API float2 make_float2(const float3& v0) +{ + return make_float2(v0.x, v0.y); +} +VECTOR_MATH_API float2 make_float2(const float4& v0) +{ + return make_float2(v0.x, v0.y); +} +VECTOR_MATH_API float2 make_float2(const uint3& v0) +{ + return make_float2(float(v0.x), float(v0.y)); +} +VECTOR_MATH_API float3 make_float3(const float4& v0) +{ + return make_float3(v0.x, v0.y, v0.z); +} +/** @} */ + +/** Assemble functions from smaller vectors +* @{ +*/ +VECTOR_MATH_API int3 make_int3(const int v0, const int2& v1) +{ + return make_int3(v0, v1.x, v1.y); +} +VECTOR_MATH_API int3 make_int3(const int2& v0, const int v1) +{ + return make_int3(v0.x, v0.y, v1); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int v1, const int2& v2) +{ + return make_int4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int2& v1, const int v2) +{ + return make_int4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API int4 make_int4(const int2& v0, const int v1, const int v2) +{ + return make_int4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int3& v1) +{ + return make_int4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API int4 make_int4(const int3& v0, const int v1) +{ + return make_int4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API int4 make_int4(const int2& v0, const int2& v1) +{ + return make_int4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API uint3 make_uint3(const unsigned int v0, const uint2& v1) +{ + return make_uint3(v0, v1.x, v1.y); +} +VECTOR_MATH_API uint3 make_uint3(const uint2& v0, const unsigned int v1) +{ + return make_uint3(v0.x, v0.y, v1); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const unsigned int v1, const uint2& v2) +{ + return make_uint4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const uint2& v1, const unsigned int v2) +{ + return make_uint4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API uint4 make_uint4(const uint2& v0, const unsigned int v1, const unsigned int v2) +{ + return make_uint4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const uint3& v1) +{ + return make_uint4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API uint4 make_uint4(const uint3& v0, const unsigned int v1) +{ + return make_uint4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API uint4 make_uint4(const uint2& v0, const uint2& v1) +{ + return make_uint4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const long long v0, const longlong2& v1) +{ + return make_longlong3(v0, v1.x, v1.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const longlong2& v0, const long long v1) +{ + return make_longlong3(v0.x, v0.y, v1); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const long long v1, const longlong2& v2) +{ + return make_longlong4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const longlong2& v1, const long long v2) +{ + return make_longlong4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong2& v0, const long long v1, const long long v2) +{ + return make_longlong4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const longlong3& v1) +{ + return make_longlong4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong3& v0, const long long v1) +{ + return make_longlong4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong2& v0, const longlong2& v1) +{ + return make_longlong4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const unsigned long long v0, const ulonglong2& v1) +{ + return make_ulonglong3(v0, v1.x, v1.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const ulonglong2& v0, const unsigned long long v1) +{ + return make_ulonglong3(v0.x, v0.y, v1); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const unsigned long long v1, const ulonglong2& v2) +{ + return make_ulonglong4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const ulonglong2& v1, const unsigned long long v2) +{ + return make_ulonglong4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong2& v0, const unsigned long long v1, const unsigned long long v2) +{ + return make_ulonglong4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const ulonglong3& v1) +{ + return make_ulonglong4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong3& v0, const unsigned long long v1) +{ + return make_ulonglong4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong2& v0, const ulonglong2& v1) +{ + return make_ulonglong4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API float3 make_float3(const float2& v0, const float v1) +{ + return make_float3(v0.x, v0.y, v1); +} +VECTOR_MATH_API float3 make_float3(const float v0, const float2& v1) +{ + return make_float3(v0, v1.x, v1.y); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float v1, const float2& v2) +{ + return make_float4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float2& v1, const float v2) +{ + return make_float4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API float4 make_float4(const float2& v0, const float v1, const float v2) +{ + return make_float4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float3& v1) +{ + return make_float4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API float4 make_float4(const float3& v0, const float v1) +{ + return make_float4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API float4 make_float4(const float2& v0, const float2& v1) +{ + return make_float4(v0.x, v0.y, v1.x, v1.y); +} +/** @} */ + +#endif // VECTOR_MATH_H diff --git a/apps/bench_shared/shaders/vertex_attributes.h b/apps/bench_shared/shaders/vertex_attributes.h new file mode 100644 index 00000000..fda243e5 --- /dev/null +++ b/apps/bench_shared/shaders/vertex_attributes.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef VERTEX_ATTRIBUTES_H +#define VERTEX_ATTRIBUTES_H + +struct TriangleAttributes +{ + float3 vertex; + float3 tangent; + float3 normal; + float3 texcoord; +}; + +#endif // VERTEX_ATTRIBUTES_H diff --git a/apps/bench_shared/src/Application.cpp b/apps/bench_shared/src/Application.cpp new file mode 100644 index 00000000..eccc1e11 --- /dev/null +++ b/apps/bench_shared/src/Application.cpp @@ -0,0 +1,2461 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Application.h" +#include "inc/Parser.h" +#include "inc/Raytracer.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "inc/CheckMacros.h" + +#include "inc/MyAssert.h" + +Application::Application(GLFWwindow* window, const Options& options) +: m_window(window) +, m_isValid(false) +, m_guiState(GUI_STATE_NONE) +, m_isVisibleGUI(false) // DAR HACK Hide the GUI by default to allow better interactive to offscreen benchmark comparisons. +, m_width(512) +, m_height(512) +, m_mode(0) +, m_maskDevices(0x00FFFFFF) // A maximum of 24 devices is supported by default. Limited by the UUID arrays +, m_sizeArena(64) // Default to 64 MiB Arenas when nothing is specified inside the system description. +, m_light(0) +, m_miss(1) +, m_interop(0) +, m_peerToPeer(P2P_TEX | P2P_GAS) // Enable material texture and GAS sharing via NVLINK only by default. +, m_present(false) +, m_presentNext(true) +, m_presentAtSecond(1.0) +, m_previousComplete(false) +, m_lensShader(LENS_SHADER_PINHOLE) +, m_samplesSqrt(1) +, m_epsilonFactor(500.0f) +, m_environmentRotation(0.0f) +, m_clockFactor(1000.0f) +, m_mouseSpeedRatio(10.0f) +, m_idGroup(0) +, m_idInstance(0) +, m_idGeometry(0) +{ + try + { + m_timer.restart(); + + // Initialize the top-level keywords of the scene description for faster search. + m_mapKeywordScene["albedo"] = KS_ALBEDO; + m_mapKeywordScene["albedoTexture"] = KS_ALBEDO_TEXTURE; + m_mapKeywordScene["cutoutTexture"] = KS_CUTOUT_TEXTURE; + m_mapKeywordScene["roughness"] = KS_ROUGHNESS; + m_mapKeywordScene["absorption"] = KS_ABSORPTION; + m_mapKeywordScene["absorptionScale"] = KS_ABSORPTION_SCALE; + m_mapKeywordScene["ior"] = KS_IOR; + m_mapKeywordScene["thinwalled"] = KS_THINWALLED; + m_mapKeywordScene["material"] = KS_MATERIAL; + m_mapKeywordScene["identity"] = KS_IDENTITY; + m_mapKeywordScene["push"] = KS_PUSH; + m_mapKeywordScene["pop"] = KS_POP; + m_mapKeywordScene["rotate"] = KS_ROTATE; + m_mapKeywordScene["scale"] = KS_SCALE; + m_mapKeywordScene["translate"] = KS_TRANSLATE; + m_mapKeywordScene["model"] = KS_MODEL; + + const double timeConstructor = m_timer.getTime(); + + m_width = std::max(1, options.getWidth()); + m_height = std::max(1, options.getHeight()); + m_mode = std::max(0, options.getMode()); + m_optimize = options.getOptimize(); + + // Initialize the system options to minimum defaults to work, but require useful settings inside the system options file. + // The minimum path length values will generate useful direct lighting results, but transmissions will be mostly black. + m_resolution = make_int2(1, 1); + m_tileSize = make_int2(8, 8); + m_pathLengths = make_int2(0, 2); + + m_prefixScreenshot = std::string("./img"); // Default to current working directory and prefix "img". + + // Tonmapper neutral defaults. The system description overrides these. + m_tonemapperGUI.gamma = 1.0f; + m_tonemapperGUI.whitePoint = 1.0f; + m_tonemapperGUI.colorBalance[0] = 1.0f; + m_tonemapperGUI.colorBalance[1] = 1.0f; + m_tonemapperGUI.colorBalance[2] = 1.0f; + m_tonemapperGUI.burnHighlights = 1.0f; + m_tonemapperGUI.crushBlacks = 0.0f; + m_tonemapperGUI.saturation = 1.0f; + m_tonemapperGUI.brightness = 1.0f; + + // System wide parameters are loaded from this file to keep the number of command line options small. + const std::string filenameSystem = options.getSystem(); + if (!loadSystemDescription(filenameSystem)) + { + std::cerr << "ERROR: Application() failed to load system description file " << filenameSystem << '\n'; + MY_ASSERT(!"Failed to load system description"); + return; // m_isValid == false. + } + + std::cout << "Arena size = " << m_sizeArena << " MB\n"; + +# if 0 + // DAR HACK Generate 5x5x5 1024x1024 textures with different RGB8 colors. + // These are tiny when compressed because there is only one color inside the whole image. + // This needs to happen after the system description is loaded becuase the filename is based on the m_prefixScreenshot string. + unsigned int previous = 0; + + for (unsigned int z = 0; z < 5; ++z) + { + for (unsigned int y = 0; y < 5; ++y) + { + for (unsigned int x = 0; x < 5; ++x) + { + previous = previous * 1664525u + 1013904223u; + + const unsigned char r = previous & 0xFF; + const unsigned char g = (previous >> 8) & 0xFF; + const unsigned char b = (previous >> 16) & 0xFF; + + generatePNG(1024, 1024, r, g, b); + } + } + } + return; // This keeps the app invalid. +#endif + + // The user interface is part of the main application. + // Setup ImGui binding. + ImGui::CreateContext(); + ImGui_ImplGlfwGL3_Init(window, true); + + // This initializes the GLFW part including the font texture. + ImGui_ImplGlfwGL3_NewFrame(); + ImGui::EndFrame(); + +#if 0 + // Style the GUI colors to a neutral greyscale with plenty of transparency to concentrate on the image. + ImGuiStyle& style = ImGui::GetStyle(); + + // Change these RGB values to get any other tint. + const float r = 1.0f; + const float g = 1.0f; + const float b = 1.0f; + + style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.6f); + style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_PopupBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_Border] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(r * 0.0f, g * 0.0f, b * 0.0f, 0.4f); + style.Colors[ImGuiCol_FrameBg] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_TitleBg] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.2f); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Button] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Header] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Column] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ColumnHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ColumnActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_CloseButton] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_CloseButtonHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_PlotLines] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 1.0f); + style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_PlotHistogram] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 1.0f); + style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(r * 0.5f, g * 0.5f, b * 0.5f, 1.0f); + style.Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.2f); + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(r * 1.0f, g * 1.0f, b * 0.0f, 1.0f); // Yellow + style.Colors[ImGuiCol_NavHighlight] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); +#endif + + const double timeGUI = m_timer.getTime(); + + m_camera.setResolution(m_resolution.x, m_resolution.y); + m_camera.setSpeedRatio(m_mouseSpeedRatio); + + // Initialize the OpenGL rasterizer. + m_rasterizer = std::make_unique(m_width, m_height, m_interop); + + // Must set the resolution explicitly to be able to calculate + // the proper vertex attributes for display and the PBO size in case of interop. + m_rasterizer->setResolution(m_resolution.x, m_resolution.y); + m_rasterizer->setTonemapper(m_tonemapperGUI); + + const unsigned int tex = m_rasterizer->getTextureObject(); + const unsigned int pbo = m_rasterizer->getPixelBufferObject(); + + const double timeRasterizer = m_timer.getTime(); + + m_raytracer = std::make_unique(m_maskDevices, m_miss, m_interop, tex, pbo, m_sizeArena, m_peerToPeer); + + // If the raytracer could not be initialized correctly, return and leave Application invalid. + if (!m_raytracer->m_isValid) + { + std::cerr << "ERROR: Application() Could not initialize Raytracer\n"; + return; // Exit application. + } + + // Determine which device is the one running the OpenGL implementation. + // The first OpenGL-CUDA device match wins. + int deviceMatch = -1; + +#if 1 + // UUID works under Windows and Linux. + const int numDevicesOGL = m_rasterizer->getNumDevices(); + + for (int i = 0; i < numDevicesOGL && deviceMatch == -1; ++i) + { + deviceMatch = m_raytracer->matchUUID(reinterpret_cast(m_rasterizer->getUUID(i))); + } +#else + // LUID only works under Windows because it requires the EXT_external_objects_win32 extension. + // DEBUG With multicast enabled, both devices have the same LUID and the OpenGL node mask is the OR of the individual device node masks. + // Means the result of the deviceMatch here is depending on the CUDA device order. + // Seems like multicast needs to handle CUDA - OpenGL interop differently. + // With multicast enabled, uploading the PBO with glTexImage2D halves the framerate when presenting each image in both the single-GPU and multi-GPU P2P strategy. + // Means there is an expensive PCI-E copy going on in that case. + const unsigned char* luid = m_rasterizer->getLUID(); + const int nodeMask = m_rasterizer->getNodeMask(); + + // The cuDeviceGetLuid() takes char* and unsigned int though. + deviceMatch = m_raytracer->matchLUID(reinterpret_cast(luid), nodeMask); +#endif + + if (deviceMatch == -1) + { + if (m_interop == INTEROP_MODE_TEX) + { + std::cerr << "ERROR: Application() OpenGL texture image interop without OpenGL device in active devices will not display the image!\n"; + return; // Exit application. + } + if (m_interop == INTEROP_MODE_PBO) + { + std::cerr << "WARNING: Application() OpenGL pixel buffer interop without OpenGL device in active devices will result in reduced performance!\n"; + } + } + + m_state.resolution = m_resolution; + m_state.tileSize = m_tileSize; + m_state.pathLengths = m_pathLengths; + m_state.samplesSqrt = m_samplesSqrt; + m_state.lensShader = m_lensShader; + m_state.epsilonFactor = m_epsilonFactor; + m_state.envRotation = m_environmentRotation; + m_state.clockFactor = m_clockFactor; + + // Sync the state with the default GUI data. + m_raytracer->initState(m_state); + + const double timeRaytracer = m_timer.getTime(); + + // Host side scene graph information. + m_scene = std::make_shared(m_idGroup++); // Create the scene's root group first. + + createPictures(); + createCameras(); + createLights(); + + // Load the scene description file and generate the host side scene. + const std::string filenameScene = options.getScene(); + if (!loadSceneDescription(filenameScene)) + { + std::cerr << "ERROR: Application() failed to load scene description file " << filenameScene << '\n'; + MY_ASSERT(!"Failed to load scene description"); + return; + } + + MY_ASSERT(m_idGeometry == m_geometries.size()); + + const double timeScene = m_timer.getTime(); + + // Device side scene information. + m_raytracer->initTextures(m_mapPictures); + m_raytracer->initCameras(m_cameras); // Currently there is only one but this supports arbitrary many which could be used to select viewpoints or do animation (and camera motion blur) in the future. + m_raytracer->initLights(m_lights); // DAR FIXME Encode the environment texture data into the LightDefinition. + m_raytracer->initMaterials(m_materialsGUI); // This will handle the per material textures as well. + m_raytracer->initScene(m_scene, m_idGeometry); // m_idGeometry is the number of geometries in the scene. + + const double timeRenderer = m_timer.getTime(); + + // Print out how long the initialization of each module took. + std::cout << "Application() " << timeRenderer - timeConstructor << " seconds overall\n"; + std::cout << "{\n"; + std::cout << " GUI = " << timeGUI - timeConstructor << " seconds\n"; + std::cout << " Rasterizer = " << timeRasterizer - timeGUI << " seconds\n"; + std::cout << " Raytracer = " << timeRaytracer - timeRasterizer << " seconds\n"; + std::cout << " Scene = " << timeScene - timeRaytracer << " seconds\n"; + std::cout << " Renderer = " << timeRenderer - timeScene << " seconds\n"; + std::cout << "}\n"; + + restartRendering(); // Trigger a new rendering. + + m_isValid = true; + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + +Application::~Application() +{ + for (std::map::const_iterator it = m_mapPictures.begin(); it != m_mapPictures.end(); ++it) + { + delete it->second; + } + + ImGui_ImplGlfwGL3_Shutdown(); + ImGui::DestroyContext(); +} + +bool Application::isValid() const +{ + return m_isValid; +} + +void Application::reshape(const int w, const int h) +{ + // Do not allow zero size client windows! All other reshape() routines depend on this and do not check this again. + if ( (m_width != w || m_height != h) && w != 0 && h != 0) + { + m_width = w; + m_height = h; + + m_rasterizer->reshape(m_width, m_height); + } +} + +void Application::restartRendering() +{ + guiRenderingIndicator(true); + + m_presentNext = true; + m_presentAtSecond = 1.0; + + m_previousComplete = false; + + m_timer.restart(); +} + +bool Application::render() +{ + bool finish = false; + bool flush = false; + + try + { + CameraDefinition camera; + + const bool cameraChanged = m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W); + if (cameraChanged) + { + m_cameras[0] = camera; + m_raytracer->updateCamera(0, camera); + + restartRendering(); + } + + const unsigned int iterationIndex = m_raytracer->render(); + + // When the renderer has completed all iterations, change the GUI title bar to green. + const bool complete = ((unsigned int)(m_samplesSqrt * m_samplesSqrt) <= iterationIndex); + + if (complete) + { + guiRenderingIndicator(false); // Not rendering anymore. + + flush = !m_previousComplete && complete; // Completion status changed to true. + } + + m_previousComplete = complete; + + // When benchmark is enabled, exit the application when the requested samples per pixel have been rendered. + // Actually this render() function is not called when m_mode == 1 but keep the finish here to exit on exceptions. + finish = ((m_mode == 1) && complete); + + // Only update the texture when a restart happened, one second passed to reduce required bandwidth, or the rendering is newly complete. + if (m_presentNext || flush) + { + m_raytracer->updateDisplayTexture(); // This directly updates the display HDR texture for all rendering strategies. + + m_presentNext = m_present; + } + + double seconds = m_timer.getTime(); +#if 1 + // When in interactive mode, show the all rendered frames during the first half second to get some initial refinement. + if (m_mode == 0 && seconds < 0.5) + { + m_presentAtSecond = 1.0; + m_presentNext = true; + } +#endif + + if (m_presentAtSecond < seconds || flush || finish) // Print performance every second or when the rendering is complete or the benchmark finished. + { + m_presentAtSecond = ceil(seconds); + m_presentNext = true; // Present at least every second. + + if (flush || finish) // Only print the performance when the samples per pixels are reached. + { + const double fps = double(iterationIndex) / seconds; + + std::ostringstream stream; + stream.precision(3); // Precision is # digits in fraction part. + stream << std::fixed << iterationIndex << " / " << seconds << " = " << fps << " fps"; + std::cout << stream.str() << '\n'; + +#if 0 // Automated benchmark in interactive mode. Change m_isVisibleGUI default to false! + std::ostringstream filename; + filename << "result_interactive_" << m_interop << "_" << m_tileSize.x << "_" << m_tileSize.y << ".log"; + const bool success = saveString(filename.str(), stream.str()); + if (success) + { + std::cout << filename.str() << '\n'; // Print out the filename to indicate success. + } + finish = true; // Exit application after interactive rendering finished. +#endif + } + } + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + finish = true; + } + return finish; +} + +void Application::benchmark() +{ + try + { + const unsigned int spp = (unsigned int)(m_samplesSqrt * m_samplesSqrt); + unsigned int iterationIndex = 0; + + m_timer.restart(); + + while (iterationIndex < spp) + { + iterationIndex = m_raytracer->render(); + } + + m_raytracer->synchronize(); // Wait until any asynchronous operations have finished. + + const double seconds = m_timer.getTime(); + const double fps = double(iterationIndex) / seconds; + + std::ostringstream stream; + stream.precision(3); // Precision is # digits in fraction part. + stream << std::fixed << iterationIndex << " / " << seconds << " = " << fps << " fps"; + std::cout << stream.str() << '\n'; + +#if 0 // Automated benchmark in batch mode. + std::ostringstream filename; + filename << "result_batch_" << m_interop << "_" << m_tileSize.x << "_" << m_tileSize.y << ".log"; + const bool success = saveString(filename.str(), stream.str()); + if (success) + { + std::cout << filename.str() << '\n'; // Print out the filename to indicate success. + } +#endif + + screenshot(true); + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + +void Application::display() +{ + m_rasterizer->display(); +} + +void Application::guiNewFrame() +{ + ImGui_ImplGlfwGL3_NewFrame(); +} + +void Application::guiReferenceManual() +{ + ImGui::ShowTestWindow(); +} + +void Application::guiRender() +{ + ImGui::Render(); + ImGui_ImplGlfwGL3_RenderDrawData(ImGui::GetDrawData()); +} + +// DAR FIXME Move all environment texture handling into the the LightDefinition. +void Application::createPictures() +{ + if (m_miss == 2 && !m_environment.empty()) + { + Picture* picture = new Picture(); + picture->load(m_environment, IMAGE_FLAG_2D | IMAGE_FLAG_ENV); // Special case for the spherical environment. + + m_mapPictures[std::string("environment")] = picture; + } +} + +void Application::createCameras() +{ + CameraDefinition camera; + + m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W, true); + + m_cameras.push_back(camera); +} + +void Application::createLights() +{ + LightDefinition light; + + // Unused in environment lights. + light.position = make_float3(0.0f, 0.0f, 0.0f); + light.vecU = make_float3(1.0f, 0.0f, 0.0f); + light.vecV = make_float3(0.0f, 1.0f, 0.0f); + light.normal = make_float3(0.0f, 0.0f, 1.0f); + light.area = 1.0f; + light.emission = make_float3(1.0f, 1.0f, 1.0f); + + // The environment light is expected in sysData.lightDefinitions[0], but since there is only one, + // the sysData struct contains the data for the spherical HDR environment light when enabled. + // All other lights are indexed by their position inside the array. + switch (m_miss) + { + case 0: // No environment light at all. Faster than a zero emission constant environment! + default: + break; + + case 1: // Constant environment light. + case 2: // HDR Environment mapping with loaded texture. Texture handling happens in the Raytracer::initTextures(). + light.type = LIGHT_ENVIRONMENT; + light.area = 4.0f * M_PIf; // Unused. + + m_lights.push_back(light); // The environment light is always in m_lights[0]. + break; + } + + const int indexLight = static_cast(m_lights.size()); + float3 normal; + + switch (m_light) + { + case 0: // No area light. + default: + break; + + case 1: // Add a 1x1 square area light over the scene objects at y = 1.95 to fit into a 2x2x2 box. + light.type = LIGHT_PARALLELOGRAM; // A geometric area light with diffuse emission distribution function. + light.position = make_float3(-0.5f, 1.95f, -0.5f); // Corner position. + light.vecU = make_float3(1.0f, 0.0f, 0.0f); // To the right. + light.vecV = make_float3(0.0f, 0.0f, 1.0f); // To the front. + normal = cross(light.vecU, light.vecV); // Length of the cross product is the area. + light.area = length(normal); // Calculate the world space area of that rectangle, unit is [m^2] + light.normal = normal / light.area; // Normalized normal + light.emission = make_float3(10.0f); // Radiant exitance in Watt/m^2. + m_lights.push_back(light); + break; + + case 2: // Add a 4x4 square area light over the scene objects at y = 4.0. + light.type = LIGHT_PARALLELOGRAM; // A geometric area light with diffuse emission distribution function. + light.position = make_float3(-2.0f, 4.0f, -2.0f); // Corner position. + light.vecU = make_float3(4.0f, 0.0f, 0.0f); // To the right. + light.vecV = make_float3(0.0f, 0.0f, 4.0f); // To the front. + normal = cross(light.vecU, light.vecV); // Length of the cross product is the area. + light.area = length(normal); // Calculate the world space area of that rectangle, unit is [m^2] + light.normal = normal / light.area; // Normalized normal + light.emission = make_float3(10.0f); // Radiant exitance in Watt/m^2. + m_lights.push_back(light); + break; + } + + if (0 < m_light) // If there is an area light in the scene + { + // Create a material for this light. + const std::string reference("bench_shared_area_light"); + + const int indexMaterial = static_cast(m_materialsGUI.size()); + + MaterialGUI materialGUI; + + materialGUI.name = reference; + materialGUI.nameTextureAlbedo = std::string(); + materialGUI.nameTextureCutout = std::string(); + materialGUI.indexBSDF = INDEX_BRDF_SPECULAR; + materialGUI.albedo = make_float3(0.0f); // Black + materialGUI.absorptionColor = make_float3(1.0f); // White means no absorption. + materialGUI.absorptionScale = 0.0f; // 0.0f means no absoption. + materialGUI.roughness = make_float2(0.1f); + materialGUI.ior = 1.5f; + materialGUI.thinwalled = true; + + m_materialsGUI.push_back(materialGUI); // at indexMaterial. + + m_mapMaterialReferences[reference] = indexMaterial; + + // Create the Triangles for this parallelogram light. + //m_mapGeometries[reference] = m_idGeometry; + + std::shared_ptr geometry(new sg::Triangles(m_idGeometry++)); + geometry->createParallelogram(light.position, light.vecU, light.vecV, light.normal); + + m_geometries.push_back(geometry); + + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + // instance->setTransform(trafo); // Instance default matrix is identity. + instance->setChild(geometry); + instance->setMaterial(indexMaterial); + instance->setLight(indexLight); + + m_scene->addChild(instance); + } +} + +void Application::guiEventHandler() +{ + const ImGuiIO& io = ImGui::GetIO(); + + if (ImGui::IsKeyPressed(' ', false)) // Key Space: Toggle the GUI window display. + { + m_isVisibleGUI = !m_isVisibleGUI; + } + if (ImGui::IsKeyPressed('S', false)) // Key S: Save the current system options to a file "system_bench_shared___.txt" + { + MY_VERIFY( saveSystemDescription() ); + } + if (ImGui::IsKeyPressed('P', false)) // Key P: Save the current output buffer with tonemapping into a *.png file. + { + MY_VERIFY( screenshot(true) ); + } + if (ImGui::IsKeyPressed('H', false)) // Key H: Save the current linear output buffer into a *.hdr file. + { + MY_VERIFY( screenshot(false) ); + } + + const ImVec2 mousePosition = ImGui::GetMousePos(); // Mouse coordinate window client rect. + const int x = int(mousePosition.x); + const int y = int(mousePosition.y); + + switch (m_guiState) + { + case GUI_STATE_NONE: + if (!io.WantCaptureMouse) // Only allow camera interactions to begin when interacting with the GUI. + { + if (ImGui::IsMouseDown(0)) // LMB down event? + { + m_camera.setBaseCoordinates(x, y); + m_guiState = GUI_STATE_ORBIT; + } + else if (ImGui::IsMouseDown(1)) // RMB down event? + { + m_camera.setBaseCoordinates(x, y); + m_guiState = GUI_STATE_DOLLY; + } + else if (ImGui::IsMouseDown(2)) // MMB down event? + { + m_camera.setBaseCoordinates(x, y); + m_guiState = GUI_STATE_PAN; + } + else if (io.MouseWheel != 0.0f) // Mouse wheel zoom. + { + m_camera.zoom(io.MouseWheel); + } + } + break; + + case GUI_STATE_ORBIT: + if (ImGui::IsMouseReleased(0)) // LMB released? End of orbit mode. + { + m_guiState = GUI_STATE_NONE; + } + else + { + m_camera.orbit(x, y); + } + break; + + case GUI_STATE_DOLLY: + if (ImGui::IsMouseReleased(1)) // RMB released? End of dolly mode. + { + m_guiState = GUI_STATE_NONE; + } + else + { + m_camera.dolly(x, y); + } + break; + + case GUI_STATE_PAN: + if (ImGui::IsMouseReleased(2)) // MMB released? End of pan mode. + { + m_guiState = GUI_STATE_NONE; + } + else + { + m_camera.pan(x, y); + } + break; + } +} + +void Application::guiWindow() +{ + if (!m_isVisibleGUI || m_mode == 1) // Use SPACE to toggle the display of the GUI window. + { + return; + } + + bool refresh = false; + + ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiSetCond_FirstUseEver); + + ImGuiWindowFlags window_flags = 0; + if (!ImGui::Begin("bench_shared", nullptr, window_flags)) // No bool flag to omit the close button. + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + ImGui::PushItemWidth(-120); // Right-aligned, keep pixels for the labels. + + if (ImGui::CollapsingHeader("System")) + { + if (ImGui::DragFloat("Mouse Ratio", &m_mouseSpeedRatio, 0.1f, 0.1f, 1000.0f, "%.1f")) + { + m_camera.setSpeedRatio(m_mouseSpeedRatio); + } + if (ImGui::Checkbox("Present", &m_present)) + { + // No action needed, happens automatically. + } + if (ImGui::Combo("Camera", (int*) &m_lensShader, "Pinhole\0Fisheye\0Spherical\0\0")) + { + m_state.lensShader = m_lensShader; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::InputInt2("Resolution", &m_resolution.x, ImGuiInputTextFlags_EnterReturnsTrue)) // This requires RETURN to apply a new value. + { + m_resolution.x = std::max(1, m_resolution.x); + m_resolution.y = std::max(1, m_resolution.y); + + m_camera.setResolution(m_resolution.x, m_resolution.y); + m_rasterizer->setResolution(m_resolution.x, m_resolution.y); + m_state.resolution = m_resolution; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::InputInt("SamplesSqrt", &m_samplesSqrt, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue)) + { + m_samplesSqrt = clamp(m_samplesSqrt, 1, 256); // Samples per pixel are squares in the range [1, 65536]. + m_state.samplesSqrt = m_samplesSqrt; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragInt2("Path Lengths", &m_pathLengths.x, 1.0f, 0, 100)) + { + m_state.pathLengths = m_pathLengths; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragFloat("Scene Epsilon", &m_epsilonFactor, 1.0f, 0.0f, 10000.0f)) + { + m_state.epsilonFactor = m_epsilonFactor; + m_raytracer->updateState(m_state); + refresh = true; + } + if (ImGui::DragFloat("Env Rotation", &m_environmentRotation, 0.001f, 0.0f, 1.0f)) + { + m_state.envRotation = m_environmentRotation; + m_raytracer->updateState(m_state); + refresh = true; + } +#if USE_TIME_VIEW + if (ImGui::DragFloat("Clock Factor", &m_clockFactor, 1.0f, 0.0f, 1000000.0f, "%.0f")) + { + m_state.clockFactor = m_clockFactor; + m_raytracer->updateState(m_state); + refresh = true; + } +#endif + } + +#if !USE_TIME_VIEW + if (ImGui::CollapsingHeader("Tonemapper")) + { + bool changed = false; + if (ImGui::ColorEdit3("Balance", (float*) &m_tonemapperGUI.colorBalance)) + { + changed = true; + } + if (ImGui::DragFloat("Gamma", &m_tonemapperGUI.gamma, 0.01f, 0.01f, 10.0f)) // Must not get 0.0f + { + changed = true; + } + if (ImGui::DragFloat("White Point", &m_tonemapperGUI.whitePoint, 0.01f, 0.01f, 255.0f, "%.2f", 2.0f)) // Must not get 0.0f + { + changed = true; + } + if (ImGui::DragFloat("Burn Lights", &m_tonemapperGUI.burnHighlights, 0.01f, 0.0f, 10.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("Crush Blacks", &m_tonemapperGUI.crushBlacks, 0.01f, 0.0f, 1.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("Saturation", &m_tonemapperGUI.saturation, 0.01f, 0.0f, 10.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("Brightness", &m_tonemapperGUI.brightness, 0.01f, 0.0f, 100.0f, "%.2f", 2.0f)) + { + changed = true; + } + if (changed) + { + m_rasterizer->setTonemapper(m_tonemapperGUI); // This doesn't need a refresh. + } + } +#endif // !USE_TIME_VIEW + if (ImGui::CollapsingHeader("Materials")) + { + for (int i = 0; i < static_cast(m_materialsGUI.size()); ++i) + { + bool changed = false; + + MaterialGUI& materialGUI = m_materialsGUI[i]; + + if (ImGui::TreeNode((void*)(intptr_t) i, "%s", m_materialsGUI[i].name.c_str())) + { + if (ImGui::Combo("BxDF Type", (int*) &materialGUI.indexBSDF, "BRDF Diffuse\0BRDF Specular\0BSDF Specular\0BRDF GGX Smith\0BSDF GGX Smith\0\0")) + { + changed = true; + } + if (ImGui::ColorEdit3("Albedo", (float*) &materialGUI.albedo)) + { + changed = true; + } + if (ImGui::Checkbox("Thin-Walled", &materialGUI.thinwalled)) // Set this to true when using cutout opacity! + { + changed = true; + } + // Only show material parameters for the BxDFs which are affected by IOR and volume absorption. + if (materialGUI.indexBSDF == INDEX_BSDF_SPECULAR || + materialGUI.indexBSDF == INDEX_BSDF_GGX_SMITH) + { + if (ImGui::ColorEdit3("Absorption", (float*) &materialGUI.absorptionColor)) + { + changed = true; + } + if (ImGui::DragFloat("Absorption Scale", &materialGUI.absorptionScale, 0.01f, 0.0f, 1000.0f, "%.2f")) + { + changed = true; + } + if (ImGui::DragFloat("IOR", &materialGUI.ior, 0.01f, 0.0f, 10.0f, "%.2f")) + { + changed = true; + } + } + // Only show material parameters for the BxDFs which are affected by roughness. + if (materialGUI.indexBSDF == INDEX_BRDF_GGX_SMITH || + materialGUI.indexBSDF == INDEX_BSDF_GGX_SMITH) + { + if (ImGui::DragFloat2("Roughness", reinterpret_cast(&materialGUI.roughness), 0.001f, 0.0f, 1.0f, "%.3f")) + { + // Clamp the microfacet roughness to working values minimum values. + // FIXME When both roughness values fall below that threshold, use a specular BXDF instead. + if (materialGUI.roughness.x < MICROFACET_MIN_ROUGHNESS) + { + materialGUI.roughness.x = MICROFACET_MIN_ROUGHNESS; + } + if (materialGUI.roughness.y < MICROFACET_MIN_ROUGHNESS) + { + materialGUI.roughness.y = MICROFACET_MIN_ROUGHNESS; + } + changed = true; + } + } + + if (changed) + { + m_raytracer->updateMaterial(i, materialGUI); + refresh = true; + } + ImGui::TreePop(); + } + } + } + if (ImGui::CollapsingHeader("Lights")) + { + for (int i = 0; i < static_cast(m_lights.size()); ++i) + { + LightDefinition& light = m_lights[i]; + + // Allow to change the emission (radiant exitance in Watt/m^2 of the rectangle lights in the scene. + if (light.type == LIGHT_PARALLELOGRAM) + { + if (ImGui::TreeNode((void*)(intptr_t) i, "Light %d", i)) + { + if (ImGui::DragFloat3("Emission", (float*) &light.emission, 0.1f, 0.0f, 10000.0f, "%.1f")) + { + m_raytracer->updateLight(i, light); + refresh = true; + } + ImGui::TreePop(); + } + } + } + } + + ImGui::PopItemWidth(); + + ImGui::End(); + + if (refresh) + { + restartRendering(); + } +} + +void Application::guiRenderingIndicator(const bool isRendering) +{ + // NVIDIA Green when rendering is complete. + float r = 0.462745f; + float g = 0.72549f; + float b = 0.0f; + + if (isRendering) + { + // Neutral grey while rendering. + r = 1.0f; + g = 1.0f; + b = 1.0f; + } + + ImGuiStyle& style = ImGui::GetStyle(); + + // Use the GUI window title bar color as rendering indicator. Green when rendering is completed. + style.Colors[ImGuiCol_TitleBg] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); +} + + +bool Application::loadSystemDescription(const std::string& filename) +{ + Parser parser; + + if (!parser.load(filename)) + { + std::cerr << "ERROR: loadSystemDescription() failed in loadString(" << filename << ")\n"; + return false; + } + + ParserTokenType tokenType; + std::string token; + + while ((tokenType = parser.getNextToken(token)) != PTT_EOF) + { + if (tokenType == PTT_UNKNOWN) + { + std::cerr << "ERROR: loadSystemDescription() " << filename << " (" << parser.getLine() << "): Unknown token type.\n"; + MY_ASSERT(!"Unknown token type."); + return false; + } + + if (tokenType == PTT_ID) + { + if (token == "strategy") + { + // Ignored in this renderer. Behaves like RS_INTERACTIVE_MULTI_GPU_LOCAL_COPY. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const int strategy = atoi(token.c_str()); + + std::cerr << "WARNING: loadSystemDescription() renderer strategy " << strategy << " ignored.\n"; + } + else if (token == "devicesMask") // DAR FIXME Kept the old name to be able to mix and match apps. + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_maskDevices = atoi(token.c_str()); + } + else if (token == "arenaSize") // In mega-bytes. + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_sizeArena = std::max(1, atoi(token.c_str())); + } + else if (token == "interop") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_interop = atoi(token.c_str()); + if (m_interop < 0 || 2 < m_interop) + { + std::cerr << "WARNING: loadSystemDescription() Invalid interop value " << m_interop << ", using interop 0 (host).\n"; + m_interop = 0; + } + } + else if (token == "peerToPeer") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_peerToPeer = atoi(token.c_str()); + } + else if (token == "present") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_present = (atoi(token.c_str()) != 0); + } + else if (token == "resolution") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_resolution.x = std::max(1, atoi(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_resolution.y = std::max(1, atoi(token.c_str())); + } + else if (token == "tileSize") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tileSize.x = std::max(1, atoi(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tileSize.y = std::max(1, atoi(token.c_str())); + + // Make sure the values are power-of-two. + if (m_tileSize.x & (m_tileSize.x - 1)) + { + std::cerr << "ERROR: loadSystemDescription() tileSize.x = " << m_tileSize.x << " is not power-of-two, using 8.\n"; + m_tileSize.x = 8; + } + if (m_tileSize.y & (m_tileSize.y - 1)) + { + std::cerr << "ERROR: loadSystemDescription() tileSize.y = " << m_tileSize.y << " is not power-of-two, using 8.\n"; + m_tileSize.y = 8; + } + } + else if (token == "samplesSqrt") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_samplesSqrt = std::max(1, atoi(token.c_str())); // spp = m_samplesSqrt * m_samplesSqrt + } + else if (token == "miss") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_miss = atoi(token.c_str()); + } + else if (token == "envMap") + { + tokenType = parser.getNextToken(token); // Needs to be a filename in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + m_environment = token; + } + else if (token == "envRotation") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_environmentRotation = (float) atof(token.c_str()); + } + else if (token == "clockFactor") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_clockFactor = (float) atof(token.c_str()); + } + else if (token == "light") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_light = atoi(token.c_str()); + if (m_light < 0) + { + m_light = 0; + } + else if (2 < m_light) + { + m_light = 2; + } + } + else if (token == "pathLengths") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_pathLengths.x = atoi(token.c_str()); // min path length before Russian Roulette kicks in + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_pathLengths.y = atoi(token.c_str()); // max path length + } + else if (token == "epsilonFactor") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_epsilonFactor = (float) atof(token.c_str()); + } + else if (token == "lensShader") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_lensShader = static_cast(atoi(token.c_str())); + if (m_lensShader < LENS_SHADER_PINHOLE || LENS_SHADER_SPHERE < m_lensShader) + { + m_lensShader = LENS_SHADER_PINHOLE; + } + } + else if (token == "center") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float z = (float) atof(token.c_str()); + m_camera.m_center = make_float3(x, y, z); + m_camera.markDirty(); + } + else if (token == "camera") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_phi = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_theta = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_fov = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_distance = (float) atof(token.c_str()); + m_camera.markDirty(); + } + else if (token == "prefixScreenshot") + { + tokenType = parser.getNextToken(token); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + m_prefixScreenshot = token; + } + else if (token == "gamma") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.gamma = (float) atof(token.c_str()); + } + else if (token == "colorBalance") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[2] = (float) atof(token.c_str()); + } + else if (token == "whitePoint") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.whitePoint = (float) atof(token.c_str()); + } + else if (token == "burnHighlights") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.burnHighlights = (float) atof(token.c_str()); + } + else if (token == "crushBlacks") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.crushBlacks = (float) atof(token.c_str()); + } + else if (token == "saturation") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.saturation = (float) atof(token.c_str()); + } + else if (token == "brightness") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.brightness = (float) atof(token.c_str()); + } + else + { + std::cerr << "WARNING: loadSystemDescription() Unknown system option name: " << token << '\n'; + } + } + } + return true; +} + + +bool Application::saveSystemDescription() +{ + std::ostringstream description; + + description << "strategy " << m_strategy << '\n'; // Ignored in this renderer. + description << "devicesMask " << m_maskDevices << '\n'; + description << "arenaSize " << m_sizeArena << '\n'; + description << "interop " << m_interop << '\n'; + description << "present " << ((m_present) ? "1" : "0") << '\n'; + description << "resolution " << m_resolution.x << " " << m_resolution.y << '\n'; + description << "tileSize " << m_tileSize.x << " " << m_tileSize.y << '\n'; + description << "samplesSqrt " << m_samplesSqrt << '\n'; + description << "miss " << m_miss << '\n'; + if (!m_environment.empty()) + { + description << "envMap \"" << m_environment << "\"\n"; + } + description << "envRotation " << m_environmentRotation << '\n'; + description << "clockFactor " << m_clockFactor << '\n'; + description << "light " << m_light << '\n'; + description << "pathLengths " << m_pathLengths.x << " " << m_pathLengths.y << '\n'; + description << "epsilonFactor " << m_epsilonFactor << '\n'; + description << "lensShader " << m_lensShader << '\n'; + description << "center " << m_camera.m_center.x << " " << m_camera.m_center.y << " " << m_camera.m_center.z << '\n'; + description << "camera " << m_camera.m_phi << " " << m_camera.m_theta << " " << m_camera.m_fov << " " << m_camera.m_distance << '\n'; + if (!m_prefixScreenshot.empty()) + { + description << "prefixScreenshot \"" << m_prefixScreenshot << "\"\n"; + } + description << "gamma " << m_tonemapperGUI.gamma << '\n'; + description << "colorBalance " << m_tonemapperGUI.colorBalance[0] << " " << m_tonemapperGUI.colorBalance[1] << " " << m_tonemapperGUI.colorBalance[2] << '\n'; + description << "whitePoint " << m_tonemapperGUI.whitePoint << '\n'; + description << "burnHighlights " << m_tonemapperGUI.burnHighlights << '\n'; + description << "crushBlacks " << m_tonemapperGUI.crushBlacks << '\n'; + description << "saturation " << m_tonemapperGUI.saturation << '\n'; + description << "brightness " << m_tonemapperGUI.brightness << '\n'; + + const std::string filename = std::string("system_bench_shared_") + getDateTime() + std::string(".txt"); + const bool success = saveString(filename, description.str()); + if (success) + { + std::cout << filename << '\n'; // Print out the filename to indicate success. + } + return success; +} + +void Application::appendInstance(std::shared_ptr& group, + std::shared_ptr geometry, + const dp::math::Mat44f& matrix, + const std::string& reference, + unsigned int& idInstance) +{ + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + const float trafo[12] = + { + matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], + matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1], + matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2] + }; + + MY_ASSERT(matrix[0][3] == 0.0f && + matrix[1][3] == 0.0f && + matrix[2][3] == 0.0f && + matrix[3][3] == 1.0f); + + std::shared_ptr instance(new sg::Instance(idInstance++)); + instance->setTransform(trafo); + instance->setChild(geometry); + + int indexMaterial = -1; + std::map::const_iterator itm = m_mapMaterialReferences.find(reference); + if (itm != m_mapMaterialReferences.end()) + { + indexMaterial = itm->second; + } + else + { + std::cerr << "WARNING: appendInstance() No material found for " << reference << ". Trying default.\n"; + + std::map::const_iterator itmd = m_mapMaterialReferences.find(std::string("default")); + if (itmd != m_mapMaterialReferences.end()) + { + indexMaterial = itmd->second; + } + else + { + std::cerr << "ERROR: appendInstance() No default material found\n"; + } + } + + instance->setMaterial(indexMaterial); + + group->addChild(instance); +} + +bool Application::loadSceneDescription(const std::string& filename) +{ + Parser parser; + + if (!parser.load(filename)) + { + std::cerr << "ERROR: loadSceneDescription() failed in loadString(" << filename << ")\n"; + return false; + } + + ParserTokenType tokenType; + std::string token; + + // Reusing some math routines from the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + // Note that matrices in the nvpro-pipeline are defined row-major but are multiplied from the right, + // which means the order of transformations is simply from left to right matrix, means first matrix is applied first, + // but puts the translation into the last row elements (12 to 14). + + std::stack stackMatrix; + std::stack stackInverse; + std::stack stackOrientation; + + // Initialize all current transformations with identity. + dp::math::Mat44f curMatrix(dp::math::cIdentity44f); // object to world + dp::math::Mat44f curInverse(dp::math::cIdentity44f); // world to object + dp::math::Quatf curOrientation(0.0f, 0.0f, 0.0f, 1.0f); // object to world + + // Material parameters. + float3 curAlbedo = make_float3(1.0f); + float2 curRoughness = make_float2(0.1f); + float3 curAbsorptionColor = make_float3(1.0f); + float curAbsorptionScale = 0.0f; // 0.0f means off. + float curIOR = 1.5f; + bool curThinwalled = false; + std::string curAlbedoTexture; + std::string curCutoutTexture; + + // FIXME Add a mechanism to specify albedo textures per material and make that resetable or add a push/pop mechanism for materials. + // E.g. special case filename "none" which translates to empty filename, which switches off albedo textures. + // Get rid of the single hardcoded texture and the toggle. + + while ((tokenType = parser.getNextToken(token)) != PTT_EOF) + { + if (tokenType == PTT_UNKNOWN) + { + std::cerr << "ERROR: loadSceneDescription() " << filename << " (" << parser.getLine() << "): Unknown token type.\n"; + MY_ASSERT(!"Unknown token type."); + return false; + } + + if (tokenType == PTT_ID) + { + std::map::const_iterator itKeyword = m_mapKeywordScene.find(token); + if (itKeyword == m_mapKeywordScene.end()) + { + std::cerr << "WARNING: loadSceneDescription() Unknown token " << token << " ignored.\n"; + // MY_ASSERT(!"loadSceneDescription() Unknown token ignored."); + continue; // Just keep getting the next token until a known keyword is found. + } + + const KeywordScene keyword = itKeyword->second; + + switch (keyword) + { + case KS_ALBEDO: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAlbedo.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAlbedo.y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAlbedo.z = (float) atof(token.c_str()); + break; + + case KS_ALBEDO_TEXTURE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + curAlbedoTexture = token; + break; + + case KS_CUTOUT_TEXTURE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + curCutoutTexture = token; + break; + + case KS_ROUGHNESS: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curRoughness.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curRoughness.y = (float) atof(token.c_str()); + break; + + case KS_ABSORPTION: // For convenience this is an absoption color used to calculate the absorption coefficient. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionColor.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionColor.y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionColor.z = (float) atof(token.c_str()); + break; + + case KS_ABSORPTION_SCALE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionScale = (float) atof(token.c_str()); + break; + + case KS_IOR: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curIOR = (float) atof(token.c_str()); + break; + + case KS_THINWALLED: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curThinwalled = (atoi(token.c_str()) != 0); + break; + + case KS_MATERIAL: + { + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); // Internal material name. If there are duplicates the last name wins. + //MY_ASSERT(tokenType == PTT_ID); // Allow any type of identifier, including strings and numbers. + + std::string nameMaterial; + tokenType = parser.getNextToken(nameMaterial); // The actual material name. + //MY_ASSERT(tokenType == PTT_ID); // Allow any type of identifier, including string and numbers. + + // Create this material in the GUI. + const int indexMaterial = static_cast(m_materialsGUI.size()); + + MaterialGUI materialGUI; + + materialGUI.name = nameMaterialReference; + + materialGUI.indexBSDF = INDEX_BRDF_DIFFUSE; // Set a default BSDF. // Base direct callable index for the BXDFs. + // Handle all cases to get the correct error. + // DAR FIXME Put these into a std::map and do a fined here. + if (nameMaterial == std::string("brdf_diffuse")) + { + materialGUI.indexBSDF = INDEX_BRDF_DIFFUSE; + } + else if (nameMaterial == std::string("brdf_specular")) + { + materialGUI.indexBSDF = INDEX_BRDF_SPECULAR; + } + else if (nameMaterial == std::string("bsdf_specular")) + { + materialGUI.indexBSDF = INDEX_BSDF_SPECULAR; + } + else if (nameMaterial == std::string("brdf_ggx_smith")) + { + materialGUI.indexBSDF = INDEX_BRDF_GGX_SMITH; + } + else if (nameMaterial == std::string("bsdf_ggx_smith")) + { + materialGUI.indexBSDF = INDEX_BSDF_GGX_SMITH; + } + else + { + std::cerr << "WARNING: loadSceneDescription() unknown material " << nameMaterial << '\n'; + } + + materialGUI.nameTextureAlbedo = curAlbedoTexture; + materialGUI.nameTextureCutout = curCutoutTexture; + materialGUI.albedo = curAlbedo; + materialGUI.absorptionColor = curAbsorptionColor; + materialGUI.absorptionScale = curAbsorptionScale; + materialGUI.roughness = curRoughness; + materialGUI.ior = curIOR; + materialGUI.thinwalled = curThinwalled; + + m_materialsGUI.push_back(materialGUI); // at indexMaterial. + + m_mapMaterialReferences[nameMaterialReference] = indexMaterial; + + // Cache the referenced pictures to load them only once. + if (!curAlbedoTexture.empty()) + { + std::map::const_iterator it = m_mapPictures.find(curAlbedoTexture); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + picture->load(curAlbedoTexture, IMAGE_FLAG_2D); + + m_mapPictures[curAlbedoTexture] = picture; + } + } + + if (!curCutoutTexture.empty()) + { + std::map::const_iterator it = m_mapPictures.find(curCutoutTexture); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + picture->load(curCutoutTexture, IMAGE_FLAG_2D); + + m_mapPictures[curCutoutTexture] = picture; + } + } + + // Special handling: Texture names are not persistent state, but single shot. + // Otherwise there would need to be a mechanism to reset the name inside the scene description. + // Think about push/pop for materials? + curAlbedoTexture.clear(); + curCutoutTexture.clear(); + } + break; + + case KS_IDENTITY: + curMatrix = dp::math::cIdentity44f; + curInverse = dp::math::cIdentity44f; + curOrientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); // identity orientation + break; + + case KS_PUSH: + stackMatrix.push(curMatrix); + stackInverse.push(curInverse); + stackOrientation.push(curOrientation); + break; + + case KS_POP: + if (!stackMatrix.empty()) + { + MY_ASSERT(!stackInverse.empty()); + MY_ASSERT(!stackOrientation.empty()); + curMatrix = stackMatrix.top(); + stackMatrix.pop(); + curInverse = stackInverse.top(); + stackInverse.pop(); + curOrientation = stackOrientation.top(); + stackOrientation.pop(); + } + else + { + std::cerr << "ERROR: loadSceneDescription() pop on empty stack. Resetting to identity.\n"; + curMatrix = dp::math::cIdentity44f; + curInverse = dp::math::cIdentity44f; + curOrientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); // identity orientation + } + break; + + case KS_ROTATE: + { + dp::math::Vec3f axis; + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[2] = (float) atof(token.c_str()); + axis.normalize(); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float angle = dp::math::degToRad((float) atof(token.c_str())); + + dp::math::Quatf rotation(axis, angle); + curOrientation *= rotation; + + dp::math::Mat44f matrix(rotation, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); // Zero translation to get a Mat44f back. + curMatrix *= matrix; // DEBUG No need for the local matrix variable. + + // Inverse. Opposite order of matrix multiplications to make M * M^-1 = I. + dp::math::Quatf rotationInv(axis, -angle); + dp::math::Mat44f matrixInv(rotationInv, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); // Zero translation to get a Mat44f back. + curInverse = matrixInv * curInverse; // DEBUG No need for the local matrixInv variable. + } + break; + + case KS_SCALE: + { + dp::math::Mat44f scaling(dp::math::cIdentity44f); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[0][0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[1][1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[2][2] = (float) atof(token.c_str()); + + curMatrix *= scaling; + + // Inverse. // DEBUG Requires scalings to not contain zeros. + scaling[0][0] = 1.0f / scaling[0][0]; + scaling[1][1] = 1.0f / scaling[1][1]; + scaling[2][2] = 1.0f / scaling[2][2]; + + curInverse = scaling * curInverse; + } + break; + + case KS_TRANSLATE: + { + dp::math::Mat44f translation(dp::math::cIdentity44f); + + // Translation is in the third row in dp::math::Mat44f. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][2] = (float) atof(token.c_str()); + + curMatrix *= translation; + + translation[3][0] = -translation[3][0]; + translation[3][1] = -translation[3][1]; + translation[3][2] = -translation[3][2]; + + curInverse = translation * curInverse; + } + break; + + case KS_MODEL: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_ID); + + if (token == "plane") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int upAxis = atoi(token.c_str()); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "plane_" << tessU << "_" << tessV << "_" << upAxis; + + std::shared_ptr geometry; + + // DAR HACK DEBUG Disable model instancing + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry.str()] = m_idGeometry; // PERF Equal to static_cast(m_geometries.size()); + + geometry = std::make_shared(m_idGeometry++); + geometry->createPlane(tessU, tessV, upAxis); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "box") + { + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + // FIXME Implement tessellation. Must be a single value to get even distributions across edges. + std::string keyGeometry("box_1_1"); + + std::shared_ptr geometry; + + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createBox(); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "sphere") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + // Theta is in the range [0.0f, 1.0f] and 1.0f means closed sphere, smaller values open the noth pole. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float theta = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "sphere_" << tessU << "_" << tessV << "_" << theta; + + std::shared_ptr geometry; + + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createSphere(tessU, tessV, 1.0f, theta * M_PIf); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "torus") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float innerRadius = float(atof(token.c_str())); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float outerRadius = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "torus_" << tessU << "_" << tessV << "_" << innerRadius << "_" << outerRadius; + + std::shared_ptr geometry; + + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createTorus(tessU, tessV, innerRadius, outerRadius); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "assimp") + { + std::string filenameModel; + tokenType = parser.getNextToken(filenameModel); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(filenameModel); + + std::shared_ptr model = createASSIMP(filenameModel); + + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + const float trafo[12] = + { + curMatrix[0][0], curMatrix[1][0], curMatrix[2][0], curMatrix[3][0], + curMatrix[0][1], curMatrix[1][1], curMatrix[2][1], curMatrix[3][1], + curMatrix[0][2], curMatrix[1][2], curMatrix[2][2], curMatrix[3][2] + }; + + MY_ASSERT(curMatrix[0][3] == 0.0f && + curMatrix[1][3] == 0.0f && + curMatrix[2][3] == 0.0f && + curMatrix[3][3] == 1.0f); + + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + instance->setTransform(trafo); + instance->setChild(model); + + m_scene->addChild(instance); + } + break; + + default: + std::cerr << "ERROR: loadSceneDescription() Unexpected KeywordScene value " << keyword << " ignored.\n"; + MY_ASSERT(!"ERROR: loadSceneDescription() Unexpected KeywordScene value"); + break; + } + } + } + + std::cout << "loadSceneDescription() m_idGroup = " << m_idGroup << ", m_idInstance = " << m_idInstance << ", m_idGeometry = " << m_idGeometry << '\n'; + + return true; +} + +bool Application::loadString(const std::string& filename, std::string& text) +{ + std::ifstream inputStream(filename); + + if (!inputStream) + { + std::cerr << "ERROR: loadString() Failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: loadString() Failed to read file " << filename << '\n'; + return false; + } + + text = data.str(); + return true; +} + +bool Application::saveString(const std::string& filename, const std::string& text) +{ + std::ofstream outputStream(filename); + + if (!outputStream) + { + std::cerr << "ERROR: saveString() Failed to open file " << filename << '\n'; + return false; + } + + outputStream << text; + + if (outputStream.fail()) + { + std::cerr << "ERROR: saveString() Failed to write file " << filename << '\n'; + return false; + } + + return true; +} + +std::string Application::getDateTime() +{ +#if defined(_WIN32) + SYSTEMTIME time; + GetLocalTime(&time); +#elif defined(__linux__) + time_t rawtime; + struct tm* ts; + time(&rawtime); + ts = localtime(&rawtime); +#else + #error "OS not supported." +#endif + + std::ostringstream oss; + +#if defined( _WIN32 ) + oss << time.wYear; + if (time.wMonth < 10) + { + oss << '0'; + } + oss << time.wMonth; + if (time.wDay < 10) + { + oss << '0'; + } + oss << time.wDay << '_'; + if (time.wHour < 10) + { + oss << '0'; + } + oss << time.wHour; + if (time.wMinute < 10) + { + oss << '0'; + } + oss << time.wMinute; + if (time.wSecond < 10) + { + oss << '0'; + } + oss << time.wSecond << '_'; + if (time.wMilliseconds < 100) + { + oss << '0'; + } + if (time.wMilliseconds < 10) + { + oss << '0'; + } + oss << time.wMilliseconds; +#elif defined(__linux__) + oss << ts->tm_year; + if (ts->tm_mon < 10) + { + oss << '0'; + } + oss << ts->tm_mon; + if (ts->tm_mday < 10) + { + oss << '0'; + } + oss << ts->tm_mday << '_'; + if (ts->tm_hour < 10) + { + oss << '0'; + } + oss << ts->tm_hour; + if (ts->tm_min < 10) + { + oss << '0'; + } + oss << ts->tm_min; + if (ts->tm_sec < 10) + { + oss << '0'; + } + oss << ts->tm_sec << '_'; + oss << "000"; // No milliseconds available. +#else + #error "OS not supported." +#endif + + return oss.str(); +} + +static void updateAABB(float3& minimum, float3& maximum, const float3& v) +{ + if (v.x < minimum.x) + { + minimum.x = v.x; + } + else if (maximum.x < v.x) + { + maximum.x = v.x; + } + + if (v.y < minimum.y) + { + minimum.y = v.y; + } + else if (maximum.y < v.y) + { + maximum.y = v.y; + } + + if (v.z < minimum.z) + { + minimum.z = v.z; + } + else if (maximum.z < v.z) + { + maximum.z = v.z; + } +} + +//static void calculateTexcoordsSpherical(std::vector& attributes, const std::vector& indices) +//{ +// dp::math::Vec3f center(0.0f, 0.0f, 0.0f); +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// center += attributes[i].vertex; +// } +// center /= (float) attributes.size(); +// +// float u; +// float v; +// +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// dp::math::Vec3f p = attributes[i].vertex - center; +// if (FLT_EPSILON < fabsf(p[1])) +// { +// u = 0.5f * atan2f(p[0], -p[1]) / dp::math::PI + 0.5f; +// } +// else +// { +// u = (0.0f <= p[0]) ? 0.75f : 0.25f; +// } +// float d = sqrtf(dp::math::square(p[0]) + dp::math::square(p[1])); +// if (FLT_EPSILON < d) +// { +// v = atan2f(p[2], d) / dp::math::PI + 0.5f; +// } +// else +// { +// v = (0.0f <= p[2]) ? 1.0f : 0.0f; +// } +// attributes[i].texcoord0 = dp::math::Vec3f(u, v, 0.0f); +// } +// +// //// The code from the environment texture lookup. +// //for (size_t i = 0; i < attributes.size(); ++i) +// //{ +// // dp::math::Vec3f R = attributes[i].vertex - center; +// // dp::math::normalize(R); +// +// // // The seam u == 0.0 == 1.0 is in positive z-axis direction. +// // // Compensate for the environment rotation done inside the direct lighting. +// // const float u = (atan2f(R[0], -R[2]) + dp::math::PI) * 0.5f / dp::math::PI; +// // const float theta = acosf(-R[1]); // theta == 0.0f is south pole, theta == M_PIf is north pole. +// // const float v = theta / dp::math::PI; // Texture is with origin at lower left, v == 0.0f is south pole. +// +// // attributes[i].texcoord0 = dp::math::Vecf(u, v, 0.0f); +// //} +//} + + +// Calculate texture tangents based on the texture coordinate gradients. +// Doesn't work when all texture coordinates are identical! Thats the reason for the other routine below. +//static void calculateTangents(std::vector& attributes, const std::vector& indices) +//{ +// for (size_t i = 0; i < indices.size(); i += 4) +// { +// unsigned int i0 = indices[i ]; +// unsigned int i1 = indices[i + 1]; +// unsigned int i2 = indices[i + 2]; +// +// dp::math::Vec3f e0 = attributes[i1].vertex - attributes[i0].vertex; +// dp::math::Vec3f e1 = attributes[i2].vertex - attributes[i0].vertex; +// dp::math::Vec2f d0 = dp::math::Vec2f(attributes[i1].texcoord0) - dp::math::Vec2f(attributes[i0].texcoord0); +// dp::math::Vec2f d1 = dp::math::Vec2f(attributes[i2].texcoord0) - dp::math::Vec2f(attributes[i0].texcoord0); +// attributes[i0].tangent += d1[1] * e0 - d0[1] * e1; +// +// e0 = attributes[i2].vertex - attributes[i1].vertex; +// e1 = attributes[i0].vertex - attributes[i1].vertex; +// d0 = dp::math::Vec2f(attributes[i2].texcoord0) - dp::math::Vec2f(attributes[i1].texcoord0); +// d1 = dp::math::Vec2f(attributes[i0].texcoord0) - dp::math::Vec2f(attributes[i1].texcoord0); +// attributes[i1].tangent += d1[1] * e0 - d0[1] * e1; +// +// e0 = attributes[i0].vertex - attributes[i2].vertex; +// e1 = attributes[i1].vertex - attributes[i2].vertex; +// d0 = dp::math::Vec2f(attributes[i0].texcoord0) - dp::math::Vec2f(attributes[i2].texcoord0); +// d1 = dp::math::Vec2f(attributes[i1].texcoord0) - dp::math::Vec2f(attributes[i2].texcoord0); +// attributes[i2].tangent += d1[1] * e0 - d0[1] * e1; +// } +// +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// dp::math::Vec3f tangent(attributes[i].tangent); +// dp::math::normalize(tangent); // This normalizes the sums from above! +// +// dp::math::Vec3f normal(attributes[i].normal); +// +// dp::math::Vec3f bitangent = normal ^ tangent; +// dp::math::normalize(bitangent); +// +// tangent = bitangent ^ normal; +// dp::math::normalize(tangent); +// +// attributes[i].tangent = tangent; +// +//#if USE_BITANGENT +// attributes[i].bitangent = bitantent; +//#endif +// } +//} + +// Calculate (geometry) tangents with the global tangent direction aligned to the biggest AABB extend of this part. +void Application::calculateTangents(std::vector& attributes, const std::vector& indices) +{ + MY_ASSERT(3 <= indices.size()); + + // Initialize with the first vertex to be able to use else-if comparisons in updateAABB(). + float3 aabbLo = attributes[indices[0]].vertex; + float3 aabbHi = attributes[indices[0]].vertex; + + // Build an axis aligned bounding box. + for (size_t i = 0; i < indices.size(); i += 3) + { + unsigned int i0 = indices[i ]; + unsigned int i1 = indices[i + 1]; + unsigned int i2 = indices[i + 2]; + + updateAABB(aabbLo, aabbHi, attributes[i0].vertex); + updateAABB(aabbLo, aabbHi, attributes[i1].vertex); + updateAABB(aabbLo, aabbHi, attributes[i2].vertex); + } + + // Get the longest extend and use that as general tangent direction. + const float3 extents = aabbHi - aabbLo; + + float f = extents.x; + int maxComponent = 0; + + if (f < extents.y) + { + f = extents.y; + maxComponent = 1; + } + if (f < extents.z) + { + maxComponent = 2; + } + + float3 direction; + float3 bidirection; + + switch (maxComponent) + { + case 0: // x-axis + default: + direction = make_float3(1.0f, 0.0f, 0.0f); + bidirection = make_float3(0.0f, 1.0f, 0.0f); + break; + case 1: // y-axis // DEBUG It might make sense to keep these directions aligned to the global coordinate system. Use the same coordinates as for z-axis then. + direction = make_float3(0.0f, 1.0f, 0.0f); + bidirection = make_float3(0.0f, 0.0f, -1.0f); + break; + case 2: // z-axis + direction = make_float3(0.0f, 0.0f, -1.0f); + bidirection = make_float3(0.0f, 1.0f, 0.0f); + break; + } + + // Build an ortho-normal basis with the existing normal. + for (size_t i = 0; i < attributes.size(); ++i) + { + float3 tangent = direction; + float3 bitangent = bidirection; + // float3 normal = attributes[i].normal; + float3 normal; + normal.x = attributes[i].normal.x; + normal.y = attributes[i].normal.y; + normal.z = attributes[i].normal.z; + + if (0.001f < 1.0f - fabsf(dot(normal, tangent))) + { + bitangent = normalize(cross(normal, tangent)); + tangent = normalize(cross(bitangent, normal)); + } + else // Normal and tangent direction too collinear. + { + MY_ASSERT(0.001f < 1.0f - fabsf(dot(bitangent, normal))); + tangent = normalize(cross(bitangent, normal)); + //bitangent = normalize(cross(normal, tangent)); + } + attributes[i].tangent = tangent; + } +} + +bool Application::screenshot(const bool tonemap) +{ + ILboolean hasImage = false; + + const int spp = m_samplesSqrt * m_samplesSqrt; // Add the samples per pixel to the filename for quality comparisons. + + std::ostringstream path; + + path << m_prefixScreenshot << "_" << spp << "spp_" << getDateTime(); + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + + ilBindImage(imageID); + ilActiveImage(0); + ilActiveFace(0); + + ilDisable(IL_ORIGIN_SET); + + const float4* bufferHost = reinterpret_cast(m_raytracer->getOutputBufferHost()); + + if (tonemap) + { + // Store a tonemapped RGB8 *.png image + path << ".png"; + + if (ilTexImage(m_resolution.x, m_resolution.y, 1, 3, IL_RGB, IL_UNSIGNED_BYTE, nullptr)) + { + uchar3* dst = reinterpret_cast(ilGetData()); + + const float invGamma = 1.0f / m_tonemapperGUI.gamma; + const float3 colorBalance = make_float3(m_tonemapperGUI.colorBalance[0], m_tonemapperGUI.colorBalance[1], m_tonemapperGUI.colorBalance[2]); + const float invWhitePoint = m_tonemapperGUI.brightness / m_tonemapperGUI.whitePoint; + const float burnHighlights = m_tonemapperGUI.burnHighlights; + const float crushBlacks = m_tonemapperGUI.crushBlacks + m_tonemapperGUI.crushBlacks + 1.0f; + const float saturation = m_tonemapperGUI.saturation; + + for (int y = 0; y < m_resolution.y; ++y) + { + for (int x = 0; x < m_resolution.x; ++x) + { + const int idx = y * m_resolution.x + x; + + // Tonemapper. // PERF Add a native CUDA kernel doing this. + float3 hdrColor = make_float3(bufferHost[idx]); + float3 ldrColor = invWhitePoint * colorBalance * hdrColor; + ldrColor *= ((ldrColor * burnHighlights) + 1.0f) / (ldrColor + 1.0f); + + float luminance = dot(ldrColor, make_float3(0.3f, 0.59f, 0.11f)); + ldrColor = lerp(make_float3(luminance), ldrColor, saturation); // This can generate negative values for saturation > 1.0f! + ldrColor = fmaxf(make_float3(0.0f), ldrColor); // Prevent negative values. + + luminance = dot(ldrColor, make_float3(0.3f, 0.59f, 0.11f)); + if (luminance < 1.0f) + { + const float3 crushed = powf(ldrColor, crushBlacks); + ldrColor = lerp(crushed, ldrColor, sqrtf(luminance)); + ldrColor = fmaxf(make_float3(0.0f), ldrColor); // Prevent negative values. + } + ldrColor = clamp(powf(ldrColor, invGamma), 0.0f, 1.0f); // Saturate, clamp to range [0.0f, 1.0f]. + + dst[idx] = make_uchar3((unsigned char) (ldrColor.x * 255.0f), + (unsigned char) (ldrColor.y * 255.0f), + (unsigned char) (ldrColor.z * 255.0f)); + } + } + hasImage = true; + } + } + else + { + // Store the float4 linear output buffer as *.hdr image. + // FIXME Add a half float conversion and store as *.exr. (Pre-built DevIL 1.7.8 supports EXR, DevIL 1.8.0 doesn't!) + path << ".hdr"; + + hasImage = ilTexImage(m_resolution.x, m_resolution.y, 1, 4, IL_RGBA, IL_FLOAT, (void*) bufferHost); + } + + if (hasImage) + { + ilEnable(IL_FILE_OVERWRITE); // By default, always overwrite + + std::string filename = path.str(); + convertPath(filename); + + if (ilSaveImage((const ILstring) filename.c_str())) + { + ilDeleteImages(1, &imageID); + + std::cout << filename << '\n'; // Print out filename to indicate that a screenshot has been taken. + return true; + } + } + + // There was an error when reaching this code. + ILenum error = ilGetError(); // DEBUG + std::cerr << "ERROR: screenshot() failed with IL error " << error << '\n'; + + while (ilGetError() != IL_NO_ERROR) // Clean up errors. + { + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + + return false; +} + +// Convert between slashes and backslashes in paths depending on the operating system +void Application::convertPath(std::string& path) +{ +#if defined(_WIN32) + std::string::size_type pos = path.find("/", 0); + while (pos != std::string::npos) + { + path[pos] = '\\'; + pos = path.find("/", pos); + } +#elif defined(__linux__) + std::string::size_type pos = path.find("\\", 0); + while (pos != std::string::npos) + { + path[pos] = '/'; + pos = path.find("\\", pos); + } +#endif +} + +void Application::convertPath(char* path) +{ +#if defined(_WIN32) + for (size_t i = 0; i < strlen(path); ++i) + { + if (path[i] == '/') + { + path[i] = '\\'; + } + } +#elif defined(__linux__) + for (size_t i = 0; i < strlen(path); ++i) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +#endif +} + +// DAR HACK DEBUG Small helper class to generate a lot of different RGB8 PNG images to be used for texture sharing benchmarks. +bool Application::generatePNG(const unsigned int width, + const unsigned int height, + const unsigned char r, + const unsigned char g, + const unsigned char b) +{ + static unsigned int number = 0; + + std::ostringstream path; + + path << m_prefixScreenshot << "_" << number << ".png"; + + ++number; // Each call this function will generate a new runnign number. + + ILboolean hasImage = false; + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + + ilBindImage(imageID); + ilActiveImage(0); + ilActiveFace(0); + + ilDisable(IL_ORIGIN_SET); + + if (ilTexImage(width, height, 1, 3, IL_RGB, IL_UNSIGNED_BYTE, nullptr)) + { + uchar3* dst = reinterpret_cast(ilGetData()); + + for (unsigned int y = 0; y < height; ++y) + { + for (unsigned int x = 0; x < width; ++x) + { + const unsigned int idx = y * width + x; + + dst[idx] = make_uchar3(r, g, b); + } + } + hasImage = true; + } + + if (hasImage) + { + ilEnable(IL_FILE_OVERWRITE); // By default, always overwrite + + std::string filename = path.str(); + convertPath(filename); + + if (ilSaveImage((const ILstring) filename.c_str())) + { + ilDeleteImages(1, &imageID); + + std::cout << filename << '\n'; // Print out filename to indicate that a screenshot has been taken. + return true; + } + } + + // There was an error when reaching this code. + ILenum error = ilGetError(); // DEBUG + std::cerr << "ERROR: screenshot() failed with IL error " << error << '\n'; + + while (ilGetError() != IL_NO_ERROR) // Clean up errors. + { + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + + return false; +} diff --git a/apps/bench_shared/src/Arena.cpp b/apps/bench_shared/src/Arena.cpp new file mode 100644 index 00000000..199b8329 --- /dev/null +++ b/apps/bench_shared/src/Arena.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include + +#include "inc/Arena.h" + +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include +#include +#include + + +#if !defined(NDEBUG) +// Only used inside a MY_ASSERT(). Prevent unused function warning in debug targets. +static bool isPowerOfTwo(const size_t s) +{ + return (s != 0) && ((s & (s - 1)) == 0); +} +#endif + + +namespace cuda +{ + + Block::Block(cuda::Arena* arena, const CUdeviceptr addr, const size_t size, const CUdeviceptr ptr) + : m_arena(arena) + , m_addr(addr) + , m_size(size) + , m_ptr(ptr) + { + } + + bool Block::isValid() const + { + return (m_ptr != 0); + } + + bool Block::operator<(const cuda::Block& rhs) + { + return (m_addr < rhs.m_addr); + } + + + Arena::Arena(const size_t size) + : m_size((size + 255) & ~255) // Make sure the Arena has a multiple of 256 bytes size. + { + // This can fail with CUDA OOM errors and then none of the further allocations will work. + // Same as if there would be no arena. + CU_CHECK( cuMemAlloc(&m_addr, m_size) ); + + // Make sure the base address of the arena is 256-byte aligned. + MY_ASSERT((m_addr & 255) == 0); + + // When the Arena is created, there is one big free block of the full size. + // Free blocks do not have a user pointer assigned, only allocated blocks are valid. + m_blocksFree.push_back(cuda::Block(this, m_addr, m_size, 0)); + } + + Arena::~Arena() + { + // By design there is no need to clear this here. + // These are not pointers and there is no Block destructor. + // m_blocksFree.clear(); + + // Wipe the arena. + // All active blocks after this point are invalid and must not be in use anymore. + CU_CHECK_NO_THROW( cuMemFree(m_addr) ); + } + + bool Arena::allocBlock(cuda::Block& block, const size_t size, const size_t alignment, const cuda::Usage usage) + { + const size_t maskAligned = alignment - 1; + + for (std::list::iterator it = m_blocksFree.begin(); it != m_blocksFree.end(); ++it) + { + if (usage == cuda::USAGE_STATIC) // Static blocks are allocated at the front of free blocks. + { + const size_t offset = it->m_addr & maskAligned; // If the address of this free block is not on the required alignment + const size_t adjust = (offset) ? alignment - offset : 0; // the size needs to be adjusted by this many bytes. + + const size_t sizeAdjusted = size + adjust; // The resulting block needs to be this big to fit the requested size with the proper alignment. + + if (sizeAdjusted <= it->m_size) + { + // The new block address starts at the beginning of the free block. + // The adjusted address is the properly aligned pointer in that free block. + block = cuda::Block(this, it->m_addr, sizeAdjusted, it->m_addr + adjust); + MY_ASSERT((block.m_ptr & maskAligned) == 0); // DEBUG + + it->m_addr += sizeAdjusted; // Advance the free block pointer. + it->m_size -= sizeAdjusted; // Reduce the free block size. + + if (it->m_size == 0) // If the block was fully used, remove it from the list of free blocks. + { + m_blocksFree.erase(it); + } + + return true; + } + } + else // if (usage == cuda::USAGE_TEMP) // Temporary allocations are placed at the end of free blocks to reduce fragmentation inside the arena. + { + const CUdeviceptr addrEnd = it->m_addr + it->m_size; // The poiner behind the last byte of the free block. + CUdeviceptr addrTmp = addrEnd - size; // The pointer to the start of the allocated block if the alignment fits. + + const size_t adjust = addrTmp & maskAligned; // If the start address is not properly aligned, this is the amount of bytes we need to start earlier to get an aligned pointer. + + const size_t sizeAdjusted = size + adjust; // For performance, do not split the free block into size and adjust, although there will be adjust many bytes free at the end of the block. + + if (sizeAdjusted <= it->m_size) + { + addrTmp -= adjust; // The block address needs to be that many bytes ealier to be aligned. + MY_ASSERT(it->m_addr <= addrTmp); // DEBUG Cannot happen with the size check above. + + // The new block starts at the aligned address at the end of the free block. + block = cuda::Block(this, addrTmp, sizeAdjusted, addrTmp); + MY_ASSERT((block.m_ptr & maskAligned) == 0); // DEBUG Check the user pointer for proper alignment. + + it->m_size -= sizeAdjusted; // Reduce the free block size. The address stays the same because we allocated at the end. + + if (it->m_size == 0) // If the block was fully used, remove it from the list of free blocks. + { + MY_ASSERT(it->m_addr == addrTmp); // If we used the whole size, these two addresses must match. + m_blocksFree.erase(it); + } + + return true; + } + } + } + + return false; + } + + void Arena::freeBlock(const cuda::Block& block) + { + // Search for the list element which has the next higher m_addr than the block. + std::list::iterator itNext = m_blocksFree.begin(); + + while (itNext != m_blocksFree.end() && itNext->m_addr < block.m_addr) + { + ++itNext; + } + + // Insert block before itNext. Returned iterator "it" points to block. + std::list::iterator it = m_blocksFree.insert(itNext, block); + + // If itNext is not end(), then it points to a free block and "it" is directly before that. + if (itNext != m_blocksFree.end()) + { + if (it->m_addr + it->m_size == itNext->m_addr) // Check if the memory blocks are adjacent. + { + it->m_size += itNext->m_size; // Merge the two blocks to the first + m_blocksFree.erase(itNext); // and erase the second. + } + } + + // If "it" is not begin(), then there is an element before it which could be adjacent. + if (it != m_blocksFree.begin()) + { + itNext = it--; // Now "it" can be at least begin() and itNext is always the element directly after it. + if (it->m_addr + it->m_size == itNext->m_addr) + { + it->m_size += itNext->m_size; // Merge the two blocks to the first + m_blocksFree.erase(itNext); // and erase the second. + } + } + } + + + ArenaAllocator::ArenaAllocator(const size_t sizeArenaBytes) + : m_sizeArenaBytes(sizeArenaBytes) + , m_sizeMemoryAllocated(0) + { + } + + ArenaAllocator::~ArenaAllocator() + { + for (auto arena : m_arenas) + { + delete arena; + } + } + + CUdeviceptr ArenaAllocator::alloc(const size_t size, const size_t alignment, const cuda::Usage usage) + { + // All memory alignments needed in this implementation are at max 256 and power-of-two + // which means adjustments can be done with bitmasks instead of modulo operators. + MY_ASSERT(0 < size && alignment <= 256 && isPowerOfTwo(alignment)); + + // This allocator does not support allocating a pointer with zero bytes capacity. (cuMemAlloc() doesn't either.) + // That would break the uniqueness of the user pointer inside the m_blocksAllocated because the free block wouldn't advance its address. + // If really needed, override the zero size here. + if (size == 0) + { + return 0; + } + + cuda::Block block; + + // PERF Normally the biggest free block is inside the most recently created arena. + // Means if the search starts from the back of the vector, the chance to find a free block is much higher. + size_t i = m_arenas.size(); + while (0 < i--) + { + if (m_arenas[i]->allocBlock(block, size, alignment, usage)) + { + break; + } + } + + // If none of the existing Arenas had a sufficient contiguous memory block, create a new Arena which can hold the size. + if (!block.isValid()) + { + try + { + const size_t sizeArenaBytes = std::max(m_sizeArenaBytes, size); + + // Allocate a new arena which can hold the requested size of bytes. + // No user pointer adjustment is required if m_sizeArenaBytes <= size. This is always aligned to 256 bytes. + Arena* arena = new Arena(sizeArenaBytes); // This can fail with a CUDA out-of-memory error! + + m_arenas.push_back(arena); // Append it to the vector of arenas. + + (void) arena->allocBlock(block, size, alignment, usage); + MY_ASSERT(block.isValid()); // This allocation should not fail. + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + } + + if (block.isValid()) + { + // DEBUG Make sure the user pointer is unique. (It wasn't when using size == 0.) + //std::map::const_iterator it = m_blocksAllocated.find(block.m_ptr); + //if (it != m_blocksAllocated.end()) + //{ + // MY_ASSERT(false); + //} + + m_blocksAllocated[block.m_ptr] = block; // Track all successful allocations inside the ArenaAllocator with the user pointer as key. + + m_sizeMemoryAllocated += block.m_size; // Track the overall number of bytes allocated. + } + + return block.m_ptr; // This is 0 when the block is invalid which can only happen with a CUDA OOM error. + } + + void ArenaAllocator::free(const CUdeviceptr ptr) + { + // Allow free() to be called with nullptr. This actually happens on purpose. + if (ptr == 0) + { + return; + } + + std::map::const_iterator it = m_blocksAllocated.find(ptr); + if (it != m_blocksAllocated.end()) + { + const cuda::Block& block = it->second; + + MY_ASSERT(block.m_size <= m_sizeMemoryAllocated); + m_sizeMemoryAllocated -= block.m_size; // Track overall number of byts allocated. + + block.m_arena->freeBlock(block); // Merge this block to the list of free blocks in this arena. + + m_blocksAllocated.erase(it); // Remove it from the map of allocated blocks. + } + else + { + std::cerr << "ERROR: ArenaAllocator::free() failed to find the pointer " << ptr << "\n"; + } + } + + size_t ArenaAllocator::getSizeMemoryAllocated() const + { + return m_sizeMemoryAllocated; + } + +} // namespace cuda diff --git a/apps/bench_shared/src/Assimp.cpp b/apps/bench_shared/src/Assimp.cpp new file mode 100644 index 00000000..77abdacb --- /dev/null +++ b/apps/bench_shared/src/Assimp.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Application.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "inc/MyAssert.h" + + +std::shared_ptr Application::createASSIMP(const std::string& filename) +{ + std::map< std::string, std::shared_ptr >::const_iterator itGroup = m_mapGroups.find(filename); + if (itGroup != m_mapGroups.end()) + { + return itGroup->second; // Full model instancing under an Instance node. + } + + std::ifstream fin(filename); + if (!fin.fail()) + { + fin.close(); // Ok, file found. + } + else + { + std::cerr << "createASSIMP() could not open " << filename << '\n'; + + // Generate a Group node in any case. It will not have children when the file loading fails. + std::shared_ptr group(new sg::Group(m_idGroup++)); + m_mapGroups[filename] = group; // Allow instancing of this whole model (to fail again quicker next time). + return group; + } + + Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL; // or Assimp::Logger::VERBOSE; + + Assimp::DefaultLogger::create("", severity, aiDefaultLogStream_STDOUT); // Create a logger instance for Console Output + //Assimp::DefaultLogger::create("assimp_log.txt", severity, aiDefaultLogStream_FILE); // Create a logger instance for File Output (found in project folder or near .exe) + + Assimp::DefaultLogger::get()->info("Assimp::DefaultLogger initialized."); // Will add message with "info" tag. + // Assimp::DefaultLogger::get()->debug(""); // Will add message with "debug" tag. + + unsigned int postProcessSteps = 0 + //| aiProcess_CalcTangentSpace + //| aiProcess_JoinIdenticalVertices + //| aiProcess_MakeLeftHanded + | aiProcess_Triangulate + //| aiProcess_RemoveComponent + //| aiProcess_GenNormals + | aiProcess_GenSmoothNormals + //| aiProcess_SplitLargeMeshes + //| aiProcess_PreTransformVertices + //| aiProcess_LimitBoneWeights + //| aiProcess_ValidateDataStructure + //| aiProcess_ImproveCacheLocality + | aiProcess_RemoveRedundantMaterials + //| aiProcess_FixInfacingNormals + | aiProcess_SortByPType + //| aiProcess_FindDegenerates + //| aiProcess_FindInvalidData + //| aiProcess_GenUVCoords + //| aiProcess_TransformUVCoords + //| aiProcess_FindInstances + //| aiProcess_OptimizeMeshes + //| aiProcess_OptimizeGraph + //| aiProcess_FlipUVs + //| aiProcess_FlipWindingOrder + //| aiProcess_SplitByBoneCount + //| aiProcess_Debone + //| aiProcess_GlobalScale + //| aiProcess_EmbedTextures + //| aiProcess_ForceGenNormals + //| aiProcess_DropNormals + ; + + Assimp::Importer importer; + + if (m_optimize) + { + postProcessSteps |= aiProcess_FindDegenerates | aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; + + // Removing degenerate triangles. + // If you don't support lines and points, then + // specify the aiProcess_FindDegenerates flag, + // specify the aiProcess_SortByPType flag, + // set the AI_CONFIG_PP_SBP_REMOVE importer property to (aiPrimitiveType_POINT | aiPrimitiveType_LINE). + importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE); + // This step also removes very small triangles with a surface area smaller than 10^-6. + // If you rely on having these small triangles, or notice holes in your model, + // set the property AI_CONFIG_PP_FD_CHECKAREA to false. + importer.SetPropertyBool(AI_CONFIG_PP_FD_CHECKAREA, false); + // The degenerate triangles are put into point or line primitives which are then ignored when building the meshes. + // Finally the traverseScene() function filters out any instance node in the hierarchy which doesn't have a polygonal mesh assigned. + } + + const aiScene* scene = importer.ReadFile(filename, postProcessSteps); + + // If the import failed, report it + if (!scene) + { + Assimp::DefaultLogger::get()->info(importer.GetErrorString()); + Assimp::DefaultLogger::kill(); // Kill it after the work is done + + std::shared_ptr group(new sg::Group(m_idGroup++)); + m_mapGroups[filename] = group; // Allow instancing of this whole model (to fail again quicker next time). + return group; + } + + // Each scene needs to know where its geometries begin in the m_geometries to calculate the correct mesh index in traverseScene() + const unsigned int indexSceneBase = static_cast(m_geometries.size()); + + m_remappedMeshIndices.clear(); // Clear the local remapping vector from iMesh to m_geometries index. + + // Create all geometries in the assimp scene with triangle data. Ignore the others and remap their geometry indices. + for (unsigned int iMesh = 0; iMesh < scene->mNumMeshes; ++iMesh) + { + const aiMesh* mesh = scene->mMeshes[iMesh]; + + unsigned int remapMeshToGeometry = ~0u; // Remap mesh index to geometry index. ~0 means there was no geometry for a mesh. + + // The post-processor took care of meshes per primitive type and triangulation. + // Need to do a bitwise comparison of the mPrimitiveTypes here because newer ASSIMP versions + // indicate triangulated former polygons with the additional aiPrimitiveType_NGONEncodingFlag. + if ((mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) && 2 < mesh->mNumVertices) + { + std::vector attributes(mesh->mNumVertices); + + bool needsTangents = false; + bool needsNormals = false; + bool needsTexcoords = false; + + for (unsigned int iVertex = 0; iVertex < mesh->mNumVertices; ++iVertex) + { + TriangleAttributes& attrib = attributes[iVertex]; + + const aiVector3D& v = mesh->mVertices[iVertex]; + attrib.vertex = make_float3(v.x, v.y, v.z); + + if (mesh->HasTangentsAndBitangents()) + { + const aiVector3D& t = mesh->mTangents[iVertex]; + attrib.tangent = make_float3(t.x, t.y, t.z); + } + else + { + needsTangents = true; + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + } + + if (mesh->HasNormals()) + { + const aiVector3D& n = mesh->mNormals[iVertex]; + attrib.normal = make_float3(n.x, n.y, n.z); + } + else + { + needsNormals = true; + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + } + + if (mesh->HasTextureCoords(0)) + { + const aiVector3D& t = mesh->mTextureCoords[0][iVertex]; + attrib.texcoord = make_float3(t.x, t.y, t.z); + } + else + { + needsTexcoords = true; + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + } + } + + std::vector indices; + + for (unsigned int iFace = 0; iFace < mesh->mNumFaces; ++iFace) + { + const struct aiFace* face = &mesh->mFaces[iFace]; + MY_ASSERT(face->mNumIndices == 3); // Must be true because of aiProcess_Triangulate. + + for (unsigned int iIndex = 0; iIndex < face->mNumIndices; ++iIndex) + { + indices.push_back(face->mIndices[iIndex]); + } + } + + //if (needsNormals) // Assimp handled that via the aiProcess_GenSmoothNormals flag. + //{ + // calculateNormals(attributes, indices); + //} + if (needsTangents) + { + calculateTangents(attributes, indices); // This calculates geometry tangents though. + } + + remapMeshToGeometry = static_cast(m_geometries.size()); + + std::shared_ptr geometry(new sg::Triangles(m_idGeometry++)); + geometry->setAttributes(attributes); + geometry->setIndices(indices); + + m_geometries.push_back(geometry); + } + + m_remappedMeshIndices.push_back(remapMeshToGeometry); + } + + std::shared_ptr group = traverseScene(scene, indexSceneBase, scene->mRootNode); + m_mapGroups[filename] = group; // Allow instancing of this whole model. + + Assimp::DefaultLogger::kill(); // Kill it after the work is done + + return group; +} + +std::shared_ptr Application::traverseScene(const struct aiScene *scene, const unsigned int indexSceneBase, const struct aiNode* node) +{ + // Create a group to hold all children and all meshes of this node. + std::shared_ptr group(new sg::Group(m_idGroup++)); + + const aiMatrix4x4& m = node->mTransformation; + + const float trafo[12] = + { + float(m.a1), float(m.a2), float(m.a3), float(m.a4), + float(m.b1), float(m.b2), float(m.b3), float(m.b4), + float(m.c1), float(m.c2), float(m.c3), float(m.c4) + }; + + // Need to do a depth first traversal here to attach the bottom most nodes to each node's group. + for (unsigned int iChild = 0; iChild < node->mNumChildren; ++iChild) + { + std::shared_ptr child = traverseScene(scene, indexSceneBase, node->mChildren[iChild]); + + // Create an instance which holds the subtree. + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(child); + + group->addChild(instance); + } + + // Now also gather all meshes assigned to this node. + for (unsigned int iMesh = 0; iMesh < node->mNumMeshes; ++iMesh) + { + const unsigned int indexMesh = node->mMeshes[iMesh]; // Original mesh index in the assimp scene. + MY_ASSERT(indexMesh < m_remappedMeshIndices.size()) + + if (m_remappedMeshIndices[indexMesh] != ~0u) // If there exists a Triangles geometry for this assimp mesh, then build the Instance. + { + const unsigned int indexGeometry = m_remappedMeshIndices[indexMesh]; + + // Create an instance with the current nodes transformation and append it to the parent group. + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(m_geometries[indexGeometry]); + + const struct aiMesh* mesh = scene->mMeshes[indexMesh]; + + // Allow to specify different materials per assimp model by using the filename (no path no extension) and the material index. + struct aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + + std::string nameMaterialReference; + aiString materialName; + if (material->Get(AI_MATKEY_NAME, materialName) == aiReturn_SUCCESS) + { + nameMaterialReference = std::string(materialName.C_Str()); + } + + int indexMaterial = -1; + std::map::const_iterator itm = m_mapMaterialReferences.find(nameMaterialReference); + if (itm != m_mapMaterialReferences.end()) + { + indexMaterial = itm->second; + + // The materials had been created with default albedo colors. + // Change it to the diffuse color of the assimp material. + aiColor4D diffuse; + if (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == aiReturn_SUCCESS) + { + m_materialsGUI[indexMaterial].albedo = make_float3(diffuse.r, diffuse.g, diffuse.b); + } + } + else + { + std::cerr << "WARNING: traverseScene() No material found for " << nameMaterialReference << ". Trying default.\n"; + + std::map::const_iterator itmd = m_mapMaterialReferences.find(std::string("default")); + if (itmd != m_mapMaterialReferences.end()) + { + indexMaterial = itmd->second; + } + else + { + std::cerr << "ERROR: loadSceneDescription() No default material found\n"; + } + } + instance->setMaterial(indexMaterial); + + group->addChild(instance); + } + } + return group; +} diff --git a/apps/bench_shared/src/Box.cpp b/apps/bench_shared/src/Box.cpp new file mode 100644 index 00000000..40960b11 --- /dev/null +++ b/apps/bench_shared/src/Box.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // A simple unit cube built from 12 triangles. + void Triangles::createBox() + { + m_attributes.clear(); + m_indices.clear(); + + const float left = -1.0f; + const float right = 1.0f; + const float bottom = -1.0f; + const float top = 1.0f; + const float back = -1.0f; + const float front = 1.0f; + + TriangleAttributes attrib; + + // Left. + attrib.tangent = make_float3(0.0f, 0.0f, 1.0f); + attrib.normal = make_float3(-1.0f, 0.0f, 0.0f); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Right. + attrib.tangent = make_float3(0.0f, 0.0f, -1.0f); + attrib.normal = make_float3(1.0f, 0.0f, 0.0f); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Back. + attrib.tangent = make_float3(-1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, -1.0f); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Front. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Bottom. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, -1.0f, 0.0f); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Top. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 1.0f, 0.0f); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + for (unsigned int i = 0; i < 6; ++i) + { + const unsigned int idx = i * 4; // Four m_attributes per box face. + + m_indices.push_back(idx); + m_indices.push_back(idx + 1); + m_indices.push_back(idx + 2); + + m_indices.push_back(idx + 2); + m_indices.push_back(idx + 3); + m_indices.push_back(idx); + } + } + +} // namespace sg diff --git a/apps/bench_shared/src/Camera.cpp b/apps/bench_shared/src/Camera.cpp new file mode 100644 index 00000000..18c91647 --- /dev/null +++ b/apps/bench_shared/src/Camera.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Camera.h" + +#include + +#include "shaders/shader_common.h" + + +Camera::Camera() +: m_distance(10.0f) // Camera is 10 units aways from the point of interest +, m_phi(0.75f) // on the positive z-axis +, m_theta(0.6f) // slightly above the equator (at 0.5f). +, m_fov(60.0f) +, m_widthResolution(1) +, m_heightResolution(1) +, m_aspect(1.0f) +, m_baseX(0) +, m_baseY(0) +, m_speedRatio(10.0f) +, m_dx(0) +, m_dy(0) +, m_changed(false) +{ + m_center = make_float3(0.0f, 0.0f, 0.0f); + + m_cameraP = make_float3(0.0f, 0.0f, 1.0f); + m_cameraU = make_float3(1.0f, 0.0f, 0.0f); + m_cameraV = make_float3(0.0f, 1.0f, 0.0f); + m_cameraW = make_float3(0.0f, 0.0f, -1.0f); +} + +//Camera::~Camera() +//{ +//} + +void Camera::setResolution(int w, int h) +{ + if (m_widthResolution != w || m_heightResolution != h) + { + // Never drop to zero viewport size. This avoids lots of checks for zero in other routines. + m_widthResolution = (0 < w) ? w : 1; + m_heightResolution = (0 < h) ? h : 1; + m_aspect = float(m_widthResolution) / float(m_heightResolution); + m_changed = true; + } +} + +void Camera::setBaseCoordinates(int x, int y) +{ + m_baseX = x; + m_baseY = y; +} + +void Camera::orbit(int x, int y) +{ + if (setDelta(x, y)) + { + m_phi -= float(m_dx) / float(m_widthResolution); // Negative to match the mouse movement to the phi progression. + // Wrap phi. + if (m_phi < 0.0f) + { + m_phi += 1.0f; + } + else if (1.0f < m_phi) + { + m_phi -= 1.0f; + } + + m_theta += float(m_dy) / float(m_heightResolution); + // Clamp theta. + if (m_theta < 0.0f) + { + m_theta = 0.0f; + } + else if (1.0f < m_theta) + { + m_theta = 1.0f; + } + } +} + +void Camera::pan(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float u = float(m_dx) / m_speedRatio; + float v = float(m_dy) / m_speedRatio; + // Pan the center of interest, the rest will follow. + m_center = m_center - u * m_cameraU + v * m_cameraV; + } +} + +void Camera::dolly(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float w = float(m_dy) / m_speedRatio; + // Adjust the distance, the center of interest stays fixed so that the orbit is around the same center. + m_distance -= w * length(m_cameraW); // Dragging down moves the camera forwards. "Drag-in the object". + if (m_distance < 0.001f) // Avoid swapping sides. Scene units are meters [m]. + { + m_distance = 0.001f; + } + } +} + +void Camera::focus(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float w = float(m_dy) / m_speedRatio; + // Adjust the center of interest. + setFocusDistance(m_distance - w * length(m_cameraW)); + } +} + +void Camera::setFocusDistance(float f) +{ + if (m_distance != f && 0.001f < f) // Avoid swapping sides. + { + m_distance = f; + m_center = m_cameraP + m_distance * m_cameraW; // Keep the camera position fixed and calculate a new center of interest which is the focus plane. + m_changed = true; // m_changed is only reset when asking for the frustum + } +} + +void Camera::zoom(float x) +{ + m_fov += float(x); + if (m_fov < 1.0f) + { + m_fov = 1.0f; + } + else if (179.0 < m_fov) + { + m_fov = 179.0f; + } + m_changed = true; +} + +float Camera::getAspectRatio() const +{ + return m_aspect; +} + +void Camera::markDirty() +{ + m_changed = true; +} + +bool Camera::getFrustum(float3& p, float3& u, float3& v, float3& w, bool force) +{ + bool changed = force || m_changed; + if (changed) + { + // Recalculate the camera parameters. + const float cosPhi = cosf(m_phi * 2.0f * M_PIf); + const float sinPhi = sinf(m_phi * 2.0f * M_PIf); + const float cosTheta = cosf(m_theta * M_PIf); + const float sinTheta = sinf(m_theta * M_PIf); + + const float3 normal = make_float3(cosPhi * sinTheta, -cosTheta, -sinPhi * sinTheta); // "normal", unit vector from origin to spherical coordinates (phi, theta) + + const float tanFovHalf = tanf((m_fov * 0.5f) * M_PIf / 180.0f); // m_fov is in the range [1.0f, 179.0f]. + + m_cameraP = m_center + m_distance * normal; + + m_cameraU = m_aspect * make_float3(-sinPhi, 0.0f, -cosPhi) * tanFovHalf; // "tangent" + m_cameraV = make_float3(cosTheta * cosPhi, sinTheta, cosTheta * -sinPhi) * tanFovHalf; // "bitangent" + m_cameraW = -normal; // "-normal" to look at the center. + + p = m_cameraP; + u = m_cameraU; + v = m_cameraV; + w = m_cameraW; + + m_changed = false; // Next time asking for the frustum will return false unless the camera has changed again. + } + return changed; +} + +bool Camera::setDelta(int x, int y) +{ + if (m_baseX != x || m_baseY != y) + { + m_dx = x - m_baseX; + m_dy = y - m_baseY; + + m_baseX = x; + m_baseY = y; + + m_changed = true; // m_changed is only reset when asking for the frustum. + return true; // There is a delta. + } + return false; +} + +void Camera::setSpeedRatio(float f) +{ + m_speedRatio = f; + if (m_speedRatio < 0.01f) + { + m_speedRatio = 0.01f; + } + else if (1000.0f < m_speedRatio) + { + m_speedRatio = 1000.0f; + } +} diff --git a/apps/bench_shared/src/Device.cpp b/apps/bench_shared/src/Device.cpp new file mode 100644 index 00000000..b27ddf04 --- /dev/null +++ b/apps/bench_shared/src/Device.cpp @@ -0,0 +1,1836 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Device.h" + +#include "inc/CheckMacros.h" + +#include "shaders/compositor_data.h" + + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +// The cfgmgr32 header is necessary for interrogating driver information in the registry. +#include +// For convenience the library is also linked in automatically using the #pragma command. +#pragma comment(lib, "Cfgmgr32.lib") +#else +#include +#endif + +#include +#if defined( _WIN32 ) +#include +#endif + +// CUDA Driver API version of the OpenGL interop header. +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +// Original code from optix_stubs.h +static void* optixLoadWindowsDll(void) +{ + const char* optixDllName = "nvoptix.dll"; + void* handle = NULL; + + // Get the size of the path first, then allocate + unsigned int size = GetSystemDirectoryA(NULL, 0); + if (size == 0) + { + // Couldn't get the system path size, so bail + return NULL; + } + + size_t pathSize = size + 1 + strlen(optixDllName); + char* systemPath = (char*) malloc(pathSize); + + if (GetSystemDirectoryA(systemPath, size) != size - 1) + { + // Something went wrong + free(systemPath); + return NULL; + } + + strcat(systemPath, "\\"); + strcat(systemPath, optixDllName); + + handle = LoadLibraryA(systemPath); + + free(systemPath); + + if (handle) + { + return handle; + } + + // If we didn't find it, go looking in the register store. Since nvoptix.dll doesn't + // have its own registry entry, we are going to look for the OpenGL driver which lives + // next to nvoptix.dll. 0 (null) will be returned if any errors occured. + + static const char* deviceInstanceIdentifiersGUID = "{4d36e968-e325-11ce-bfc1-08002be10318}"; + const ULONG flags = CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT; + ULONG deviceListSize = 0; + + if (CM_Get_Device_ID_List_SizeA(&deviceListSize, deviceInstanceIdentifiersGUID, flags) != CR_SUCCESS) + { + return NULL; + } + + char* deviceNames = (char*) malloc(deviceListSize); + + if (CM_Get_Device_ID_ListA(deviceInstanceIdentifiersGUID, deviceNames, deviceListSize, flags)) + { + free(deviceNames); + return NULL; + } + + DEVINST devID = 0; + + // Continue to the next device if errors are encountered. + for (char* deviceName = deviceNames; *deviceName; deviceName += strlen(deviceName) + 1) + { + if (CM_Locate_DevNodeA(&devID, deviceName, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) + { + continue; + } + + HKEY regKey = 0; + if (CM_Open_DevNode_Key(devID, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, ®Key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) + { + continue; + } + + const char* valueName = "OpenGLDriverName"; + DWORD valueSize = 0; + + LSTATUS ret = RegQueryValueExA(regKey, valueName, NULL, NULL, NULL, &valueSize); + if (ret != ERROR_SUCCESS) + { + RegCloseKey(regKey); + continue; + } + + char* regValue = (char*) malloc(valueSize); + ret = RegQueryValueExA(regKey, valueName, NULL, NULL, (LPBYTE) regValue, &valueSize); + if (ret != ERROR_SUCCESS) + { + free(regValue); + RegCloseKey(regKey); + continue; + } + + // Strip the OpenGL driver dll name from the string then create a new string with + // the path and the nvoptix.dll name + for (int i = valueSize - 1; i >= 0 && regValue[i] != '\\'; --i) + { + regValue[i] = '\0'; + } + + size_t newPathSize = strlen(regValue) + strlen(optixDllName) + 1; + char* dllPath = (char*) malloc(newPathSize); + strcpy(dllPath, regValue); + strcat(dllPath, optixDllName); + + free(regValue); + RegCloseKey(regKey); + + handle = LoadLibraryA((LPCSTR) dllPath); + free(dllPath); + + if (handle) + { + break; + } + } + + free(deviceNames); + + return handle; +} +#endif + + +// Global logger function instead of the Logger class to be able to submit per device date via the cbdata pointer. +static std::mutex g_mutexLogger; + +static void callbackLogger(unsigned int level, const char* tag, const char* message, void* cbdata) +{ + std::lock_guard lock(g_mutexLogger); + + Device* device = static_cast(cbdata); + + std::cerr << tag << " (" << level << ") [" << device->m_ordinal << "]: " << ((message) ? message : "(no message)") << '\n'; +} + + +static std::vector readData(std::string const& filename) +{ + std::ifstream inputData(filename, std::ios::binary); + + if (inputData.fail()) + { + std::cerr << "ERROR: readData() Failed to open file " << filename << '\n'; + return std::vector(); + } + + // Copy the input buffer to a char vector. + std::vector data(std::istreambuf_iterator(inputData), {}); + + if (inputData.fail()) + { + std::cerr << "ERROR: readData() Failed to read file " << filename << '\n'; + return std::vector(); + } + + return data; +} + + +Device::Device(const int ordinal, + const int index, + const int count, + const int miss, + const int interop, + const unsigned int tex, + const unsigned int pbo, + const size_t sizeArena) +: m_ordinal(ordinal) +, m_index(index) +, m_count(count) +, m_miss(miss) +, m_interop(interop) +, m_tex(tex) +, m_pbo(pbo) +, m_nodeMask(0) +, m_launchWidth(0) +, m_ownsSharedBuffer(false) +, m_d_compositorData(0) +, m_cudaGraphicsResource(nullptr) +, m_sizeMemoryTextureArrays(0) +{ + // Get the CUdevice handle from the CUDA device ordinal. + CU_CHECK( cuDeviceGet(&m_cudaDevice, m_ordinal) ); + + initDeviceAttributes(); // Query all CUDA capabilities of this device. + + OPTIX_CHECK( initFunctionTable() ); + + // Create a CUDA Context and make it current to this thread. + // PERF What is the best CU_CTX_SCHED_* setting here? + // CU_CTX_MAP_HOST host to allow pinned memory. + CU_CHECK( cuCtxCreate(&m_cudaContext, CU_CTX_SCHED_SPIN | CU_CTX_MAP_HOST, m_cudaDevice) ); + + // PERF To make use of asynchronous copies. Currently not really anything happening in parallel due to synchronize calls. + CU_CHECK( cuStreamCreate(&m_cudaStream, CU_STREAM_NON_BLOCKING) ); + + size_t sizeFree = 0; + size_t sizeTotal = 0; + + CU_CHECK( cuMemGetInfo(&sizeFree, &sizeTotal) ); + + std::cout << "Device ordinal " << m_ordinal << ": " << sizeFree << " bytes free; " << sizeTotal << " bytes total\n"; + + m_allocator = new cuda::ArenaAllocator(sizeArena * 1024 * 1024); // The ArenaAllocator gets the default Arena size in bytes! + +#if 1 + // UUID works under Windows and Linux. + memset(&m_deviceUUID, 0, 16); + CU_CHECK( cuDeviceGetUuid(&m_deviceUUID, m_cudaDevice) ); +#else + // LUID only works under Windows and only in WDDM mode, not in TCC mode! + // Get the LUID and node mask to be able to determine which device needs to allocate the peer-to-peer staging buffer for the OpenGL interop PBO. + memset(m_deviceLUID, 0, 8); + CU_CHECK( cuDeviceGetLuid(m_deviceLUID, &m_nodeMask, m_cudaDevice) ); +#endif + + CU_CHECK( cuModuleLoad(&m_moduleCompositor, "./bench_shared_core/compositor.ptx") ); // DAR FIXME Only load this on the primary device! + CU_CHECK( cuModuleGetFunction(&m_functionCompositor, m_moduleCompositor, "compositor") ); + + OptixDeviceContextOptions options = {}; + + options.logCallbackFunction = &callbackLogger; + options.logCallbackData = this; // This allows per device logs. It's currently printing the device ordinal. + options.logCallbackLevel = 3; // Keep at warning level to suppress the disk cache messages. + + OPTIX_CHECK( m_api.optixDeviceContextCreate(m_cudaContext, &options, &m_optixContext) ); + + initDeviceProperties(); // OptiX + + m_d_systemData = reinterpret_cast(memAlloc(sizeof(SystemData), 16)); // Currently 8 would be enough. + + m_isDirtySystemData = true; // Trigger SystemData update before the next launch. + + // Initialize all renderer system data. + //m_systemData.rect = make_int4(0, 0, 1, 1); // Unused, this is not a tiled renderer. + m_systemData.topObject = 0; + m_systemData.outputBuffer = 0; // Deferred allocation. Only done in render() of the derived Device classes to allow for different memory spaces! + m_systemData.tileBuffer = 0; // For the final frame tiled renderer the intermediate buffer is only tileSize. + m_systemData.texelBuffer = 0; // For the final frame tiled renderer. Contains the accumulated result of the current tile. + m_systemData.cameraDefinitions = nullptr; + m_systemData.lightDefinitions = nullptr; + m_systemData.materialDefinitions = nullptr; + m_systemData.envTexture = 0; + m_systemData.envCDF_U = nullptr; + m_systemData.envCDF_V = nullptr; + m_systemData.resolution = make_int2(1, 1); // Deferred allocation after setResolution() when m_isDirtyOutputBuffer == true. + m_systemData.tileSize = make_int2(8, 8); // Default value for multi-GPU tiling. Must be power-of-two values. (8x8 covers either 8x4 or 4x8 internal 2D warp shapes.) + m_systemData.tileShift = make_int2(3, 3); // The right-shift for the division by tileSize. + m_systemData.pathLengths = make_int2(2, 5); // min, max + m_systemData.deviceCount = m_count; // The number of active devices. + m_systemData.deviceIndex = m_index; // This allows to distinguish multiple devices. + m_systemData.iterationIndex = 0; + m_systemData.samplesSqrt = 0; // Invalid value! Enforces that there is at least one setState() call before rendering. + m_systemData.sceneEpsilon = 500.0f * SCENE_EPSILON_SCALE; + m_systemData.clockScale = 1000.0f * CLOCK_FACTOR_SCALE; + m_systemData.lensShader = 0; + m_systemData.numCameras = 0; + m_systemData.numLights = 0; + m_systemData.numMaterials = 0; + m_systemData.envWidth = 0; + m_systemData.envHeight = 0; + m_systemData.envIntegral = 1.0f; + m_systemData.envRotation = 0.0f; + + m_isDirtyOutputBuffer = true; // First render call initializes it. This is done in the derived render() functions. + + m_moduleFilenames.resize(NUM_MODULE_IDENTIFIERS); + + // Starting with OptiX SDK 7.5.0 and CUDA 11.7 either PTX or OptiX IR input can be used to create modules. + // Just initialize the m_moduleFilenames depending on the definition of USE_OPTIX_IR. + // That is added to the project definitions inside the CMake script when OptiX SDK 7.5.0 and CUDA 11.7 or newer are found. +#if defined(USE_OPTIX_IR) + m_moduleFilenames[MODULE_ID_RAYGENERATION] = std::string("./bench_shared_core/raygeneration.optixir"); + m_moduleFilenames[MODULE_ID_EXCEPTION] = std::string("./bench_shared_core/exception.optixir"); + m_moduleFilenames[MODULE_ID_MISS] = std::string("./bench_shared_core/miss.optixir"); + m_moduleFilenames[MODULE_ID_CLOSESTHIT] = std::string("./bench_shared_core/closesthit.optixir"); + m_moduleFilenames[MODULE_ID_ANYHIT] = std::string("./bench_shared_core/anyhit.optixir"); + m_moduleFilenames[MODULE_ID_LENS_SHADER] = std::string("./bench_shared_core/lens_shader.optixir"); + m_moduleFilenames[MODULE_ID_LIGHT_SAMPLE] = std::string("./bench_shared_core/light_sample.optixir"); + m_moduleFilenames[MODULE_ID_BXDF_DIFFUSE] = std::string("./bench_shared_core/bxdf_diffuse.optixir"); + m_moduleFilenames[MODULE_ID_BXDF_SPECULAR] = std::string("./bench_shared_core/bxdf_specular.optixir"); + m_moduleFilenames[MODULE_ID_BXDF_GGX_SMITH] = std::string("./bench_shared_core/bxdf_ggx_smith.optixir"); +#else + m_moduleFilenames[MODULE_ID_RAYGENERATION] = std::string("./bench_shared_core/raygeneration.ptx"); + m_moduleFilenames[MODULE_ID_EXCEPTION] = std::string("./bench_shared_core/exception.ptx"); + m_moduleFilenames[MODULE_ID_MISS] = std::string("./bench_shared_core/miss.ptx"); + m_moduleFilenames[MODULE_ID_CLOSESTHIT] = std::string("./bench_shared_core/closesthit.ptx"); + m_moduleFilenames[MODULE_ID_ANYHIT] = std::string("./bench_shared_core/anyhit.ptx"); + m_moduleFilenames[MODULE_ID_LENS_SHADER] = std::string("./bench_shared_core/lens_shader.ptx"); + m_moduleFilenames[MODULE_ID_LIGHT_SAMPLE] = std::string("./bench_shared_core/light_sample.ptx"); + m_moduleFilenames[MODULE_ID_BXDF_DIFFUSE] = std::string("./bench_shared_core/bxdf_diffuse.ptx"); + m_moduleFilenames[MODULE_ID_BXDF_SPECULAR] = std::string("./bench_shared_core/bxdf_specular.ptx"); + m_moduleFilenames[MODULE_ID_BXDF_GGX_SMITH] = std::string("./bench_shared_core/bxdf_ggx_smith.ptx"); +#endif + + initPipeline(); +} + + +Device::~Device() +{ + CU_CHECK_NO_THROW( cuCtxSetCurrent(m_cudaContext) ); // Activate this CUDA context. Not using activate() because this needs a no-throw check. + CU_CHECK_NO_THROW( cuCtxSynchronize() ); // Make sure everthing running on this CUDA context has finished. + + if (m_cudaGraphicsResource != nullptr) + { + CU_CHECK_NO_THROW( cuGraphicsUnregisterResource(m_cudaGraphicsResource) ); + } + + CU_CHECK_NO_THROW( cuModuleUnload(m_moduleCompositor) ); + + for (std::map::const_iterator it = m_mapTextures.begin(); it != m_mapTextures.end(); ++it) + { + if (it->second) + { + // The texture array data might be owned by a peer device. + // Explicitly destroy only the parts which belong to this device. + m_sizeMemoryTextureArrays -= it->second->destroy(this); + + delete it->second; // This will delete the CUtexObject which exists per device. + } + } + MY_ASSERT(m_sizeMemoryTextureArrays == 0); // Make sure the texture memory racking + + OPTIX_CHECK_NO_THROW( m_api.optixPipelineDestroy(m_pipeline) ); + OPTIX_CHECK_NO_THROW( m_api.optixDeviceContextDestroy(m_optixContext) ); + + delete m_allocator; // This frees all CUDA allocations! + + CU_CHECK_NO_THROW( cuStreamDestroy(m_cudaStream) ); + CU_CHECK_NO_THROW( cuCtxDestroy(m_cudaContext) ); +} + + +void Device::initDeviceAttributes() +{ + char buffer[1024]; + buffer[1023] = 0; + + CU_CHECK( cuDeviceGetName(buffer, 1023, m_cudaDevice) ); + m_deviceName = std::string(buffer); + + CU_CHECK(cuDeviceGetPCIBusId(buffer, 1023, m_cudaDevice)); + m_devicePciBusId = std::string(buffer); + + std::cout << "Device ordinal " << m_ordinal << ": " << m_deviceName << " visible\n"; + + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxThreadsPerBlock, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimX, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimY, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Y, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimZ, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Z, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimX, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimY, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimZ, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerBlock, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.sharedMemoryPerBlock, CU_DEVICE_ATTRIBUTE_SHARED_MEMORY_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.totalConstantMemory, CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.warpSize, CU_DEVICE_ATTRIBUTE_WARP_SIZE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxPitch, CU_DEVICE_ATTRIBUTE_MAX_PITCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxRegistersPerBlock, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.registersPerBlock, CU_DEVICE_ATTRIBUTE_REGISTERS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.clockRate, CU_DEVICE_ATTRIBUTE_CLOCK_RATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.textureAlignment, CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.gpuOverlap, CU_DEVICE_ATTRIBUTE_GPU_OVERLAP, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiprocessorCount, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.kernelExecTimeout, CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.integrated, CU_DEVICE_ATTRIBUTE_INTEGRATED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canMapHostMemory, CU_DEVICE_ATTRIBUTE_CAN_MAP_HOST_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeMode, CU_DEVICE_ATTRIBUTE_COMPUTE_MODE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dDepth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayNumslices, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_NUMSLICES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.surfaceAlignment, CU_DEVICE_ATTRIBUTE_SURFACE_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.concurrentKernels, CU_DEVICE_ATTRIBUTE_CONCURRENT_KERNELS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.eccEnabled, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciBusId, CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciDeviceId, CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.tccDriver, CU_DEVICE_ATTRIBUTE_TCC_DRIVER, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.memoryClockRate, CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.globalMemoryBusWidth, CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.l2CacheSize, CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxThreadsPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.asyncEngineCount, CU_DEVICE_ATTRIBUTE_ASYNC_ENGINE_COUNT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.unifiedAddressing, CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canTex2dGather, CU_DEVICE_ATTRIBUTE_CAN_TEX2D_GATHER, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dGatherWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dGatherHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dWidthAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dHeightAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dDepthAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciDomainId, CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.texturePitchAlignment, CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dDepth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_DEPTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLinearWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LINEAR_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearPitch, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_PITCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dMipmappedWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dMipmappedHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeCapabilityMajor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeCapabilityMinor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dMipmappedWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_MIPMAPPED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.streamPrioritiesSupported, CU_DEVICE_ATTRIBUTE_STREAM_PRIORITIES_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.globalL1CacheSupported, CU_DEVICE_ATTRIBUTE_GLOBAL_L1_CACHE_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.localL1CacheSupported, CU_DEVICE_ATTRIBUTE_LOCAL_L1_CACHE_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxRegistersPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.managedMemory, CU_DEVICE_ATTRIBUTE_MANAGED_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiGpuBoard, CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiGpuBoardGroupId, CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD_GROUP_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.hostNativeAtomicSupported, CU_DEVICE_ATTRIBUTE_HOST_NATIVE_ATOMIC_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.singleToDoublePrecisionPerfRatio, CU_DEVICE_ATTRIBUTE_SINGLE_TO_DOUBLE_PRECISION_PERF_RATIO, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pageableMemoryAccess, CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.concurrentManagedAccess, CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computePreemptionSupported, CU_DEVICE_ATTRIBUTE_COMPUTE_PREEMPTION_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUseHostPointerForRegisteredMem, CU_DEVICE_ATTRIBUTE_CAN_USE_HOST_POINTER_FOR_REGISTERED_MEM, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUse64BitStreamMemOps, CU_DEVICE_ATTRIBUTE_CAN_USE_64_BIT_STREAM_MEM_OPS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUseStreamWaitValueNor, CU_DEVICE_ATTRIBUTE_CAN_USE_STREAM_WAIT_VALUE_NOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.cooperativeLaunch, CU_DEVICE_ATTRIBUTE_COOPERATIVE_LAUNCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.cooperativeMultiDeviceLaunch, CU_DEVICE_ATTRIBUTE_COOPERATIVE_MULTI_DEVICE_LAUNCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerBlockOptin, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK_OPTIN, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canFlushRemoteWrites, CU_DEVICE_ATTRIBUTE_CAN_FLUSH_REMOTE_WRITES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.hostRegisterSupported, CU_DEVICE_ATTRIBUTE_HOST_REGISTER_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pageableMemoryAccessUsesHostPageTables, CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS_USES_HOST_PAGE_TABLES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.directManagedMemAccessFromHost, CU_DEVICE_ATTRIBUTE_DIRECT_MANAGED_MEM_ACCESS_FROM_HOST, m_cudaDevice) ); +} + +void Device::initDeviceProperties() +{ + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_RTCORE_VERSION, &m_deviceProperty.rtcoreVersion, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH, &m_deviceProperty.limitMaxTraceDepth, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH, &m_deviceProperty.limitMaxTraversableGraphDepth, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS, &m_deviceProperty.limitMaxPrimitivesPerGas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS, &m_deviceProperty.limitMaxInstancesPerIas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID, &m_deviceProperty.limitMaxInstanceId, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK, &m_deviceProperty.limitNumBitsInstanceVisibilityMask, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS, &m_deviceProperty.limitMaxSbtRecordsPerGas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET, &m_deviceProperty.limitMaxSbtOffset, sizeof(unsigned int)) ); + +#if 0 + std::cout << "OPTIX_DEVICE_PROPERTY_RTCORE_VERSION = " << m_deviceProperty.rtcoreVersion << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH = " << m_deviceProperty.limitMaxTraceDepth << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH = " << m_deviceProperty.limitMaxTraversableGraphDepth << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS = " << m_deviceProperty.limitMaxPrimitivesPerGas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS = " << m_deviceProperty.limitMaxInstancesPerIas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID = " << m_deviceProperty.limitMaxInstanceId << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK = " << m_deviceProperty.limitNumBitsInstanceVisibilityMask << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS = " << m_deviceProperty.limitMaxSbtRecordsPerGas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET = " << m_deviceProperty.limitMaxSbtOffset << '\n'; +#endif +} + + +OptixResult Device::initFunctionTable() +{ +#ifdef _WIN32 + void* handle = optixLoadWindowsDll(); + if (!handle) + { + return OPTIX_ERROR_LIBRARY_NOT_FOUND; + } + + void* symbol = reinterpret_cast(GetProcAddress((HMODULE) handle, "optixQueryFunctionTable")); + if (!symbol) + { + return OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND; + } +#else + void* handle = dlopen("libnvoptix.so.1", RTLD_NOW); + if (!handle) + { + return OPTIX_ERROR_LIBRARY_NOT_FOUND; + } + + void* symbol = dlsym(handle, "optixQueryFunctionTable"); + if (!symbol) + + { + return OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND; + } +#endif + + OptixQueryFunctionTable_t* optixQueryFunctionTable = reinterpret_cast(symbol); + + return optixQueryFunctionTable(OPTIX_ABI_VERSION, 0, 0, 0, &m_api, sizeof(OptixFunctionTable)); +} + + +void Device::initPipeline() +{ + MY_ASSERT(NUM_RAYTYPES == 2); // The following code only works for two raytypes. + + OptixModuleCompileOptions mco = {}; + + mco.maxRegisterCount = OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT; +#if USE_DEBUG_EXCEPTIONS + mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_0; // No optimizations. + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; // Full debug. Never profile kernels with this setting! +#else + mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_3; // All optimizations, is the default. + // Keep generated line info for Nsight Compute profiling. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) +#if (OPTIX_VERSION >= 70400) + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; +#else + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; +#endif +#endif // USE_DEBUG_EXCEPTIONS + + OptixPipelineCompileOptions pco = {}; + + pco.usesMotionBlur = 0; + pco.traversableGraphFlags = OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; + pco.numPayloadValues = 2; // I need two to encode a 64-bit pointer to the per ray payload structure. + pco.numAttributeValues = 2; // The minimum is two for the barycentrics. +#if USE_DEBUG_EXCEPTIONS + pco.exceptionFlags = + OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW + | OPTIX_EXCEPTION_FLAG_TRACE_DEPTH + | OPTIX_EXCEPTION_FLAG_USER +#if (OPTIX_VERSION < 80000) + // Removed in OptiX SDK 8.0.0. + // Use OptixDeviceContextOptions validationMode = OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL instead. + | OPTIX_EXCEPTION_FLAG_DEBUG +#endif + ; +#else + pco.exceptionFlags = OPTIX_EXCEPTION_FLAG_NONE; +#endif + pco.pipelineLaunchParamsVariableName = "sysData"; +#if (OPTIX_VERSION >= 70100) + // Only using built-in Triangles in this renderer. + // This is the recommended setting for best performance then. + pco.usesPrimitiveTypeFlags = OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; // New in OptiX 7.1.0. +#endif + + // Each source file results in one OptixModule. + std::vector modules(NUM_MODULE_IDENTIFIERS); + + // Create all modules: + for (size_t i = 0; i < m_moduleFilenames.size(); ++i) + { + // Since OptiX 7.5.0 the program input can either be *.ptx source code or *.optixir binary code. + // The module filenames are automatically switched between *.ptx or *.optixir extension based on the definition of USE_OPTIX_IR + std::vector programData = readData(m_moduleFilenames[i]); + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixModuleCreate(m_optixContext, &mco, &pco, programData.data(), programData.size(), nullptr, nullptr, &modules[i]) ); +#else + OPTIX_CHECK( m_api.optixModuleCreateFromPTX(m_optixContext, &mco, &pco, programData.data(), programData.size(), nullptr, nullptr, &modules[i]) ); +#endif + } + + std::vector programGroupDescriptions(NUM_PROGRAM_GROUP_IDS); + memset(programGroupDescriptions.data(), 0, sizeof(OptixProgramGroupDesc) * programGroupDescriptions.size()); + + OptixProgramGroupDesc* pgd; + + // All of these first because they are SbtRecordHeader and put into a single CUDA memory block. + pgd = &programGroupDescriptions[PGID_RAYGENERATION]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->raygen.module = modules[MODULE_ID_RAYGENERATION]; + pgd->raygen.entryFunctionName = "__raygen__path_tracer_local_copy"; + + pgd = &programGroupDescriptions[PGID_EXCEPTION]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_EXCEPTION; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->exception.module = modules[MODULE_ID_EXCEPTION]; + pgd->exception.entryFunctionName = "__exception__all"; + + pgd = &programGroupDescriptions[PGID_MISS_RADIANCE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_MISS; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->miss.module = modules[MODULE_ID_MISS]; + switch (m_miss) + { + case 0: // Black, not a light. + pgd->miss.entryFunctionName = "__miss__env_null"; + break; + case 1: // Constant white environment. + default: + pgd->miss.entryFunctionName = "__miss__env_constant"; + break; + case 2: // Spherical HDR environment light. + pgd->miss.entryFunctionName = "__miss__env_sphere"; + break; + } + + pgd = &programGroupDescriptions[PGID_MISS_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_MISS; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->miss.module = nullptr; + pgd->miss.entryFunctionName = nullptr; // No miss program for shadow rays. + + // CALLABLES + // Lens Shader + pgd = &programGroupDescriptions[PGID_LENS_PINHOLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__pinhole"; + + pgd = &programGroupDescriptions[PGID_LENS_FISHEYE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__fisheye"; + + pgd = &programGroupDescriptions[PGID_LENS_SPHERE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sphere"; + + // Light Sampler + pgd = &programGroupDescriptions[PGID_LIGHT_ENV]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = (m_miss == 2) ? "__direct_callable__light_env_sphere" : "__direct_callable__light_env_constant"; // miss == 0 is not a light, use constant program. + + pgd = &programGroupDescriptions[PGID_LIGHT_AREA]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_parallelogram"; + + // BxDF sample and eval + pgd = &programGroupDescriptions[PGID_BRDF_DIFFUSE_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_DIFFUSE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_brdf_diffuse"; + + pgd = &programGroupDescriptions[PGID_BRDF_DIFFUSE_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_DIFFUSE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_diffuse"; + + pgd = &programGroupDescriptions[PGID_BRDF_SPECULAR_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_brdf_specular"; + + pgd = &programGroupDescriptions[PGID_BRDF_SPECULAR_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_specular"; // black + + pgd = &programGroupDescriptions[PGID_BSDF_SPECULAR_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_bsdf_specular"; + + pgd = &programGroupDescriptions[PGID_BSDF_SPECULAR_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + // No implementation for __direct_callable__eval_bsdf_specular, it's specular. + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_specular"; // black + + pgd = &programGroupDescriptions[PGID_BRDF_GGX_SMITH_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_GGX_SMITH]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_brdf_ggx_smith"; + + pgd = &programGroupDescriptions[PGID_BRDF_GGX_SMITH_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_GGX_SMITH]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_ggx_smith"; + + pgd = &programGroupDescriptions[PGID_BSDF_GGX_SMITH_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_GGX_SMITH]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_bsdf_ggx_smith"; + + pgd = &programGroupDescriptions[PGID_BSDF_GGX_SMITH_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + // No implementation for __direct_callable__eval_ggx_smith, it's specular. + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_specular"; // black + + // HitGroups are using SbtRecordGeometryInstanceData and will be put into a separate CUDA memory block. + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_CLOSESTHIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_ANYHIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow"; + + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE_CUTOUT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_CLOSESTHIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance"; + pgd->hitgroup.moduleAH = modules[MODULE_ID_ANYHIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__radiance_cutout"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW_CUTOUT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_ANYHIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow_cutout"; + + OptixProgramGroupOptions pgo = {}; // This is a just placeholder. + + std::vector programGroups(programGroupDescriptions.size()); + + OPTIX_CHECK( m_api.optixProgramGroupCreate(m_optixContext, programGroupDescriptions.data(), (unsigned int) programGroupDescriptions.size(), &pgo, nullptr, nullptr, programGroups.data()) ); + + OptixPipelineLinkOptions plo = {}; + + plo.maxTraceDepth = 2; +#if (OPTIX_VERSION < 70700) + // OptixPipelineLinkOptions debugLevel is only present in OptiX SDK versions before 7.7.0. + #if USE_DEBUG_EXCEPTIONS + plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; // Full debug. Never profile kernels with this setting! + #else + // Keep generated line info for Nsight Compute profiling. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) + #if (OPTIX_VERSION >= 70400) + plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; + #else + plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; + #endif + #endif // USE_DEBUG_EXCEPTIONS +#endif // 70700 + + OPTIX_CHECK( m_api.optixPipelineCreate(m_optixContext, &pco, &plo, programGroups.data(), (unsigned int) programGroups.size(), nullptr, nullptr, &m_pipeline) ); + + // STACK SIZES + OptixStackSizes ssp = {}; // Whole pipeline. + + for (auto pg: programGroups) + { + OptixStackSizes ss; + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixProgramGroupGetStackSize(pg, &ss, m_pipeline) ); +#else + OPTIX_CHECK( m_api.optixProgramGroupGetStackSize(pg, &ss) ); +#endif + + ssp.cssRG = std::max(ssp.cssRG, ss.cssRG); + ssp.cssMS = std::max(ssp.cssMS, ss.cssMS); + ssp.cssCH = std::max(ssp.cssCH, ss.cssCH); + ssp.cssAH = std::max(ssp.cssAH, ss.cssAH); + ssp.cssIS = std::max(ssp.cssIS, ss.cssIS); + ssp.cssCC = std::max(ssp.cssCC, ss.cssCC); + ssp.dssDC = std::max(ssp.dssDC, ss.dssDC); + } + + // Temporaries + unsigned int cssCCTree = ssp.cssCC; // Should be 0. No continuation callables in this pipeline. // maxCCDepth == 0 + unsigned int cssCHOrMSPlusCCTree = std::max(ssp.cssCH, ssp.cssMS) + cssCCTree; + + // Arguments + unsigned int directCallableStackSizeFromTraversal = ssp.dssDC; // maxDCDepth == 1 // FromTraversal: DC is invoked from IS or AH. // Possible stack size optimizations. + unsigned int directCallableStackSizeFromState = ssp.dssDC; // maxDCDepth == 1 // FromState: DC is invoked from RG, MS, or CH. // Possible stack size optimizations. + unsigned int continuationStackSize = ssp.cssRG + cssCCTree + cssCHOrMSPlusCCTree * (std::max(1u, plo.maxTraceDepth) - 1u) + + std::min(1u, plo.maxTraceDepth) * std::max(cssCHOrMSPlusCCTree, ssp.cssAH + ssp.cssIS); + unsigned int maxTraversableGraphDepth = 2; + + OPTIX_CHECK( m_api.optixPipelineSetStackSize(m_pipeline, directCallableStackSizeFromTraversal, directCallableStackSizeFromState, continuationStackSize, maxTraversableGraphDepth) ); + + // Set up the fixed portion of the Shader Binding Table (SBT) + + // Put all SbtRecordHeader types in one CUdeviceptr. + const int numHeaders = LAST_DIRECT_CALLABLE_ID - PGID_RAYGENERATION + 1; + + std::vector sbtRecordHeaders(numHeaders); + + for (int i = 0; i < numHeaders; ++i) + { + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_RAYGENERATION + i], &sbtRecordHeaders[i]) ); + } + + m_d_sbtRecordHeaders = memAlloc(sizeof(SbtRecordHeader) * numHeaders, OPTIX_SBT_RECORD_ALIGNMENT); + CU_CHECK( cuMemcpyHtoDAsync(m_d_sbtRecordHeaders, sbtRecordHeaders.data(), sizeof(SbtRecordHeader) * numHeaders, m_cudaStream) ); + + // Hit groups for radiance and shadow rays. These will be initialized later per instance. + // This just provides the headers with the program group indices. + + // Note that the SBT record data field is uninitialized after these! + // These are stored to be able to initialize the SBT hitGroup with the respective opaque and cutout shaders. + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_RADIANCE], &m_sbtRecordHitRadiance) ); + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_SHADOW], &m_sbtRecordHitShadow) ); + + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_RADIANCE_CUTOUT], &m_sbtRecordHitRadianceCutout) ); + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_SHADOW_CUTOUT], &m_sbtRecordHitShadowCutout) ); + + // Setup the OptixShaderBindingTable. + + m_sbt.raygenRecord = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_RAYGENERATION; + + m_sbt.exceptionRecord = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_EXCEPTION; + + m_sbt.missRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_MISS_RADIANCE; + m_sbt.missRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + m_sbt.missRecordCount = NUM_RAYTYPES; + + // These are going to be setup after the RenderGraph has been built! + //m_sbt.hitgroupRecordBase = reinterpret_cast(m_d_sbtRecordGeometryInstanceData); + //m_sbt.hitgroupRecordStrideInBytes = (unsigned int) sizeof(SbtRecordGeometryInstanceData); + //m_sbt.hitgroupRecordCount = NUM_RAYTYPES * numInstances; + + m_sbt.callablesRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * FIRST_DIRECT_CALLABLE_ID; + m_sbt.callablesRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + m_sbt.callablesRecordCount = LAST_DIRECT_CALLABLE_ID - FIRST_DIRECT_CALLABLE_ID + 1; + + // After all required optixSbtRecordPackHeader, optixProgramGroupGetStackSize, and optixPipelineCreate + // calls have been done, the OptixProgramGroup and OptixModule objects can be destroyed. + for (auto pg: programGroups) + { + OPTIX_CHECK( m_api.optixProgramGroupDestroy(pg) ); + } + + for (auto m : modules) + { + OPTIX_CHECK(m_api.optixModuleDestroy(m)); + } +} + + +void Device::initCameras(const std::vector& cameras) +{ + // DAR FIXME PERF For simplicity, the public Device functions make sure to set the CUDA context and wait for the previous operation to finish. + // Faster would be to do that only when needed, which means the caller would be responsible to do the proper synchronization, + // while the functions themselves work as asynchronously as possible. + activateContext(); + synchronizeStream(); + + const int numCameras = static_cast(cameras.size()); + MY_ASSERT(0 < numCameras); // There must be at least one camera defintion or the lens shaders won't work. + + // The default initialization of numCameras is 0. + if (m_systemData.numCameras != numCameras) + { + memFree(reinterpret_cast(m_systemData.cameraDefinitions)); + m_systemData.cameraDefinitions = reinterpret_cast(memAlloc(sizeof(CameraDefinition) * numCameras, 16)); + } + + // Update the camera data. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.cameraDefinitions), cameras.data(), sizeof(CameraDefinition) * numCameras, m_cudaStream) ); + m_systemData.numCameras = numCameras; + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::initLights(const std::vector& lights) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT((sizeof(LightDefinition) & 15) == 0); // Verify float4 alignment. + + const int numLights = static_cast(lights.size()); // This is allowed to be zero. + + // The default initialization of m_systemData.numLights is 0. + if (m_systemData.numLights != numLights) + { + memFree(reinterpret_cast(m_systemData.lightDefinitions)); + m_systemData.lightDefinitions = nullptr; + + m_systemData.lightDefinitions = (0 < numLights) ? reinterpret_cast(memAlloc(sizeof(LightDefinition) * numLights, 16)) : nullptr; + } + + if (0 < numLights) + { + // DAR FIXME Move this from global launch parameters to the LightDefinition. + if (m_miss == 2) + { + std::map::const_iterator it = m_mapTextures.find(std::string("environment")); + if (it != m_mapTextures.end()) + { + const Texture* env = it->second; + + m_systemData.envTexture = env->getTextureObject(); + m_systemData.envCDF_U = reinterpret_cast(env->getCDF_U()); + m_systemData.envCDF_V = reinterpret_cast(env->getCDF_V()); + m_systemData.envWidth = env->getWidth(); + m_systemData.envHeight = env->getHeight(); + m_systemData.envIntegral = env->getIntegral(); + } + } + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.lightDefinitions), lights.data(), sizeof(LightDefinition) * numLights, m_cudaStream) ); + m_systemData.numLights = numLights; + } + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::initMaterials(const std::vector& materialsGUI) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT((sizeof(MaterialDefinition) & 15) == 0); // Verify float4 structure size for proper array element alignment. + + const int numMaterials = static_cast(materialsGUI.size()); + MY_ASSERT(0 < numMaterials); // There must be at least one material or the hit shaders won't work. + + // The default initialization of m_systemData.numMaterials is 0. + if (m_systemData.numMaterials != numMaterials) // FIXME Could grow only with additional capacity tracker. + { + memFree(reinterpret_cast(m_systemData.materialDefinitions)); + m_systemData.materialDefinitions = reinterpret_cast(memAlloc(sizeof(MaterialDefinition) * numMaterials, 16)); + + m_materials.resize(numMaterials); + } + + for (int i = 0; i < numMaterials; ++i) + { + const MaterialGUI& materialGUI = materialsGUI[i]; // Material UI data in the host. + MaterialDefinition& material = m_materials[i]; // MaterialDefinition data on the host in device layout. + + material.textureAlbedo = 0; + if (!materialGUI.nameTextureAlbedo.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureAlbedo); + MY_ASSERT(it != m_mapTextures.end()); + material.textureAlbedo = it->second->getTextureObject(); + } + + material.textureCutout = 0; + if (!materialGUI.nameTextureCutout.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureCutout); + MY_ASSERT(it != m_mapTextures.end()); + material.textureCutout = it->second->getTextureObject(); + } + + material.roughness = materialGUI.roughness; + material.indexBSDF = materialGUI.indexBSDF; + material.albedo = materialGUI.albedo; + material.absorption = make_float3(0.0f); // Null coefficient means no absorption active. + if (0.0f < materialGUI.absorptionScale) + { + // Calculate the effective absorption coefficient from the GUI parameters. + // The absorption coefficient components must all be > 0.0f if absorptionScale > 0.0f. + // Prevent logf(0.0f) which results in infinity. + const float x = -logf(fmax(0.0001f, materialGUI.absorptionColor.x)); + const float y = -logf(fmax(0.0001f, materialGUI.absorptionColor.y)); + const float z = -logf(fmax(0.0001f, materialGUI.absorptionColor.z)); + material.absorption = make_float3(x, y, z) * materialGUI.absorptionScale; + //std::cout << "absorption = (" << material.absorption.x << ", " << material.absorption.y << ", " << material.absorption.z << ")\n"; // DEBUG + } + material.ior = materialGUI.ior; + material.flags = (materialGUI.thinwalled) ? FLAG_THINWALLED : 0; + } + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.materialDefinitions), m_materials.data(), sizeof(MaterialDefinition) * numMaterials, m_cudaStream) ); + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::updateCamera(const int idCamera, const CameraDefinition& camera) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idCamera < m_systemData.numCameras); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.cameraDefinitions[idCamera]), &camera, sizeof(CameraDefinition), m_cudaStream) ); +} + +void Device::updateLight(const int idLight, const LightDefinition& light) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idLight < m_systemData.numLights); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.lightDefinitions[idLight]), &light, sizeof(LightDefinition), m_cudaStream) ); +} + +void Device::updateMaterial(const int idMaterial, const MaterialGUI& materialGUI) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idMaterial < m_materials.size()); + MaterialDefinition& material = m_materials[idMaterial]; // MaterialDefinition on the host in device layout. + + material.textureAlbedo = 0; + if (!materialGUI.nameTextureAlbedo.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureAlbedo); + MY_ASSERT(it != m_mapTextures.end()); + material.textureAlbedo = it->second->getTextureObject(); + } + + // The material system in this renderer does not support switching cutout opacity at runtime. + // It's defined by the presence of the "cutoutTexture" filename in the material parameters. + material.textureCutout = 0; + if (!materialGUI.nameTextureCutout.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureCutout); + MY_ASSERT(it != m_mapTextures.end()); + material.textureCutout = it->second->getTextureObject(); + } + + material.roughness = materialGUI.roughness; + material.indexBSDF = materialGUI.indexBSDF; + material.albedo = materialGUI.albedo; + material.absorption = make_float3(0.0f); // Null coefficient means no absorption active. + if (0.0f < materialGUI.absorptionScale) + { + // Calculate the effective absorption coefficient from the GUI parameters. + // The absorption coefficient components must all be > 0.0f if absoprionScale > 0.0f. + // Prevent logf(0.0f) which results in infinity. + const float x = -logf(fmax(0.0001f, materialGUI.absorptionColor.x)); + const float y = -logf(fmax(0.0001f, materialGUI.absorptionColor.y)); + const float z = -logf(fmax(0.0001f, materialGUI.absorptionColor.z)); + material.absorption = make_float3(x, y, z) * materialGUI.absorptionScale; + //std::cout << "absorption = (" << material.absorption.x << ", " << material.absorption.y << ", " << material.absorption.z << ")\n"; // DEBUG + } + material.ior = materialGUI.ior; + material.flags = (materialGUI.thinwalled) ? FLAG_THINWALLED : 0; + + // Copy only the one changed material. No need to trigger an update of the system data, because the m_systemData.materialDefinitions pointer itself didn't change. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.materialDefinitions[idMaterial]), &material, sizeof(MaterialDefinition), m_cudaStream) ); +} + + +static int2 calculateTileShift(const int2 tileSize) +{ + int xShift = 0; + while (xShift < 32 && (tileSize.x & (1 << xShift)) == 0) + { + ++xShift; + } + + int yShift = 0; + while (yShift < 32 && (tileSize.y & (1 << yShift)) == 0) + { + ++yShift; + } + + MY_ASSERT(xShift < 32 && yShift < 32); // Can only happen for zero input. + + return make_int2(xShift, yShift); +} + + +void Device::setState(const DeviceState& state) +{ + activateContext(); + synchronizeStream(); + + // Special handling from the previous DeviceMultiGPULocalCopy class. + if (m_systemData.resolution != state.resolution || + m_systemData.tileSize != state.tileSize) + { + // Calculate the new launch width for the tiled rendering. + // It must be a multiple of the tileSize width, otherwise the right-most tiles will not get filled correctly. + const int width = (state.resolution.x + m_count - 1) / m_count; + const int mask = state.tileSize.x - 1; + m_launchWidth = (width + mask) & ~mask; // == ((width + (tileSize - 1)) / tileSize.x) * tileSize.x; + } + + if (m_systemData.resolution != state.resolution) + { + m_systemData.resolution = state.resolution; + + m_isDirtyOutputBuffer = true; + m_isDirtySystemData = true; + } + + if (m_systemData.tileSize != state.tileSize) + { + m_systemData.tileSize = state.tileSize; + m_systemData.tileShift = calculateTileShift(m_systemData.tileSize); + m_isDirtySystemData = true; + } + + if (m_systemData.samplesSqrt != state.samplesSqrt) + { + m_systemData.samplesSqrt = state.samplesSqrt; + m_isDirtySystemData = true; + } + + if (m_systemData.lensShader != state.lensShader) + { + m_systemData.lensShader = state.lensShader; + m_isDirtySystemData = true; + } + + if (m_systemData.pathLengths != state.pathLengths) + { + m_systemData.pathLengths = state.pathLengths; + m_isDirtySystemData = true; + } + + if (m_systemData.sceneEpsilon != state.epsilonFactor * SCENE_EPSILON_SCALE) + { + m_systemData.sceneEpsilon = state.epsilonFactor * SCENE_EPSILON_SCALE; + m_isDirtySystemData = true; + } + + if (m_systemData.envRotation != state.envRotation) + { + // FIXME Implement free rotation with a rotation matrix. + m_systemData.envRotation = state.envRotation; + m_isDirtySystemData = true; + } + +#if USE_TIME_VIEW + if (m_systemData.clockScale != state.clockFactor * CLOCK_FACTOR_SCALE) + { + m_systemData.clockScale = state.clockFactor * CLOCK_FACTOR_SCALE; + m_isDirtySystemData = true; + } +#endif +} + + +GeometryData Device::createGeometry(std::shared_ptr geometry) +{ + activateContext(); + synchronizeStream(); + + GeometryData data; + + data.primitiveType = PT_TRIANGLES; + data.owner = m_index; + + const std::vector& attributes = geometry->getAttributes(); + const std::vector& indices = geometry->getIndices(); + + const size_t attributesSizeInBytes = sizeof(TriangleAttributes) * attributes.size(); + const size_t indicesSizeInBytes = sizeof(unsigned int) * indices.size(); + + data.d_attributes = memAlloc(attributesSizeInBytes, 16); + data.d_indices = memAlloc(indicesSizeInBytes, sizeof(unsigned int)); + + data.numAttributes = attributes.size(); + data.numIndices = indices.size(); + + CU_CHECK( cuMemcpyHtoDAsync(data.d_attributes, attributes.data(), attributesSizeInBytes, m_cudaStream) ); + CU_CHECK( cuMemcpyHtoDAsync(data.d_indices, indices.data(), indicesSizeInBytes, m_cudaStream) ); + + OptixBuildInput buildInput = {}; + + buildInput.type = OPTIX_BUILD_INPUT_TYPE_TRIANGLES; + + buildInput.triangleArray.vertexFormat = OPTIX_VERTEX_FORMAT_FLOAT3; + buildInput.triangleArray.vertexStrideInBytes = sizeof(TriangleAttributes); + buildInput.triangleArray.numVertices = static_cast(attributes.size()); + buildInput.triangleArray.vertexBuffers = &data.d_attributes; + + buildInput.triangleArray.indexFormat = OPTIX_INDICES_FORMAT_UNSIGNED_INT3; + buildInput.triangleArray.indexStrideInBytes = sizeof(unsigned int) * 3; + + buildInput.triangleArray.numIndexTriplets = static_cast(indices.size()) / 3; + buildInput.triangleArray.indexBuffer = data.d_indices; + + unsigned int inputFlags[1] = { OPTIX_GEOMETRY_FLAG_NONE }; + + buildInput.triangleArray.flags = inputFlags; + buildInput.triangleArray.numSbtRecords = 1; + + OptixAccelBuildOptions accelBuildOptions = {}; + + // Note that OPTIX_BUILD_FLAG_PREFER_FAST_TRACE will use more memeory, which performs worse when sharing across the NVLINK bridge which is much slower than VRAM accesses. + accelBuildOptions.buildFlags = /* OPTIX_BUILD_FLAG_PREFER_FAST_TRACE | */ OPTIX_BUILD_FLAG_ALLOW_COMPACTION; + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &buildInput, 1, &accelBufferSizes) ); + + data.d_gas = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); // This is a temporary buffer. The Compaction will be the static one! + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OptixAccelEmitDesc accelEmit = {}; + + accelEmit.result = memAlloc(sizeof(size_t), sizeof(size_t), cuda::USAGE_TEMP); + accelEmit.type = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE; + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &buildInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + data.d_gas, accelBufferSizes.outputSizeInBytes, + &data.traversable, &accelEmit, 1) ); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); + + size_t sizeCompact; + + CU_CHECK( cuMemcpyDtoH(&sizeCompact, accelEmit.result, sizeof(size_t)) ); // Synchronous. + + memFree(accelEmit.result); + memFree(d_tmp); + + // Compact the AS only when possible. This can save more than half the memory on RTX boards. + if (sizeCompact < accelBufferSizes.outputSizeInBytes) + { + CUdeviceptr d_gasCompact = memAlloc(sizeCompact, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); // This is the static GAS allocation! + + OPTIX_CHECK( m_api.optixAccelCompact(m_optixContext, m_cudaStream, data.traversable, d_gasCompact, sizeCompact, &data.traversable) ); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); // Must finish accessing data.d_gas source before it can be freed and overridden. + + memFree(data.d_gas); + + data.d_gas = d_gasCompact; + + //std::cout << "Compaction saved " << accelBufferSizes.outputSizeInBytes - sizeCompact << '\n'; // DEBUG + accelBufferSizes.outputSizeInBytes = sizeCompact; // DEBUG for the std::cout below. + } + + // Return the relocation info for this GAS traversable handle from this device's OptiX context. + // It's used to assert that the GAS is compatible across devices which means NVLINK peer-to-peer sharing is allowed. + // (This is more meant as example code, because in NVLINK islands the GPU configuration must be homogeneous and addresses are unique with UVA.) + OPTIX_CHECK( m_api.optixAccelGetRelocationInfo(m_optixContext, data.traversable, &data.info) ); + + //std::cout << "createGeometry() device ordinal = " << m_ordinal << ": attributes = " << attributesSizeInBytes << ", indices = " << indicesSizeInBytes << ", GAS = " << accelBufferSizes.outputSizeInBytes << "\n"; // DEBUG + + return data; +} + +void Device::destroyGeometry(GeometryData& data) +{ + memFree(data.d_gas); + memFree(data.d_indices); + memFree(data.d_attributes); +} + +void Device::createInstance(const GeometryData& geometryData, const InstanceData& instanceData, const float matrix[12]) +{ + activateContext(); + synchronizeStream(); + + // If the GeometryData is owned by a different device, that means it has been created in a different OptiX context. + // Then check if the data is compatible with the OptiX context on this device. + // If yes, it can be shared via peer-to-peer as well because the device pointers are all unique with UVA. It's not actually relocated. + // If not, no instance with this geometry data is created, which means there will be rendering corruption on this device. + if (m_index != geometryData.owner) // No need to check compatibility on the same device. + { + int compatible = 0; + +#if (OPTIX_VERSION >= 70600) + OPTIX_CHECK( m_api.optixCheckRelocationCompatibility(m_optixContext, &geometryData.info, &compatible) ); +#else + OPTIX_CHECK( m_api.optixAccelCheckRelocationCompatibility(m_optixContext, &geometryData.info, &compatible) ); +#endif + + if (compatible == 0) + { + std::cerr << "ERROR: createInstance() device index " << m_index << " is not AS-compatible with the GeometryData owner " << geometryData.owner << '\n'; + MY_ASSERT(!"createInstance() AS incompatible"); + return; // This means this geometry will not actually be present in the OptiX render graph of this device! + } + } + + MY_ASSERT(0 <= instanceData.idMaterial); + + OptixInstance instance = {}; + + const unsigned int id = static_cast(m_instances.size()); + memcpy(instance.transform, matrix, sizeof(float) * 12); + instance.instanceId = id; // User defined instance index, queried with optixGetInstanceId(). + instance.visibilityMask = 255; + instance.sbtOffset = id * NUM_RAYTYPES; // This controls the SBT instance offset! This must be set explicitly when each instance is using a separate BLAS. + instance.flags = OPTIX_INSTANCE_FLAG_NONE; + instance.traversableHandle = geometryData.traversable; // Shared! + + m_instances.push_back(instance); // OptixInstance data + m_instanceData.push_back(instanceData); // SBT record data: idGeometry, idMaterial, idLight +} + + +void Device::createTLAS() +{ + activateContext(); + synchronizeStream(); + + // Construct the TLAS by attaching all flattened instances. + const size_t instancesSizeInBytes = sizeof(OptixInstance) * m_instances.size(); + + CUdeviceptr d_instances = memAlloc(instancesSizeInBytes, OPTIX_INSTANCE_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + CU_CHECK( cuMemcpyHtoDAsync(d_instances, m_instances.data(), instancesSizeInBytes, m_cudaStream) ); + + OptixBuildInput instanceInput = {}; + + instanceInput.type = OPTIX_BUILD_INPUT_TYPE_INSTANCES; + instanceInput.instanceArray.instances = d_instances; + instanceInput.instanceArray.numInstances = static_cast(m_instances.size()); + + OptixAccelBuildOptions accelBuildOptions = {}; + + accelBuildOptions.buildFlags = OPTIX_BUILD_FLAG_NONE; + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &instanceInput, 1, &accelBufferSizes ) ); + + m_d_ias = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &instanceInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + m_d_ias, accelBufferSizes.outputSizeInBytes, + &m_systemData.topObject, nullptr, 0)); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); + + memFree(d_tmp); + memFree(d_instances); +} + + +void Device::createHitGroupRecords(const std::vector& geometryData, const unsigned int stride, const unsigned int index) +{ + activateContext(); + synchronizeStream(); + + const unsigned int numInstances = static_cast(m_instances.size()); + + m_sbtRecordGeometryInstanceData.resize(NUM_RAYTYPES * numInstances); + + for (unsigned int i = 0; i < numInstances; ++i) + { + const InstanceData& inst = m_instanceData[i]; + const GeometryData& geom = geometryData[inst.idGeometry * stride + index]; // GeometryData is per island only and shared among all devices in one island. + + const int idx = i * NUM_RAYTYPES; // idx == radiance ray, idx + 1 == shadow ray + + switch (geom.primitiveType) + { + case PT_UNKNOWN: // This is a fatal error and the launch will fail! + default: + std::cerr << "ERROR: createHitGroupRecords() GeometryData.primitiveType unknown.\n"; + MY_ASSERT(!"GeometryData.primitiveType unknown"); + break; + + case PT_TRIANGLES: + if (m_materials[inst.idMaterial].textureCutout == 0) + { + // Only update the header to switch the program hit group. The SBT record inst field doesn't change. + // FIXME Just remember the pointer and have one memcpy() at the end. + memcpy(m_sbtRecordGeometryInstanceData[idx ].header, m_sbtRecordHitRadiance.header, OPTIX_SBT_RECORD_HEADER_SIZE); + memcpy(m_sbtRecordGeometryInstanceData[idx + 1].header, m_sbtRecordHitShadow.header, OPTIX_SBT_RECORD_HEADER_SIZE); + } + else + { + memcpy(m_sbtRecordGeometryInstanceData[idx ].header, m_sbtRecordHitRadianceCutout.header, OPTIX_SBT_RECORD_HEADER_SIZE); + memcpy(m_sbtRecordGeometryInstanceData[idx + 1].header, m_sbtRecordHitShadowCutout.header, OPTIX_SBT_RECORD_HEADER_SIZE); + } + break; + } + + m_sbtRecordGeometryInstanceData[idx ].data.attributes = geom.d_attributes; + m_sbtRecordGeometryInstanceData[idx ].data.indices = geom.d_indices; + m_sbtRecordGeometryInstanceData[idx ].data.idMaterial = inst.idMaterial; + m_sbtRecordGeometryInstanceData[idx ].data.idLight = inst.idLight; + + m_sbtRecordGeometryInstanceData[idx + 1].data.attributes = geom.d_attributes; + m_sbtRecordGeometryInstanceData[idx + 1].data.indices = geom.d_indices; + m_sbtRecordGeometryInstanceData[idx + 1].data.idMaterial = inst.idMaterial; + m_sbtRecordGeometryInstanceData[idx + 1].data.idLight = inst.idLight; + } + + m_d_sbtRecordGeometryInstanceData = reinterpret_cast(memAlloc(sizeof(SbtRecordGeometryInstanceData) * NUM_RAYTYPES * numInstances, OPTIX_SBT_RECORD_ALIGNMENT) ); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_d_sbtRecordGeometryInstanceData), m_sbtRecordGeometryInstanceData.data(), sizeof(SbtRecordGeometryInstanceData) * NUM_RAYTYPES * numInstances, m_cudaStream) ); + + m_sbt.hitgroupRecordBase = reinterpret_cast(m_d_sbtRecordGeometryInstanceData); + m_sbt.hitgroupRecordStrideInBytes = (unsigned int) sizeof(SbtRecordGeometryInstanceData); + m_sbt.hitgroupRecordCount = NUM_RAYTYPES * numInstances; +} + +// Given an OpenGL UUID find the matching CUDA device. +bool Device::matchUUID(const char* uuid) +{ + for (size_t i = 0; i < 16; ++i) + { + if (m_deviceUUID.bytes[i] != uuid[i]) + { + return false; + } + } + return true; +} + +// Given an OpenGL LUID find the matching CUDA device. +bool Device::matchLUID(const char* luid, const unsigned int nodeMask) +{ + if ((m_nodeMask & nodeMask) == 0) + { + return false; + } + for (size_t i = 0; i < 8; ++i) + { + if (m_deviceLUID[i] != luid[i]) + { + return false; + } + } + return true; +} + + +void Device::activateContext() const +{ + CU_CHECK( cuCtxSetCurrent(m_cudaContext) ); +} + +void Device::synchronizeStream() const +{ + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); +} + +void Device::render(const unsigned int iterationIndex, void** buffer) +{ + activateContext(); + + m_systemData.iterationIndex = iterationIndex; + + if (m_isDirtyOutputBuffer) + { + MY_ASSERT(buffer != nullptr); + if (*buffer == nullptr) // The buffer is nullptr for the device which should allocate the full resolution buffers. This device is called first! + { + // Only allocate the host buffer once, not per each device. + m_bufferHost.resize(m_systemData.resolution.x * m_systemData.resolution.y); + + // Note that this requires that all other devices have finished accessing this buffer, but that is automatically the case + // after calling Device::setState() which is the only place which can change the resolution. + memFree(m_systemData.outputBuffer); // This is asynchronous and the pointer can be 0. + m_systemData.outputBuffer = memAlloc(sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, sizeof(float4)); + + *buffer = reinterpret_cast(m_systemData.outputBuffer); // Set the pointer, so that other devices don't allocate it. It's not shared! + + // This is a temporary buffer on the primary board which is used by the compositor. The texelBuffer needs to stay intact for the accumulation. + memFree(m_systemData.tileBuffer); + m_systemData.tileBuffer = memAlloc(sizeof(float4) * m_launchWidth * m_systemData.resolution.y, sizeof(float4)); + + m_d_compositorData = memAlloc(sizeof(CompositorData), 16); // DAR FIXME Check alignment. Could be reduced to 8. + + m_ownsSharedBuffer = true; // Indicate which device owns the m_systemData.outputBuffer and m_bufferHost so that display routines can assert. + + if (m_cudaGraphicsResource != nullptr) // Need to unregister texture or PBO before resizing it. + { + CU_CHECK( cuGraphicsUnregisterResource(m_cudaGraphicsResource) ); + } + + switch (m_interop) + { + case INTEROP_MODE_OFF: + break; + + case INTEROP_MODE_TEX: + // Let the device which is called first resize the OpenGL texture. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, (GLvoid*) m_bufferHost.data()); // RGBA32F + glFinish(); // Synchronize with following CUDA operations. + + CU_CHECK( cuGraphicsGLRegisterImage(&m_cudaGraphicsResource, m_tex, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD) ); + break; + + case INTEROP_MODE_PBO: + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4), nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + CU_CHECK( cuGraphicsGLRegisterBuffer(&m_cudaGraphicsResource, m_pbo, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD) ); + break; + } + } + // Allocate a GPU local buffer in the per-device launch size. This is where the accumulation happens. + memFree(m_systemData.texelBuffer); + m_systemData.texelBuffer = memAlloc(sizeof(float4) * m_launchWidth * m_systemData.resolution.y, sizeof(float4)); + + m_isDirtyOutputBuffer = false; // Buffer is allocated with new size. + m_isDirtySystemData = true; // Now the sysData on the device needs to be updated, and that needs a sync! + } + + if (m_isDirtySystemData) // Update the whole SystemData block because more than the iterationIndex changed. This normally means a GUI interaction. Just sync. + { + synchronizeStream(); + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_d_systemData), &m_systemData, sizeof(SystemData), m_cudaStream) ); + m_isDirtySystemData = false; + } + else // Just copy the new iterationIndex. + { + synchronizeStream(); + + // FIXME PERF For really asynchronous copies of the iteration indices, multiple source pointers are required. Good that I know the number of iterations upfront! + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_d_systemData->iterationIndex), &m_systemData.iterationIndex, sizeof(unsigned int), m_cudaStream) ); + } + + // Note the launch width per device to render in tiles. + OPTIX_CHECK( m_api.optixLaunch(m_pipeline, m_cudaStream, reinterpret_cast(m_d_systemData), sizeof(SystemData), &m_sbt, m_launchWidth, m_systemData.resolution.y, /* depth */ 1) ); +} + + +void Device::updateDisplayTexture() +{ + activateContext(); + + // Only allow this on the device which owns the shared peer-to-peer buffer which also resized the host buffer to copy this to the host. + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer && m_tex != 0); + + switch (m_interop) + { + case INTEROP_MODE_OFF: + // Copy the GPU local render buffer into host and update the HDR texture image from there. + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); + synchronizeStream(); // Wait for the buffer to arrive on the host. + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, m_bufferHost.data()); // RGBA32F from host buffer data. + break; + + case INTEROP_MODE_TEX: + { + // Map the Texture object directly and copy the output buffer. + CU_CHECK( cuGraphicsMapResources(1, &m_cudaGraphicsResource, m_cudaStream )); // This is an implicit cuSynchronizeStream(). + + CUarray dstArray = nullptr; + + CU_CHECK( cuGraphicsSubResourceGetMappedArray(&dstArray, m_cudaGraphicsResource, 0, 0) ); // arrayIndex = 0, mipLevel = 0 + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_DEVICE; + params.srcDevice = m_systemData.outputBuffer; + params.srcPitch = m_systemData.resolution.x * sizeof(float4); + params.srcHeight = m_systemData.resolution.y; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = dstArray; + params.WidthInBytes = m_systemData.resolution.x * sizeof(float4); + params.Height = m_systemData.resolution.y; + params.Depth = 1; + + CU_CHECK( cuMemcpy3D(¶ms) ); // Copy from linear to array layout. + + CU_CHECK( cuGraphicsUnmapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). + } + break; + + case INTEROP_MODE_PBO: // This contains two device-to-device copies and is just for demonstration. Use INTEROP_MODE_TEX when possible. + { + size_t size = 0; + CUdeviceptr d_ptr; + + CU_CHECK( cuGraphicsMapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). + CU_CHECK( cuGraphicsResourceGetMappedPointer(&d_ptr, &size, m_cudaGraphicsResource) ); // The pointer can change on every map! + MY_ASSERT(m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4) <= size); + CU_CHECK( cuMemcpyDtoDAsync(d_ptr, m_systemData.outputBuffer, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4), m_cudaStream) ); // PERF PBO interop is kind of moot with a direct texture access. + CU_CHECK( cuGraphicsUnmapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_tex); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, (GLvoid*) 0); // RGBA32F from byte offset 0 in the pixel unpack buffer. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + break; + } +} + + +const void* Device::getOutputBufferHost() +{ + activateContext(); + + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer); // Only allow this on the device which owns the shared peer-to-peer buffer and resized the host buffer to copy this to the host. + + // Note that the caller takes care to sync the other devices before calling into here or this image might not be complete! + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); + + synchronizeStream(); // Wait for the buffer to arrive on the host. + + return m_bufferHost.data(); +} + +// DAR FIXME The focus of this application is multi-GPU peer-to-peer resource sharing. +// While this also works with single-GPU, it's doing an unnecessary device-to-device compositing of the already final image. +// There isn't even a need to have a tileBuffer and texelBuffer in that case. Though this is only called once a second normally. +void Device::compositor(Device* other) +{ + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer); + + // The compositor sources the tileBuffer, which is only allocated on the primary device. + // The texelBuffer is a GPU local buffer on all devices and contains the accumulation. + if (this == other) + { + activateContext(); + + CU_CHECK( cuMemcpyDtoDAsync(m_systemData.tileBuffer, m_systemData.texelBuffer, + sizeof(float4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); + } + else + { + // Make sure the other device has finished rendering! Otherwise there can be checkerboard corruption visible. + other->activateContext(); + other->synchronizeStream(); + + activateContext(); + + CU_CHECK( cuMemcpyPeerAsync(m_systemData.tileBuffer, m_cudaContext, other->m_systemData.texelBuffer, other->m_cudaContext, + sizeof(float4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); + } + + CompositorData compositorData; // DAR FIXME This needs to be persistent per Device to allow async copies! + + compositorData.outputBuffer = m_systemData.outputBuffer; + compositorData.tileBuffer = m_systemData.tileBuffer; + compositorData.resolution = m_systemData.resolution; + compositorData.tileSize = m_systemData.tileSize; + compositorData.tileShift = m_systemData.tileShift; + compositorData.launchWidth = m_launchWidth; + compositorData.deviceCount = m_systemData.deviceCount; + compositorData.deviceIndex = other->m_systemData.deviceIndex; // This is the only value which changes per device. + + // Need a synchronous copy here to not overwrite or delete the compositorData above. + CU_CHECK( cuMemcpyHtoD(m_d_compositorData, &compositorData, sizeof(CompositorData)) ); + + void* args[1] = { &m_d_compositorData }; + + const int blockDimX = std::min(compositorData.tileSize.x, 16); + const int blockDimY = std::min(compositorData.tileSize.y, 16); + + const int gridDimX = (m_launchWidth + blockDimX - 1) / blockDimX; + const int gridDimY = (compositorData.resolution.y + blockDimY - 1) / blockDimY; + + MY_ASSERT(gridDimX <= m_deviceAttribute.maxGridDimX && + gridDimY <= m_deviceAttribute.maxGridDimY); + + // Reduction kernel with launch dimension of height blocks with 32 threads. + CU_CHECK( cuLaunchKernel(m_functionCompositor, // CUfunction f, + gridDimX, // unsigned int gridDimX, + gridDimY, // unsigned int gridDimY, + 1, // unsigned int gridDimZ, + blockDimX, // unsigned int blockDimX, + blockDimY, // unsigned int blockDimY, + 1, // unsigned int blockDimZ, + 0, // unsigned int sharedMemBytes, + m_cudaStream, // CUstream hStream, + args, // void **kernelParams, + nullptr) ); // void **extra + + synchronizeStream(); +} + + +// Arena version of cuMemAlloc(), but asynchronous! +CUdeviceptr Device::memAlloc(const size_t size, const size_t alignment, const cuda::Usage usage) +{ + return m_allocator->alloc(size, alignment, usage); +} + +// Arena version of cuMemFree(), but asynchronous! +void Device::memFree(const CUdeviceptr ptr) +{ + m_allocator->free(ptr); +} + +// This is getting the current VRAM situation on the device. +// Means this includes everything running on the GPU and all allocations done for textures and the ArenaAllocator. +// (Currently not used for picking the home device for the next shared allocation, because with the ArenaAlloocator that isn't fine grained.) +size_t Device::getMemoryFree() const +{ + activateContext(); + + size_t sizeFree = 0; + size_t sizeTotal = 0; + + CU_CHECK( cuMemGetInfo(&sizeFree, &sizeTotal) ); + + return sizeFree; +} + +// getMemoryAllocated() returns the sum of all allocated blocks inside arenas and the texture sizes in bytes (without GPU alignment and padding). +// Using this in Raytracer::getDeviceHome() assumes the free VRAM amount is about equal on the devices in an island. +size_t Device::getMemoryAllocated() const +{ + return m_allocator->getSizeMemoryAllocated() + m_sizeMemoryTextureArrays; +} + +Texture* Device::initTexture(const std::string& name, const Picture* picture, const unsigned int flags) +{ + activateContext(); + synchronizeStream(); + + Texture* texture; + + // DAR FIXME Only using the filename as key and not the load flags. This will not support the same image with different flags. + std::map::const_iterator it = m_mapTextures.find(name); + if (it == m_mapTextures.end()) + { + texture = new Texture(this); // This device is the owner of the CUarray or CUmipmappedArray data. + texture->create(picture, flags); + + m_sizeMemoryTextureArrays += texture->getSizeBytes(); // Texture memory tracking. + + m_mapTextures[name] = texture; + + //std::cout << "initTexture() device ordinal = " << m_ordinal << ": name = " << name << '\n'; // DEBUG + } + else + { + texture = it->second; // Return the existing texture under this name. + + //std::cout << "initTexture() Texture " << name << " reused\n"; // DEBUG + } + + return texture; // Not used when not sharing. +} + + +void Device::shareTexture(const std::string& name, const Texture* shared) +{ + activateContext(); + synchronizeStream(); + + std::map::const_iterator it = m_mapTextures.find(name); + + if (it == m_mapTextures.end()) + { + Texture* texture = new Texture(shared->getOwner()); + + texture->create(shared); // No texture memory tracking in this case. Arrays are reused. + + m_mapTextures[name] = texture; + } +} diff --git a/apps/bench_shared/src/NVMLImpl.cpp b/apps/bench_shared/src/NVMLImpl.cpp new file mode 100644 index 00000000..3afedead --- /dev/null +++ b/apps/bench_shared/src/NVMLImpl.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +// The cfgmgr32 header is necessary for interrogating driver information in the registry. +#include +// For convenience the library is also linked in automatically using the #pragma command. +#pragma comment(lib, "Cfgmgr32.lib") +#else +#include +#endif + +#include "inc/NVMLImpl.h" + +#include +#include + +#ifdef _WIN32 + +static void *nvmlLoadFromDriverStore(const char* nvmlDllName) +{ + void* handle = NULL; + + // We are going to look for the OpenGL driver which lives next to nvoptix.dll and nvml.dll. + // 0 (null) will be returned if any errors occured. + + static const char* deviceInstanceIdentifiersGUID = "{4d36e968-e325-11ce-bfc1-08002be10318}"; + const ULONG flags = CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT; + ULONG deviceListSize = 0; + + if (CM_Get_Device_ID_List_SizeA(&deviceListSize, deviceInstanceIdentifiersGUID, flags) != CR_SUCCESS) + { + return NULL; + } + + char* deviceNames = (char*) malloc(deviceListSize); + + if (CM_Get_Device_ID_ListA(deviceInstanceIdentifiersGUID, deviceNames, deviceListSize, flags)) + { + free(deviceNames); + return NULL; + } + + DEVINST devID = 0; + + // Continue to the next device if errors are encountered. + for (char* deviceName = deviceNames; *deviceName; deviceName += strlen(deviceName) + 1) + { + if (CM_Locate_DevNodeA(&devID, deviceName, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) + { + continue; + } + + HKEY regKey = 0; + if (CM_Open_DevNode_Key(devID, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, ®Key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) + { + continue; + } + + const char* valueName = "OpenGLDriverName"; + DWORD valueSize = 0; + + LSTATUS ret = RegQueryValueExA(regKey, valueName, NULL, NULL, NULL, &valueSize); + if (ret != ERROR_SUCCESS) + { + RegCloseKey(regKey); + continue; + } + + char* regValue = (char*) malloc(valueSize); + ret = RegQueryValueExA(regKey, valueName, NULL, NULL, (LPBYTE) regValue, &valueSize); + if (ret != ERROR_SUCCESS) + { + free(regValue); + RegCloseKey(regKey); + continue; + } + + // Strip the OpenGL driver dll name from the string then create a new string with + // the path and the nvoptix.dll name + for (int i = valueSize - 1; i >= 0 && regValue[i] != '\\'; --i) + { + regValue[i] = '\0'; + } + + size_t newPathSize = strlen(regValue) + strlen(nvmlDllName) + 1; + char* dllPath = (char*) malloc(newPathSize); + strcpy(dllPath, regValue); + strcat(dllPath, nvmlDllName); + + free(regValue); + RegCloseKey(regKey); + + handle = LoadLibraryA((LPCSTR) dllPath); + free(dllPath); + + if (handle) + { + break; + } + } + + free(deviceNames); + + return handle; +} + +static void *nvmlLoadFromSystemDirectory(const char* nvmlDllName) +{ + // Get the size of the path first, then allocate. + const unsigned int size = GetSystemDirectoryA(NULL, 0); + if (size == 0) + { + // Couldn't get the system path size, so bail. + return NULL; + } + + // Alloc enough memory to concatenate with "\\nvml.dll". + const size_t pathSize = size + 1 + strlen(nvmlDllName); + + char* systemPath = (char*) malloc(pathSize); + + if (GetSystemDirectoryA(systemPath, size) != size - 1) + { + // Something went wrong. + free(systemPath); + return NULL; + } + + strcat(systemPath, "\\"); + strcat(systemPath, nvmlDllName); + + void* handle = LoadLibraryA(systemPath); + + free(systemPath); + + return handle; +} + +static void* nvmlLoadWindowsDll(void) +{ + const char* nvmlDllName = "nvml.dll"; + + void* handle = nvmlLoadFromDriverStore(nvmlDllName); + + if (!handle) + { + handle = nvmlLoadFromSystemDirectory(nvmlDllName); + // If the nvml.dll is still not found here, something is wrong with the display driver installation. + } + + return handle; +} +#endif + + +NVMLImpl::NVMLImpl() + : m_handle(0) +{ + // Fill all existing NVML function pointers with nullptr. + memset(&m_api, 0, sizeof(NVMLFunctionTable)); +} + +//NVMLImpl::~NVMLImpl() +//{ +//} + + +// Helper function to get the entry point address in a loaded library just to abstract the platform in GET_FUNC macro. +static void* getFunc(void* handle, const char* name) +{ +#ifdef _WIN32 + return GetProcAddress((HMODULE) handle, name); +#else + return dlsym(handle, name); +#endif +} + +bool NVMLImpl::initFunctionTable() +{ +#ifdef _WIN32 + void* handle = nvmlLoadWindowsDll(); + if (!handle) + { + std::cerr << "nvml.dll not found\n"; + return false; + } +#else + void* handle = dlopen("libnvidia-ml.so.1", RTLD_NOW); + if (!handle) + { + std::cerr << "libnvidia-ml.so.1 not found\n"; + return false; + } +#endif + +// Local macro to get the NVML entry point addresses and assign them to the NVMLFunctionTable members with the right type. +// Some of the NVML functions are versioned by a #define adding a version suffix (_v2, _v3) to the name, +// which requires a set of two macros to resolve the unversioned function name to the versioned one. + +#define GET_FUNC_V(name) \ +{ \ + const void* func = getFunc(handle, #name); \ + if (func) { \ + m_api.name = reinterpret_cast(func); \ + } else { \ + std::cerr << "ERROR: " << #name << " is nullptr\n"; \ + success = false; \ + } \ +} + +#define GET_FUNC(name) GET_FUNC_V(name) + + + bool success = true; + + GET_FUNC(nvmlInit); + //GET_FUNC(nvmlInitWithFlags); + GET_FUNC(nvmlShutdown); + //GET_FUNC(nvmlErrorString); + //GET_FUNC(nvmlSystemGetDriverVersion); + //GET_FUNC(nvmlSystemGetNVMLVersion); + //GET_FUNC(nvmlSystemGetCudaDriverVersion); + //GET_FUNC(nvmlSystemGetCudaDriverVersion_v2); + //GET_FUNC(nvmlSystemGetProcessName); + //GET_FUNC(nvmlUnitGetCount); + //GET_FUNC(nvmlUnitGetHandleByIndex); + //GET_FUNC(nvmlUnitGetUnitInfo); + //GET_FUNC(nvmlUnitGetLedState); + //GET_FUNC(nvmlUnitGetPsuInfo); + //GET_FUNC(nvmlUnitGetTemperature); + //GET_FUNC(nvmlUnitGetFanSpeedInfo); + //GET_FUNC(nvmlUnitGetDevices); + //GET_FUNC(nvmlSystemGetHicVersion); + //GET_FUNC(nvmlDeviceGetCount); + //GET_FUNC(nvmlDeviceGetAttributes); + //GET_FUNC(nvmlDeviceGetHandleByIndex); + //GET_FUNC(nvmlDeviceGetHandleBySerial); + //GET_FUNC(nvmlDeviceGetHandleByUUID); + GET_FUNC(nvmlDeviceGetHandleByPciBusId); + //GET_FUNC(nvmlDeviceGetName); + //GET_FUNC(nvmlDeviceGetBrand); + //GET_FUNC(nvmlDeviceGetIndex); + //GET_FUNC(nvmlDeviceGetSerial); + //GET_FUNC(nvmlDeviceGetMemoryAffinity); + //GET_FUNC(nvmlDeviceGetCpuAffinityWithinScope); + //GET_FUNC(nvmlDeviceGetCpuAffinity); + //GET_FUNC(nvmlDeviceSetCpuAffinity); + //GET_FUNC(nvmlDeviceClearCpuAffinity); + //GET_FUNC(nvmlDeviceGetTopologyCommonAncestor); + //GET_FUNC(nvmlDeviceGetTopologyNearestGpus); + //GET_FUNC(nvmlSystemGetTopologyGpuSet); + //GET_FUNC(nvmlDeviceGetP2PStatus); + //GET_FUNC(nvmlDeviceGetUUID); + //GET_FUNC(nvmlVgpuInstanceGetMdevUUID); + //GET_FUNC(nvmlDeviceGetMinorNumber); + //GET_FUNC(nvmlDeviceGetBoardPartNumber); + //GET_FUNC(nvmlDeviceGetInforomVersion); + //GET_FUNC(nvmlDeviceGetInforomImageVersion); + //GET_FUNC(nvmlDeviceGetInforomConfigurationChecksum); + //GET_FUNC(nvmlDeviceValidateInforom); + //GET_FUNC(nvmlDeviceGetDisplayMode); + //GET_FUNC(nvmlDeviceGetDisplayActive); + //GET_FUNC(nvmlDeviceGetPersistenceMode); + //GET_FUNC(nvmlDeviceGetPciInfo); + //GET_FUNC(nvmlDeviceGetMaxPcieLinkGeneration); + //GET_FUNC(nvmlDeviceGetMaxPcieLinkWidth); + //GET_FUNC(nvmlDeviceGetCurrPcieLinkGeneration); + //GET_FUNC(nvmlDeviceGetCurrPcieLinkWidth); + //GET_FUNC(nvmlDeviceGetPcieThroughput); + //GET_FUNC(nvmlDeviceGetPcieReplayCounter); + //GET_FUNC(nvmlDeviceGetClockInfo); + //GET_FUNC(nvmlDeviceGetMaxClockInfo); + //GET_FUNC(nvmlDeviceGetApplicationsClock); + //GET_FUNC(nvmlDeviceGetDefaultApplicationsClock); + //GET_FUNC(nvmlDeviceResetApplicationsClocks); + //GET_FUNC(nvmlDeviceGetClock); + //GET_FUNC(nvmlDeviceGetMaxCustomerBoostClock); + //GET_FUNC(nvmlDeviceGetSupportedMemoryClocks); + //GET_FUNC(nvmlDeviceGetSupportedGraphicsClocks); + //GET_FUNC(nvmlDeviceGetAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceSetAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceSetDefaultAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceGetFanSpeed); + //GET_FUNC(nvmlDeviceGetFanSpeed_v2); + //GET_FUNC(nvmlDeviceGetTemperature); + //GET_FUNC(nvmlDeviceGetTemperatureThreshold); + //GET_FUNC(nvmlDeviceGetPerformanceState); + //GET_FUNC(nvmlDeviceGetCurrentClocksThrottleReasons); + //GET_FUNC(nvmlDeviceGetSupportedClocksThrottleReasons); + //GET_FUNC(nvmlDeviceGetPowerState); + //GET_FUNC(nvmlDeviceGetPowerManagementMode); + //GET_FUNC(nvmlDeviceGetPowerManagementLimit); + //GET_FUNC(nvmlDeviceGetPowerManagementLimitConstraints); + //GET_FUNC(nvmlDeviceGetPowerManagementDefaultLimit); + //GET_FUNC(nvmlDeviceGetPowerUsage); + //GET_FUNC(nvmlDeviceGetTotalEnergyConsumption); + //GET_FUNC(nvmlDeviceGetEnforcedPowerLimit); + //GET_FUNC(nvmlDeviceGetGpuOperationMode); + //GET_FUNC(nvmlDeviceGetMemoryInfo); + //GET_FUNC(nvmlDeviceGetComputeMode); + //GET_FUNC(nvmlDeviceGetCudaComputeCapability); + //GET_FUNC(nvmlDeviceGetEccMode); + //GET_FUNC(nvmlDeviceGetBoardId); + //GET_FUNC(nvmlDeviceGetMultiGpuBoard); + //GET_FUNC(nvmlDeviceGetTotalEccErrors); + //GET_FUNC(nvmlDeviceGetDetailedEccErrors); + //GET_FUNC(nvmlDeviceGetMemoryErrorCounter); + //GET_FUNC(nvmlDeviceGetUtilizationRates); + //GET_FUNC(nvmlDeviceGetEncoderUtilization); + //GET_FUNC(nvmlDeviceGetEncoderCapacity); + //GET_FUNC(nvmlDeviceGetEncoderStats); + //GET_FUNC(nvmlDeviceGetEncoderSessions); + //GET_FUNC(nvmlDeviceGetDecoderUtilization); + //GET_FUNC(nvmlDeviceGetFBCStats); + //GET_FUNC(nvmlDeviceGetFBCSessions); + //GET_FUNC(nvmlDeviceGetDriverModel); + //GET_FUNC(nvmlDeviceGetVbiosVersion); + //GET_FUNC(nvmlDeviceGetBridgeChipInfo); + //GET_FUNC(nvmlDeviceGetComputeRunningProcesses); + //GET_FUNC(nvmlDeviceGetGraphicsRunningProcesses); + //GET_FUNC(nvmlDeviceOnSameBoard); + //GET_FUNC(nvmlDeviceGetAPIRestriction); + //GET_FUNC(nvmlDeviceGetSamples); + //GET_FUNC(nvmlDeviceGetBAR1MemoryInfo); + //GET_FUNC(nvmlDeviceGetViolationStatus); + //GET_FUNC(nvmlDeviceGetAccountingMode); + //GET_FUNC(nvmlDeviceGetAccountingStats); + //GET_FUNC(nvmlDeviceGetAccountingPids); + //GET_FUNC(nvmlDeviceGetAccountingBufferSize); + //GET_FUNC(nvmlDeviceGetRetiredPages); + //GET_FUNC(nvmlDeviceGetRetiredPages_v2); + //GET_FUNC(nvmlDeviceGetRetiredPagesPendingStatus); + //GET_FUNC(nvmlDeviceGetRemappedRows); + //GET_FUNC(nvmlDeviceGetArchitecture); + //GET_FUNC(nvmlUnitSetLedState); + //GET_FUNC(nvmlDeviceSetPersistenceMode); + //GET_FUNC(nvmlDeviceSetComputeMode); + //GET_FUNC(nvmlDeviceSetEccMode); + //GET_FUNC(nvmlDeviceClearEccErrorCounts); + //GET_FUNC(nvmlDeviceSetDriverModel); + //GET_FUNC(nvmlDeviceSetGpuLockedClocks); + //GET_FUNC(nvmlDeviceResetGpuLockedClocks); + //GET_FUNC(nvmlDeviceSetApplicationsClocks); + //GET_FUNC(nvmlDeviceSetPowerManagementLimit); + //GET_FUNC(nvmlDeviceSetGpuOperationMode); + //GET_FUNC(nvmlDeviceSetAPIRestriction); + //GET_FUNC(nvmlDeviceSetAccountingMode); + //GET_FUNC(nvmlDeviceClearAccountingPids); + GET_FUNC(nvmlDeviceGetNvLinkState); + //GET_FUNC(nvmlDeviceGetNvLinkVersion); + GET_FUNC(nvmlDeviceGetNvLinkCapability); + GET_FUNC(nvmlDeviceGetNvLinkRemotePciInfo); + //GET_FUNC(nvmlDeviceGetNvLinkErrorCounter); + //GET_FUNC(nvmlDeviceResetNvLinkErrorCounters); + //GET_FUNC(nvmlDeviceSetNvLinkUtilizationControl); + //GET_FUNC(nvmlDeviceGetNvLinkUtilizationControl); + //GET_FUNC(nvmlDeviceGetNvLinkUtilizationCounter); + //GET_FUNC(nvmlDeviceFreezeNvLinkUtilizationCounter); + //GET_FUNC(nvmlDeviceResetNvLinkUtilizationCounter); + //GET_FUNC(nvmlEventSetCreate); + //GET_FUNC(nvmlDeviceRegisterEvents); + //GET_FUNC(nvmlDeviceGetSupportedEventTypes); + //GET_FUNC(nvmlEventSetWait); + //GET_FUNC(nvmlEventSetFree); + //GET_FUNC(nvmlDeviceModifyDrainState); + //GET_FUNC(nvmlDeviceQueryDrainState); + //GET_FUNC(nvmlDeviceRemoveGpu); + //GET_FUNC(nvmlDeviceDiscoverGpus); + //GET_FUNC(nvmlDeviceGetFieldValues); + //GET_FUNC(nvmlDeviceGetVirtualizationMode); + //GET_FUNC(nvmlDeviceGetHostVgpuMode); + //GET_FUNC(nvmlDeviceSetVirtualizationMode); + //GET_FUNC(nvmlDeviceGetGridLicensableFeatures); + //GET_FUNC(nvmlDeviceGetProcessUtilization); + //GET_FUNC(nvmlDeviceGetSupportedVgpus); + //GET_FUNC(nvmlDeviceGetCreatableVgpus); + //GET_FUNC(nvmlVgpuTypeGetClass); + //GET_FUNC(nvmlVgpuTypeGetName); + //GET_FUNC(nvmlVgpuTypeGetDeviceID); + //GET_FUNC(nvmlVgpuTypeGetFramebufferSize); + //GET_FUNC(nvmlVgpuTypeGetNumDisplayHeads); + //GET_FUNC(nvmlVgpuTypeGetResolution); + //GET_FUNC(nvmlVgpuTypeGetLicense); + //GET_FUNC(nvmlVgpuTypeGetFrameRateLimit); + //GET_FUNC(nvmlVgpuTypeGetMaxInstances); + //GET_FUNC(nvmlVgpuTypeGetMaxInstancesPerVm); + //GET_FUNC(nvmlDeviceGetActiveVgpus); + //GET_FUNC(nvmlVgpuInstanceGetVmID); + //GET_FUNC(nvmlVgpuInstanceGetUUID); + //GET_FUNC(nvmlVgpuInstanceGetVmDriverVersion); + //GET_FUNC(nvmlVgpuInstanceGetFbUsage); + //GET_FUNC(nvmlVgpuInstanceGetLicenseStatus); + //GET_FUNC(nvmlVgpuInstanceGetType); + //GET_FUNC(nvmlVgpuInstanceGetFrameRateLimit); + //GET_FUNC(nvmlVgpuInstanceGetEccMode); + //GET_FUNC(nvmlVgpuInstanceGetEncoderCapacity); + //GET_FUNC(nvmlVgpuInstanceSetEncoderCapacity); + //GET_FUNC(nvmlVgpuInstanceGetEncoderStats); + //GET_FUNC(nvmlVgpuInstanceGetEncoderSessions); + //GET_FUNC(nvmlVgpuInstanceGetFBCStats); + //GET_FUNC(nvmlVgpuInstanceGetFBCSessions); + //GET_FUNC(nvmlVgpuInstanceGetMetadata); + //GET_FUNC(nvmlDeviceGetVgpuMetadata); + //GET_FUNC(nvmlGetVgpuCompatibility); + //GET_FUNC(nvmlDeviceGetPgpuMetadataString); + //GET_FUNC(nvmlGetVgpuVersion); + //GET_FUNC(nvmlSetVgpuVersion); + //GET_FUNC(nvmlDeviceGetVgpuUtilization); + //GET_FUNC(nvmlDeviceGetVgpuProcessUtilization); + //GET_FUNC(nvmlVgpuInstanceGetAccountingMode); + //GET_FUNC(nvmlVgpuInstanceGetAccountingPids); + //GET_FUNC(nvmlVgpuInstanceGetAccountingStats); + //GET_FUNC(nvmlVgpuInstanceClearAccountingPids); + //GET_FUNC(nvmlGetBlacklistDeviceCount); + //GET_FUNC(nvmlGetBlacklistDeviceInfoByIndex); + //GET_FUNC(nvmlDeviceSetMigMode); + //GET_FUNC(nvmlDeviceGetMigMode); + //GET_FUNC(nvmlDeviceGetGpuInstanceProfileInfo); + //GET_FUNC(nvmlDeviceGetGpuInstancePossiblePlacements); + //GET_FUNC(nvmlDeviceGetGpuInstanceRemainingCapacity); + //GET_FUNC(nvmlDeviceCreateGpuInstance); + //GET_FUNC(nvmlGpuInstanceDestroy); + //GET_FUNC(nvmlDeviceGetGpuInstances); + //GET_FUNC(nvmlDeviceGetGpuInstanceById); + //GET_FUNC(nvmlGpuInstanceGetInfo); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceProfileInfo); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceRemainingCapacity); + //GET_FUNC(nvmlGpuInstanceCreateComputeInstance); + //GET_FUNC(nvmlComputeInstanceDestroy); + //GET_FUNC(nvmlGpuInstanceGetComputeInstances); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceById); + //GET_FUNC(nvmlComputeInstanceGetInfo); + //GET_FUNC(nvmlDeviceIsMigDeviceHandle); + //GET_FUNC(nvmlDeviceGetGpuInstanceId); + //GET_FUNC(nvmlDeviceGetComputeInstanceId); + //GET_FUNC(nvmlDeviceGetMaxMigDeviceCount); + //GET_FUNC(nvmlDeviceGetMigDeviceHandleByIndex); + //GET_FUNC(nvmlDeviceGetDeviceHandleFromMigDeviceHandle); + + return success; +} diff --git a/apps/bench_shared/src/Options.cpp b/apps/bench_shared/src/Options.cpp new file mode 100644 index 00000000..d0ff4716 --- /dev/null +++ b/apps/bench_shared/src/Options.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Options.h" + +#include + +Options::Options() +: m_width(512) +, m_height(512) +, m_mode(0) +, m_optimize(false) +{ +} + +//Options::~Options() +//{ +//} + +bool Options::parseCommandLine(int argc, char *argv[]) +{ + for (int i = 1; i < argc; ++i) + { + const std::string arg(argv[i]); + + if (arg == "?" || arg == "help" || arg == "--help") + { + printUsage(std::string(argv[0])); // Application name. + return false; + } + else if (arg == "-w" || arg == "--width") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_width = atoi(argv[++i]); + } + else if (arg == "-h" || arg == "--height") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_height = atoi(argv[++i]); + } + else if (arg == "-m" || arg == "--mode") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_mode = atoi(argv[++i]); + } + else if (arg == "-o" || arg == "--optimize") + { + m_optimize = true; + } + else if (arg == "-s" || arg == "--system") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_filenameSystem = std::string(argv[++i]); + } + else if (arg == "-d" || arg == "--desc") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_filenameScene = std::string(argv[++i]); + } + else + { + std::cerr << "Unknown option '" << arg << "'\n"; + printUsage(argv[0]); + return false; + } + } + return true; +} + +int Options::getWidth() const +{ + return m_width; +} + +int Options::getHeight() const +{ + return m_height; +} + +int Options::getMode() const +{ + return m_mode; +} + +bool Options::getOptimize() const +{ + return m_optimize; +} + +std::string Options::getSystem() const +{ + return m_filenameSystem; +} + +std::string Options::getScene() const +{ + return m_filenameScene; +} + + +void Options::printUsage(const std::string& argv0) +{ + std::cerr << "\nUsage: " << argv0 << " [options]\n"; + std::cerr << + "App Options:\n" + " ? | help | --help Print this usage message and exit.\n" + " -w | --width Width of the client window (512) \n" + " -h | --height Height of the client window (512)\n" + " -m | --mode 0 = interactive, 1 == benchmark (0)\n" + " -o | --optimize Optimize the assimp scene graph (false)\n" + " -s | --system Filename for system options (empty).\n" + " -d | --desc Filename for scene description (empty).\n" + "App Keystrokes:\n" + " SPACE Toggles GUI display.\n"; +} diff --git a/apps/bench_shared/src/Parallelogram.cpp b/apps/bench_shared/src/Parallelogram.cpp new file mode 100644 index 00000000..86eab0ed --- /dev/null +++ b/apps/bench_shared/src/Parallelogram.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + //#include "inc/Application.h" + // + //#include + //#include + //#include + // + //#include "shaders/shader_common.h" + +#include "inc/SceneGraph.h" + +#include "shaders/vector_math.h" + + +namespace sg +{ + + // Parallelogram from footpoint position, spanned by unnormalized vectors vecU and vecV, normal is normalized and on the CCW frontface. + void Triangles::createParallelogram(const float3& position, const float3& vecU, const float3& vecV, const float3& normal) + { + m_attributes.clear(); + m_indices.clear(); + + TriangleAttributes attrib; + + // Same for all four vertices in this parallelogram. + attrib.tangent = normalize(vecU); + attrib.normal = normal; + + attrib.vertex = position; // left bottom + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = position + vecU; // right bottom + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = position + vecU + vecV; // right top + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = position + vecV; // left top + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + m_indices.push_back(0); + m_indices.push_back(1); + m_indices.push_back(2); + + m_indices.push_back(2); + m_indices.push_back(3); + m_indices.push_back(0); + } + +} // namespace sg diff --git a/apps/bench_shared/src/Parser.cpp b/apps/bench_shared/src/Parser.cpp new file mode 100644 index 00000000..750ac09e --- /dev/null +++ b/apps/bench_shared/src/Parser.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "inc/Parser.h" + +#include +#include +#include + + +Parser::Parser() +: m_index(0) +, m_line(1) +{ +} + +//Parser::~Parser() +//{ +//} + +bool Parser::load(const std::string& filename) +{ + m_source.clear(); + + std::ifstream inputStream(filename); + if (!inputStream) + { + std::cerr << "ERROR: Parser::load() failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: loadString() Failed to read file " << filename << '\n'; + return false; + } + + m_source = data.str(); + return true; +} + +ParserTokenType Parser::getNextToken(std::string& token) +{ + const static std::string whitespace = " \t"; // space, tab + const static std::string value = "+-0123456789.eE"; + const static std::string delimiter = " \t\r\n"; // space, tab, carriage return, linefeed + const static std::string newline = "\n"; + const static std::string quotation = "\""; + + token.clear(); // Make sure the returned token starts empty. + + ParserTokenType type = PTT_UNKNOWN; // This return value indicates an error. + + std::string::size_type first; + std::string::size_type last; + + bool done = false; + while (!done) + { + // Find first character which is not a whitespace. + first = m_source.find_first_not_of(whitespace, m_index); + if (first == std::string::npos) + { + token = std::string(); + type = PTT_EOF; + done = true; + continue; + } + + // The found character indicates how parsing continues. + char c = m_source[first]; + + if (c == '#') // comment until the next newline + { + // m_index = first + 1; // skip '#' // Redundant. + first = m_source.find_first_of(newline, m_index); // Skip everything until the next newline. + if (first == std::string::npos) + { + type = PTT_EOF; + done = true; + } + m_index = first + 1; // skip newline + m_line++; + } + else if (c == '\r') // carriage return 13 + { + m_index = first + 1; + } + else if (c == '\n') // newline (linefeed 10) + { + m_index = first + 1; + m_line++; + } + else if (c == '\"') // Quotation mark delimits strings (filenames or material names with spaces.) + { + ++first; // Skip beginning quotation mark. + last = m_source.find_first_of(quotation, first); // Find the ending quotation mark. Should be in the same line! + if (last == std::string::npos) // Error, no matching end quotation mark found. + { + m_index = first; // Keep scanning behind the quotation mark. + } + else + { + m_index = last + 1; // Skip the ending quotation mark. + token = m_source.substr(first, last - first); + type = PTT_STRING; + done = true; + } + } + else // anything else + { + last = m_source.find_first_of(delimiter, first); + if (last == std::string::npos) + { + last = m_source.size(); + } + m_index = last; + token = m_source.substr(first, last - first); + type = PTT_ID; // Default to general identifier. + // Check if token is only built of characters used for numbers. + // (Not perfectly parsing a floating point number but good enough for most filenames.) + if (isdigit(c) || c == '-' || c == '+' || c == '.') // Legal start characters for a floating point number. + { + last = token.find_first_not_of(value, 0); + if (last == std::string::npos) + { + type = PTT_VAL; + } + } + done = true; + } + } + + return type; +} + +std::string::size_type Parser::getSize() const +{ + return m_source.size(); +} + +std::string::size_type Parser::getIndex() const +{ + return m_index; +} + +unsigned int Parser::getLine() const +{ + return m_line; +} + + + diff --git a/apps/bench_shared/src/Picture.cpp b/apps/bench_shared/src/Picture.cpp new file mode 100644 index 00000000..15a03889 --- /dev/null +++ b/apps/bench_shared/src/Picture.cpp @@ -0,0 +1,744 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// Code in these classes is based on the ILTexLoader.h/.cpp routines inside the NVIDIA nvpro-pipeline ILTexLoader plugin: +// https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp + + +#include "inc/Picture.h" + +#include +#include +#include +#include + +#include "inc/MyAssert.h" + + +static unsigned int numberOfComponents(int format) +{ + switch (format) + { + case IL_RGB: + case IL_BGR: + return 3; + + case IL_RGBA: + case IL_BGRA: + return 4; + + case IL_LUMINANCE: + case IL_ALPHA: + return 1; + + case IL_LUMINANCE_ALPHA: + return 2; + + default: + MY_ASSERT(!"Unsupported image data format."); + return 0; + } +} + +static unsigned int sizeOfComponents(int type) +{ + switch (type) + { + case IL_BYTE: + case IL_UNSIGNED_BYTE: + return 1; + + case IL_SHORT: + case IL_UNSIGNED_SHORT: + return 2; + + case IL_INT: + case IL_UNSIGNED_INT: + case IL_FLOAT: + return 4; + + default: + MY_ASSERT(!"Unsupported image data type."); + return 0; + } +} + +Image::Image(unsigned int width, + unsigned int height, + unsigned int depth, + int format, + int type) +: m_width(width) +, m_height(height) +, m_depth(depth) +, m_format(format) +, m_type(type) +, m_pixels(nullptr) +{ + m_bpp = numberOfComponents(m_format) * sizeOfComponents(m_type); + m_bpl = m_width * m_bpp; + m_bps = m_height * m_bpl; + m_nob = m_depth * m_bps; +} + +Image::~Image() +{ + if (m_pixels != nullptr) + { + delete[] m_pixels; + m_pixels = nullptr; + } +} + +static int determineFace(int i, bool isCubemapDDS) +{ + int face = i; + + // If this is a cubemap in a DDS file, exchange the z-negative and z-positive images to match OpenGL and what's used here for OptiX. + if (isCubemapDDS) + { + if (i == 4) + { + face = 5; + } + else if (i == 5) + { + face = 4; + } + } + + return face; +} + + +Picture::Picture() +: m_flags(0) +, m_isCube(false) +{ +} + +Picture::~Picture() +{ + clearImages(); +} + +unsigned int Picture::getFlags() const +{ + return m_flags; +} + +unsigned int Picture::getNumberOfImages() const +{ + return static_cast(m_images.size()); +} + +unsigned int Picture::getNumberOfLevels(unsigned int index) const +{ + MY_ASSERT(index < m_images.size()); + return static_cast(m_images[index].size()); +} + +const Image* Picture::getImageLevel(unsigned int index, unsigned int level) const +{ + if (index < m_images.size() && level < m_images[index].size()) + { + return m_images[index][level]; + } + return nullptr; +} + +bool Picture::isCubemap() const +{ + return m_isCube; +} + +void Picture::setIsCubemap(const bool isCube) +{ + m_isCube = isCube; +} + +// Returns true if the input extents were already the smallest possible mipmap level. +static bool calculateNextExtents(unsigned int &w, unsigned int &h, unsigned int& d, const unsigned int flags) +{ + bool done = false; + + // Calculate the expected LOD image extents. + if (flags & IMAGE_FLAG_LAYER) + { + if (flags & IMAGE_FLAG_1D) + { + // 1D layered mipmapped. + done = (w == 1); + w = (1 < w) ? w >> 1 : 1; + // height is 1 + // depth is the number of layers and must not change. + } + else if (flags & (IMAGE_FLAG_2D | IMAGE_FLAG_CUBE)) + { + // 2D or cubemap layered mipmapped + done = (w == 1 && h == 1); + w = (1 < w) ? w >> 1 : 1; + h = (1 < h) ? h >> 1 : 1; + // depth is the number of layers (* 6 for cubemaps) and must not change. + } + } + else + { + // Standard mipmap chain. + done = (w == 1 && h == 1 && d == 1); + w = (1 < w) ? w >> 1 : 1; + h = (1 < h) ? h >> 1 : 1; + d = (1 < d) ? d >> 1 : 1; + } + return done; +} + + +void Picture::clearImages() +{ + for (size_t i = 0; i < m_images.size(); ++i) + { + for (size_t lod = 0; lod < m_images[i].size(); ++lod) + { + delete m_images[i][lod]; + m_images[i][lod] = nullptr; + } + m_images[i].clear(); + } + m_images.clear(); +} + + +bool Picture::load(const std::string& filename, const unsigned int flags) +{ + bool success = false; + + clearImages(); // Each load() wipes previously loaded image data. + + std::string foundFile = filename; // FIXME Search at least the current working directory. + if (foundFile.empty()) + { + std::cerr << "ERROR: Picture::load() " << filename << " not found\n"; + MY_ASSERT(!"Picture::load() File not found"); + return success; + } + + m_flags = flags; // Track the flags with which this picture was loaded. + + std::string ext; + std::string::size_type last = filename.find_last_of('.'); + if (last != std::string::npos) + { + ext = filename.substr(last, std::string::npos); + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + } + + bool isDDS = (ext == std::string(".dds")); // .dds images need special handling + m_isCube = false; + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + ilBindImage(imageID); + + // Let DevIL handle the proper orientation during loading. + if (isDDS) + { + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_UPPER_LEFT); // DEBUG What happens when I set IL_ORIGIN_LOWER_LEFT all the time? + } + else + { + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_LOWER_LEFT); + } + + // Load the image from file. This loads all data. + if (ilLoadImage((const ILstring) foundFile.c_str())) + { + std::vector mipmaps; // All mipmaps excluding the LOD 0. + + ilBindImage(imageID); + ilActiveImage(0); // Get the frst image, potential LOD 0. + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // Get the size of the LOD 0 image. + unsigned int w = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int h = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int d = ilGetInteger(IL_IMAGE_DEPTH); + + // Querying for IL_NUM_IMAGES returns the number of images following the current one. Add 1 for the correct image count! + int numImages = ilGetInteger(IL_NUM_IMAGES) + 1; + + int numMipmaps = 0; // Default to no mipmap handling. + + if (flags & IMAGE_FLAG_MIPMAP) // Only handle mipmaps if we actually want to load them. + { + numMipmaps = ilGetInteger(IL_NUM_MIPMAPS); // Excluding the current image which becomes LOD 0. + + // Special check to see if the number of top-level images build a 1D, 2D, or 3D mipmap chain, if there are no mipmaps in the LOD 0 image. + if (1 < numImages && !numMipmaps) + { + bool isMipmapChain = true; // Indicates if the images in this file build a standard mimpmap chain. + + for (int i = 1; i < numImages; ++i) // Start check at LOD 1. + { + ilBindImage(imageID); + ilActiveImage(i); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // Next image extents. + const unsigned int ww = ilGetInteger(IL_IMAGE_WIDTH); + const unsigned int hh = ilGetInteger(IL_IMAGE_HEIGHT); + const unsigned int dd = ilGetInteger(IL_IMAGE_DEPTH); + + calculateNextExtents(w, h, d, flags); // Calculates the extents of the next mipmap level, taking layered textures into account! + + if (ww == w && hh == h && dd == d) // Criteria for next mipmap level match. + { + // Top-level image actually is the i-th mipmap level. Remember the data. + // This doesn't get overwritten below, because the standard mipmap handling is based on numMipmaps != 0. + mipmaps.push_back(ilGetData()); + } + else + { + // Could not identify top-level image as a mipmap level, no further testing required. + // Test failed, means the number of images do not build a mipmap chain. + isMipmapChain = false; + mipmaps.clear(); + break; + } + } + + if (isMipmapChain) + { + // Consider only the very first image in the file in the following code. + numImages = 1; + } + } + } + + m_isCube = (ilGetInteger(IL_IMAGE_CUBEFLAGS) != 0); + + // If the file isn't identified as cubemap already, + // check if there are six square images of the same extents in the file and handle them as cubemap. + if (!m_isCube && numImages == 6) + { + bool isCube = true; + + unsigned int w0 = 0; + unsigned int h0 = 0; + unsigned int d0 = 0; + + for (int image = 0; image < numImages && isCube; ++image) + { + ilBindImage(imageID); + ilActiveImage(image); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + if (image == 0) + { + w0 = ilGetInteger(IL_IMAGE_WIDTH); + h0 = ilGetInteger(IL_IMAGE_HEIGHT); + d0 = ilGetInteger(IL_IMAGE_DEPTH); + + MY_ASSERT(0 < d0); // This case of no image data is handled later. + + if (w0 != h0) + { + isCube = false; // Not square. + } + } + else + { + unsigned int w1 = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int h1 = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int d1 = ilGetInteger(IL_IMAGE_DEPTH); + + // All LOD 0 faces must be the same size. + if (w0 != w1 || h0 != h1) + { + isCube = false; + } + // If this should be interpreted as layered cubemap, all images must have the same number of layers. + if ((flags & IMAGE_FLAG_LAYER) && d0 != d1) + { + isCube = false; + } + } + } + m_isCube = isCube; + } + + for (int image = 0; image < numImages; ++image) + { + // Cubemap faces within DevIL philosophy are organized like this: + // image -> 1st face -> face index 0 + // face1 -> 2nd face -> face index 1 + // ... + // face5 -> 6th face -> face index 5 + + const int numFaces = ilGetInteger(IL_NUM_FACES) + 1; + + for (int f = 0; f < numFaces; ++f) + { + // Need to juggle with the faces to get them aligned with how OpenGL expects cube faces. Using the same layout in OptiX. + const int face = determineFace(f, m_isCube && isDDS); + + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // pixel format + int format = ilGetInteger(IL_IMAGE_FORMAT); + + if (IL_COLOR_INDEX == format) + { + // Convert color index to whatever the base type of the palette is. + if (!ilConvertImage(ilGetInteger(IL_PALETTE_BASE_TYPE), IL_UNSIGNED_BYTE)) + { + // Free all resources associated with the DevIL image. + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + return false; + } + // Now query format of the converted image. + format = ilGetInteger(IL_IMAGE_FORMAT); + } + + const int type = ilGetInteger(IL_IMAGE_TYPE); + + // Image dimension of the LOD 0 in pixels. + unsigned int width = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int height = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int depth = ilGetInteger(IL_IMAGE_DEPTH); + + if (width == 0 || height == 0 || depth == 0) // There must be at least a single pixel. + { + std::cerr << "ERROR Picture::load() " << filename << ": image " << image << " face " << f << " extents (" << width << ", " << height << ", " << depth << ")\n"; + MY_ASSERT(!"Picture::load() Image with zero extents."); + + // Free all resources associated with the DevIL image. + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + return false; + } + + // Get the remaining mipmaps for this image. + // Note that the special case handled above where multiple images built a mipmap chain + // will not enter this because that was only checked when numMipmaps == 0. + if (0 < numMipmaps) + { + mipmaps.clear(); // Clear this for currently processed image. + + for (int j = 1; j <= numMipmaps; ++j) // Mipmaps are starting at LOD 1. + { + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + ilActiveMipmap(j); + + // Not checking consistency of the individual LODs here. + mipmaps.push_back(ilGetData()); + } + + // Look at LOD 0 of this image again for the next ilGetData(). + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + ilActiveMipmap(0); + } + + // Add a a new vector of images with the whole mipmap chain. + // Mind that there will be six of these for a cubemap image! + unsigned int index = addImages(ilGetData(), width, height, depth, format, type, mipmaps, flags); + + if (m_isCube && isDDS) + { + // WARNING: + // This piece of code MUST NOT be visited twice for the same image, + // because this would falsify the desired effect! + // The images at this position are flipped at the x-axis (due to DevIL) + // flipping at x-axis will result in original image + // mirroring at y-axis will result in rotating the image 180 degree + if (face == 0 || face == 1 || face == 4 || face == 5) // px, nx, pz, nz + { + mirrorY(index); // mirror over y-axis + } + else // py, ny + { + mirrorX(index); // flip over x-axis + } + } + + ILint origin = ilGetInteger(IL_IMAGE_ORIGIN); + if (!m_isCube && origin == IL_ORIGIN_UPPER_LEFT) + { + // OpenGL expects origin at lower left, so the image has to be flipped at the x-axis + // for DDS cubemaps we handle the separate face rotations above + // DEBUG This should only happen for DDS images. + // All others are flipped by DevIL because I set the origin to lower left. Handle DDS images the same? + mirrorX(index); // reverse rows + } + } + } + success = true; + } + + if (!success) + { + std::cerr << "ERROR Picture::load(): " << filename << " not loaded\n"; + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + return success; +} + +void Picture::clear() +{ + m_images.clear(); +} + +// Append a new empty vector of images. Returns the new image index. +unsigned int Picture::addImages() +{ + const unsigned int index = static_cast(m_images.size()); + + m_images.push_back(std::vector()); // Append a new empty vector of image pointers. Each vector holds a mipmap chain. + + return index; +} + +// Add a vector of images and fill it with the LOD 0 pixels and the optional mipmap chain. +unsigned int Picture::addImages(const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type, + const std::vector& mipmaps, const unsigned int flags) +{ + const unsigned int index = static_cast(m_images.size()); + + m_images.push_back(std::vector()); // Append a new empty vector of image pointers. + + Image* image = new Image(width, height, depth, format, type); // LOD 0 + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, pixels, image->m_nob); + + m_images[index].push_back(image); // LOD 0 + + unsigned int w = width; + unsigned int h = height; + unsigned int d = depth; + + for (size_t i = 0; i < mipmaps.size(); ++i) + { + MY_ASSERT(mipmaps[i]); // No nullptr expected. + + calculateNextExtents(w, h, d, flags); // Mind that the flags let this work for layered mipmap chains! + + image = new Image(w, h, d, format, type); // LOD 1 to N. + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, mipmaps[i], image->m_nob); + + m_images[index].push_back(image); // LOD 1 - N + } + + return index; +} + +// Append a new image LOD to the images in index. +unsigned int Picture::addLevel(const unsigned int index, const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type) +{ + MY_ASSERT(index < m_images.size()); + MY_ASSERT(pixels != nullptr); + MY_ASSERT((0 < width) && (0 < height) && (0 < depth)); + + Image* image = new Image(width, height, depth, format, type); + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, pixels, image->m_nob); + + const unsigned int level = static_cast(m_images[index].size()); + + m_images[index].push_back(image); + + return level; +} + + +void Picture::mirrorX(unsigned int index) +{ + MY_ASSERT(index < m_images.size()); + + // Flip all images upside down. + for (size_t i = 0; i < m_images[index].size(); ++i) + { + Image* image = m_images[index][i]; + + const unsigned char* srcPixels = image->m_pixels; + unsigned char* dstPixels = new unsigned char[image->m_nob]; + + for (unsigned int z = 0; z < image->m_depth; ++z) + { + for (unsigned int y = 0; y < image->m_height; ++y) + { + const unsigned char* srcLine = srcPixels + z * image->m_bps + y * image->m_bpl; + unsigned char* dstLine = dstPixels + z * image->m_bps + (image->m_height - 1 - y) * image->m_bpl; + + memcpy(dstLine, srcLine, image->m_bpl); + } + } + delete[] image->m_pixels; + image->m_pixels = dstPixels; + } +} + +void Picture::mirrorY(unsigned int index) +{ + MY_ASSERT(index < m_images.size()); + + // Mirror all images left to right. + for (size_t i = 0; i < m_images[index].size(); ++i) + { + Image* image = m_images[index][i]; + + const unsigned char* srcPixels = image->m_pixels; + unsigned char* dstPixels = new unsigned char[image->m_nob]; + + for (unsigned int z = 0; z < image->m_depth; ++z) + { + for (unsigned int y = 0; y < image->m_height; ++y) + { + const unsigned char* srcLine = srcPixels + z * image->m_bps + y * image->m_bpl; + unsigned char* dstLine = dstPixels + z * image->m_bps + y * image->m_bpl; + + for (unsigned int x = 0; x < image->m_width; ++x) + { + const unsigned char* srcPixel = srcLine + x * image->m_bpp; + unsigned char* dstPixel = dstLine + (image->m_width - 1 - x) * image->m_bpp; + + memcpy(dstPixel, srcPixel, image->m_bpp); + } + } + } + + delete[] image->m_pixels; + image->m_pixels = dstPixels; + } +} + + +void Picture::generateRGBA8(unsigned int width, unsigned int height, unsigned int depth, const unsigned int flags) +{ + clearImages(); + + const unsigned char colors[14][4] = + { + { 0xFF, 0x00, 0x00, 0xFF }, // red + { 0x00, 0xFF, 0x00, 0xFF }, // green + { 0x00, 0x00, 0xFF, 0xFF }, // blue + { 0xFF, 0xFF, 0x00, 0xFF }, // yellow + { 0x00, 0xFF, 0xFF, 0xFF }, // cyan + { 0xFF, 0x00, 0xFF, 0xFF }, // magenta + { 0xFF, 0xFF, 0xFF, 0xFF }, // white + + { 0x7F, 0x00, 0x00, 0xFF }, // dark red + { 0x00, 0x7F, 0x00, 0xFF }, // dark green + { 0x00, 0x00, 0x7F, 0xFF }, // dark blue + { 0x7F, 0x7F, 0x00, 0xFF }, // dark yellow + { 0x00, 0x7F, 0x7F, 0xFF }, // dark cyan + { 0x7F, 0x00, 0x7F, 0xFF }, // dark magenta + { 0x7F, 0x7F, 0x7F, 0xFF } // grey + }; + + m_isCube = (flags & IMAGE_FLAG_CUBE) != 0; + + unsigned char* rgba = new unsigned char[width * height * depth * 4]; // Enough to hold the LOD 0. + + const unsigned int numFaces = (flags & IMAGE_FLAG_CUBE) ? 6 : 1; // Cubemaps put each face in a new images vector. + + for (unsigned int face = 0; face < numFaces; ++face) + { + const unsigned int index = addImages(); // New mipmap chain. + MY_ASSERT(index == face); + + // calculateNextExtents() changes the w, h, d values. Restore them for each face. + unsigned int w = width; + unsigned int h = height; + unsigned int d = depth; + + bool done = false; // Indicates if one mipmap chain is done. + + unsigned int idx = face; // Color index. Each face gets a different color. + + while (!done) + { + unsigned char* p = rgba; + + for (unsigned int z = 0; z < d; ++z) + { + for (unsigned int y = 0; y < h; ++y) + { + for (unsigned int x = 0; x < w; ++x) + { + p[0] = colors[idx][0]; + p[1] = colors[idx][1]; + p[2] = colors[idx][2]; + p[3] = colors[idx][3]; + p += 4; + } + } + } + + idx = (idx + 1) % 14; // Next color index. Each mipmap level gets a different color. + + const unsigned int level = addLevel(index, rgba, w, h, d, IL_RGBA, IL_UNSIGNED_BYTE); + + if ((flags & IMAGE_FLAG_MIPMAP) == 0) + { + done = true; + } + else + { + done = calculateNextExtents(w, h, d, flags); + } + } + } + + delete[] rgba; +} diff --git a/apps/bench_shared/src/Plane.cpp b/apps/bench_shared/src/Plane.cpp new file mode 100644 index 00000000..3cc435b7 --- /dev/null +++ b/apps/bench_shared/src/Plane.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + void Triangles::createPlane(const unsigned int tessU, const unsigned int tessV, const unsigned int upAxis) + { + MY_ASSERT(1 <= tessU && 1 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + const float uTile = 2.0f / float(tessU); + const float vTile = 2.0f / float(tessV); + + float3 corner; + + TriangleAttributes attrib; + + switch (upAxis) + { + case 0: // Positive x-axis is the geometry normal, create geometry on the yz-plane. + corner = make_float3(0.0f, -1.0f, 1.0f); // Lower front corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(0.0f, 0.0f, -1.0f); + attrib.normal = make_float3(1.0f, 0.0f, 0.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(0.0f, v, -u); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + + case 1: // Positive y-axis is the geometry normal, create geometry on the xz-plane. + corner = make_float3(-1.0f, 0.0f, 1.0f); // left front corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 1.0f, 0.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(u, 0.0f, -v); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + + case 2: // Positive z-axis is the geometry normal, create geometry on the xy-plane. + corner = make_float3(-1.0f, -1.0f, 0.0f); // Lower left corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(u, v, 0.0f); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + } + + const unsigned int stride = tessU + 1; + for (unsigned int j = 0; j < tessV; ++j) + { + for (unsigned int i = 0; i < tessU; ++i) + { + m_indices.push_back( j * stride + i); + m_indices.push_back( j * stride + i + 1); + m_indices.push_back((j + 1) * stride + i + 1); + + m_indices.push_back((j + 1) * stride + i + 1); + m_indices.push_back((j + 1) * stride + i); + m_indices.push_back( j * stride + i); + } + } + } + +} // namespace sg \ No newline at end of file diff --git a/apps/bench_shared/src/Rasterizer.cpp b/apps/bench_shared/src/Rasterizer.cpp new file mode 100644 index 00000000..6cab212c --- /dev/null +++ b/apps/bench_shared/src/Rasterizer.cpp @@ -0,0 +1,824 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Rasterizer.h" +#include "inc/MyAssert.h" + +#include +#include +#include + + +#if USE_TIME_VIEW +static bool generateColorRamp(const std::vector& definition, int size, float *ramp) +{ + if (definition.size() < 1 || !size || !ramp) + { + return false; + } + + float r; + float g; + float b; + float a = 1.0f; // CUDA doesn't support float3 textures. + + float *p = ramp; + + if (definition.size() == 1) + { + // Special case, only one color in the input, means the whole color ramp is that color. + r = definition[0].c[0]; + g = definition[0].c[1]; + b = definition[0].c[2]; + + for (int i = 0; i < size; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } + return true; + } + + // Here definition.size() is at least 2. + ColorRampElement left; + ColorRampElement right; + size_t entry = 0; + + left = definition[entry]; + if (0.0f < left.u) + { + left.u = 0.0f; + } + else // left.u == 0.0f; + { + entry++; + } + right = definition[entry++]; + + for (int i = 0; i < size; ++i) + { + // The 1D coordinate at which we need to calculate the color. + float u = (float) i / (float) (size - 1); + + // Check if it's in the range [left.u, right.u) + while (!(left.u <= u && u < right.u)) + { + left = right; + if (entry < definition.size()) + { + right = definition[entry++]; + } + else + { + // left is already the last entry, move right.u to the end of the range. + right.u = 1.0001f; // Make sure we pass 1.0 < right.u in the last iteration. + break; + } + } + + float t = (u - left.u) / (right.u - left.u); + r = left.c[0] + t * (right.c[0] - left.c[0]); + g = left.c[1] + t * (right.c[1] - left.c[1]); + b = left.c[2] + t * (right.c[2] - left.c[2]); + + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } + return true; +} +#endif + + +Rasterizer::Rasterizer(const int w, const int h, const int interop) +: m_width(w) +, m_height(h) +, m_interop(interop) +, m_widthResolution(w) +, m_heightResolution(h) +, m_numDevices(0) +, m_nodeMask(0) +, m_hdrTexture(0) +, m_pbo(0) +, m_glslProgram(0) +, m_vboAttributes(0) +, m_vboIndices(0) +, m_locAttrPosition(-1) +, m_locAttrTexCoord(-1) +, m_locProjection(-1) +, m_locSamplerHDR(-1) +, m_colorRampTexture(0) +, m_locSamplerColorRamp(-1) +, m_locInvGamma(-1) +, m_locColorBalance(-1) +, m_locInvWhitePoint(-1) +, m_locBurnHighlights(-1) +, m_locCrushBlacks(-1) +, m_locSaturation(-1) +{ + for (int i = 0; i < 24; ++i) // Hardcoded size of maximum 24 devices expected. + { + memset(m_deviceUUID[0], 0, sizeof(m_deviceUUID[0])); + } + //memset(m_driverUUID, 0, sizeof(m_driverUUID)); // Unused. + memset(m_deviceLUID, 0, sizeof(m_deviceLUID)); + + // Find out which device is running the OpenGL implementation to be able to allocate the PBO peer-to-peer staging buffer on the same device. + // Needs these OpenGL extensions: + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects.txt + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects_win32.txt + // and on CUDA side the CUDA 10.0 Driver API function cuDeviceGetLuid(). + // While the extensions are named EXT_external_objects, the enums and functions are found under name string EXT_memory_object! + if (GLEW_EXT_memory_object) + { + // UUID + // To determine which devices are used by the current context, first call GetIntegerv with set to NUM_DEVICE_UUIDS_EXT, + // then call GetUnsignedBytei_vEXT with set to DEVICE_UUID_EXT, set to a value in the range [0, ), + // and set to point to an array of UUID_SIZE_EXT unsigned bytes. + glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &m_numDevices); // This is normally 1, but not when multicast is enabled! + MY_ASSERT(m_numDevices <= 24); // m_deviceUUID is "only" prepared for 24 devices. + m_numDevices = std::min(m_numDevices, 24); + + for (GLint i = 0; i < m_numDevices; ++i) + { + glGetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, i, m_deviceUUID[i]); + } + //glGetUnsignedBytevEXT(GL_DRIVER_UUID_EXT, m_driverUUID); // Not used here. + + // LUID + // "The devices in use by the current context may also be identified by an (LUID, node) pair. + // To determine the LUID of the current context, call GetUnsignedBytev with set to DEVICE_LUID_EXT and set to point to an array of LUID_SIZE_EXT unsigned bytes. + // Following the call, can be cast to a pointer to an LUID object that will be equal to the locally unique identifier + // of an IDXGIAdapter1 object corresponding to the adapter used by the current context. + // To identify which individual devices within an adapter are used by the current context, call GetIntegerv with set to DEVICE_NODE_MASK_EXT. + // A bitfield is returned with one bit set for each device node used by the current context. + // The bits set will be subset of those available on a Direct3D 12 device created on an adapter with the same LUID as the current context." + if (GLEW_EXT_memory_object_win32) + { + // It is not expected that a single context will be associated with multiple DXGI adapters, so only one LUID is returned. + glGetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, m_deviceLUID); + glGetIntegerv(GL_DEVICE_NODE_MASK_EXT, &m_nodeMask); + } + } + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + glViewport(0, 0, m_width, m_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // default, works for BGRA8, RGBA16F, and RGBA32F. + + glDisable(GL_CULL_FACE); // default + glDisable(GL_DEPTH_TEST); // default + + glGenTextures(1, &m_hdrTexture); + MY_ASSERT(m_hdrTexture != 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + // For batch rendering initialize the texture contents to some default. + const float texel[4] = { 1.0f, 0.0f, 1.0f, 1.0f }; // Magenta to indicate that the texture has not been initialized. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, &texel); // RGBA32F + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (GLEW_NV_gpu_multicast) + { + const char* envMulticast = getenv("GL_NV_GPU_MULTICAST"); + if (envMulticast != nullptr && envMulticast[0] != '0') + { + std::cerr << "WARNING: Rasterizer() GL_NV_GPU_MULTICAST is enabled. Primary device needs to be inside the maskDevices to display correctly.\n"; + glTexParameteri(GL_TEXTURE_2D, GL_PER_GPU_STORAGE_NV, GL_TRUE); + } + } + + glBindTexture(GL_TEXTURE_2D, 0); + + // DAR The local ImGui sources have been changed to push the GL_TEXTURE_BIT so that this works. + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + if (m_interop == INTEROP_MODE_PBO) + { + // PBO for CUDA-OpenGL interop. + glGenBuffers(1, &m_pbo); + MY_ASSERT(m_pbo != 0); + + // Make sure the buffer is not zero size. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(float) * 4, (GLvoid*) 0, GL_DYNAMIC_DRAW); // RGBA32F from byte offset 0 in the pixel unpack buffer. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + // GLSL shaders objects and program. Must be initialized before using any shader variable locations. + initGLSL(); + + // This initialization is just to generate the vertex buffer objects and bind the VertexAttribPointers. + // Two hardcoded triangles in the viewport size projection coordinate system with 2D texture coordinates. + // These get updated to the correct values in reshape() and in setResolution(). The resolution is not known at this point. + const float attributes[16] = + { + // vertex2f, + 0.0f, 0.0f, + 1.0, 0.0f, + 1.0, 1.0, + 0.0f, 1.0, + //texcoord2f + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + const unsigned int indices[6] = + { + 0, 1, 2, + 2, 3, 0 + }; + + glGenBuffers(1, &m_vboAttributes); + MY_ASSERT(m_vboAttributes != 0); + + glGenBuffers(1, &m_vboIndices); + MY_ASSERT(m_vboIndices != 0); + + // Setup the vertex arrays from the vertex attributes. + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) sizeof(float) * 16, (GLvoid const*) attributes, GL_DYNAMIC_DRAW); + // This requires a bound array buffer! + glVertexAttribPointer(m_locAttrPosition, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (GLvoid*) 0); + glVertexAttribPointer(m_locAttrTexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (GLvoid*) (sizeof(float) * 8)); + glBindBuffer(GL_ARRAY_BUFFER, 0); // PERF It should be faster to keep these buffers bound. + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vboIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr) sizeof(unsigned int) * 6, (const GLvoid*) indices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // PERF It should be faster to keep these buffers bound. + + // Synchronize data with the current values. + updateProjectionMatrix(); + updateVertexAttributes(); + +#if USE_TIME_VIEW + // Generate the color ramp definition vector. + std::vector colorRampDefinition; + + ColorRampElement cre; + + // Cold to hot: blue, green, red, yellow, white + cre.u = 0.0f; + cre.c[0] = 0.0f; // blue + cre.c[1] = 0.0f; + cre.c[2] = 1.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.25f; + cre.c[0] = 0.0f; // green + cre.c[1] = 1.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.5f; + cre.c[0] = 1.0f; // red + cre.c[1] = 0.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.75f; + cre.c[0] = 1.0f; // yellow + cre.c[1] = 1.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 1.0f; + cre.c[0] = 1.0f; // white + cre.c[1] = 1.0f; + cre.c[2] = 1.0f; + colorRampDefinition.push_back(cre); + + std::vector texels(256 * 4); + + bool success = generateColorRamp(colorRampDefinition, 256, texels.data()); + if (success) + { + glGenTextures(1, &m_colorRampTexture); + MY_ASSERT(m_colorRampTexture != 0); + + glActiveTexture(GL_TEXTURE1); // It's set to texture image unit 1. + glBindTexture(GL_TEXTURE_1D, m_colorRampTexture); + + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, texels.data()); + + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glActiveTexture(GL_TEXTURE0); + } +#endif +} + +Rasterizer::~Rasterizer() +{ + glDeleteTextures(1, &m_hdrTexture); + +#if USE_TIME_VIEW + glDeleteTextures(1, &m_colorRampTexture); +#endif + + if (m_interop) + { + glDeleteBuffers(1, &m_pbo); + } + + glDeleteBuffers(1, &m_vboAttributes); + glDeleteBuffers(1, &m_vboIndices); + + glDeleteProgram(m_glslProgram); +} + + +void Rasterizer::reshape(const int w, const int h) +{ + // No check for zero sizes needed. That's done in Application::reshape() + if (m_width != w || m_height != h) + { + m_width = w; + m_height = h; + + glViewport(0, 0, m_width, m_height); + + updateProjectionMatrix(); + updateVertexAttributes(); + } +} + +void Rasterizer::display() +{ + glClear(GL_COLOR_BUFFER_BIT); // PERF Do not do this for benchmarks! + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vboIndices); + + glEnableVertexAttribArray(m_locAttrPosition); + glEnableVertexAttribArray(m_locAttrTexCoord); + + glUseProgram(m_glslProgram); + + glDrawElements(GL_TRIANGLES, (GLsizei) 6, GL_UNSIGNED_INT, (const GLvoid*) 0); + + glUseProgram(0); + + glDisableVertexAttribArray(m_locAttrPosition); + glDisableVertexAttribArray(m_locAttrTexCoord); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +const int Rasterizer::getNumDevices() const +{ + return m_numDevices; +} + +const unsigned char* Rasterizer::getUUID(const unsigned int index) const +{ + MY_ASSERT(index < 24); + return m_deviceUUID[index]; +} + +const unsigned char* Rasterizer::getLUID() const +{ + return m_deviceLUID; +} + +int Rasterizer::getNodeMask() const +{ + return m_nodeMask; +} + +unsigned int Rasterizer::getTextureObject() const +{ + return m_hdrTexture; +} + +unsigned int Rasterizer::getPixelBufferObject() const +{ + return m_pbo; +} + +void Rasterizer::setResolution(const int w, const int h) +{ + if (m_widthResolution != w || m_heightResolution != h) + { + m_widthResolution = (0 < w) ? w : 1; + m_heightResolution = (0 < h) ? h : 1; + + updateVertexAttributes(); + + // Cannot resize the PBO while it's registered with cuGraphicsGLRegisterBuffer(). Deferred to the Device::render() calls. + } +} + +void Rasterizer::setTonemapper(const TonemapperGUI& tm) +{ +#if !USE_TIME_VIEW + glUseProgram(m_glslProgram); + + glUniform1f(m_locInvGamma, 1.0f / tm.gamma); + glUniform3f(m_locColorBalance, tm.colorBalance[0], tm.colorBalance[1], tm.colorBalance[2]); + glUniform1f(m_locInvWhitePoint, tm.brightness / tm.whitePoint); + glUniform1f(m_locBurnHighlights, tm.burnHighlights); + glUniform1f(m_locCrushBlacks, tm.crushBlacks + tm.crushBlacks + 1.0f); + glUniform1f(m_locSaturation, tm.saturation); + + glUseProgram(0); +#endif +} + + +// Private functions: + +void Rasterizer::checkInfoLog(const char* /* msg */, GLuint object) +{ + GLint maxLength = 0; + + const GLboolean isShader = glIsShader(object); + + if (isShader) + { + glGetShaderiv(object, GL_INFO_LOG_LENGTH, &maxLength); + } + else + { + glGetProgramiv(object, GL_INFO_LOG_LENGTH, &maxLength); + } + + if (1 < maxLength) + { + GLchar *infoLog = new GLchar[maxLength]; + + if (infoLog != nullptr) + { + GLint length = 0; + + if (isShader) + { + glGetShaderInfoLog(object, maxLength, &length, infoLog); + } + else + { + glGetProgramInfoLog(object, maxLength, &length, infoLog); + } + + //fprintf(fileLog, "-- tried to compile (len=%d): %s\n", (unsigned int)strlen(msg), msg); + //fprintf(fileLog, "--- info log contents (len=%d) ---\n", (int) maxLength); + //fprintf(fileLog, "%s", infoLog); + //fprintf(fileLog, "--- end ---\n"); + std::cout << infoLog << '\n'; + // Look at the info log string here... + + delete [] infoLog; + } + } +} + +void Rasterizer::initGLSL() +{ + static const std::string vsSource = + "#version 330\n" + "layout(location = 0) in vec2 attrPosition;\n" + "layout(location = 1) in vec2 attrTexCoord;\n" + "uniform mat4 projection;\n" + "out vec2 varTexCoord;\n" + "void main()\n" + "{\n" + " gl_Position = projection * vec4(attrPosition, 0.0, 1.0);\n" + " varTexCoord = attrTexCoord;\n" + "}\n"; + + +#if USE_TIME_VIEW + static const std::string fsSource = + "#version 330\n" + "uniform sampler2D samplerHDR;\n" + "uniform sampler1D samplerColorRamp;\n" + "in vec2 varTexCoord;\n" + "layout(location = 0, index = 0) out vec4 outColor;\n" + "void main()\n" + "{\n" + " float alpha = texture(samplerHDR, varTexCoord).a;\n" + " outColor = texture(samplerColorRamp, alpha);\n" + "}\n"; +#else + static const std::string fsSource = + "#version 330\n" + "uniform sampler2D samplerHDR;\n" + "uniform vec3 colorBalance;\n" + "uniform float invWhitePoint;\n" + "uniform float burnHighlights;\n" + "uniform float saturation;\n" + "uniform float crushBlacks;\n" + "uniform float invGamma;\n" + "in vec2 varTexCoord;\n" + "layout(location = 0, index = 0) out vec4 outColor;\n" + "void main()\n" + "{\n" + " vec3 hdrColor = texture(samplerHDR, varTexCoord).rgb;\n" + " vec3 ldrColor = invWhitePoint * colorBalance * hdrColor;\n" + " ldrColor *= (ldrColor * burnHighlights + 1.0) / (ldrColor + 1.0);\n" + " float luminance = dot(ldrColor, vec3(0.3, 0.59, 0.11));\n" + " ldrColor = max(mix(vec3(luminance), ldrColor, saturation), 0.0);\n" + " luminance = dot(ldrColor, vec3(0.3, 0.59, 0.11));\n" + " if (luminance < 1.0)\n" + " {\n" + " ldrColor = max(mix(pow(ldrColor, vec3(crushBlacks)), ldrColor, sqrt(luminance)), 0.0);\n" + " }\n" + " ldrColor = pow(ldrColor, vec3(invGamma));\n" + " outColor = vec4(ldrColor, 1.0);\n" + "}\n"; +#endif + + GLint vsCompiled = 0; + GLint fsCompiled = 0; + + GLuint glslVS = glCreateShader(GL_VERTEX_SHADER); + if (glslVS) + { + GLsizei len = (GLsizei) vsSource.size(); + const GLchar *vs = vsSource.c_str(); + glShaderSource(glslVS, 1, &vs, &len); + glCompileShader(glslVS); + checkInfoLog(vs, glslVS); + + glGetShaderiv(glslVS, GL_COMPILE_STATUS, &vsCompiled); + MY_ASSERT(vsCompiled); + } + + GLuint glslFS = glCreateShader(GL_FRAGMENT_SHADER); + if (glslFS) + { + GLsizei len = (GLsizei) fsSource.size(); + const GLchar *fs = fsSource.c_str(); + glShaderSource(glslFS, 1, &fs, &len); + glCompileShader(glslFS); + checkInfoLog(fs, glslFS); + + glGetShaderiv(glslFS, GL_COMPILE_STATUS, &fsCompiled); + MY_ASSERT(fsCompiled); + } + + m_glslProgram = glCreateProgram(); + if (m_glslProgram) + { + GLint programLinked = 0; + + if (glslVS && vsCompiled) + { + glAttachShader(m_glslProgram, glslVS); + } + if (glslFS && fsCompiled) + { + glAttachShader(m_glslProgram, glslFS); + } + + glLinkProgram(m_glslProgram); + checkInfoLog("m_glslProgram", m_glslProgram); + + glGetProgramiv(m_glslProgram, GL_LINK_STATUS, &programLinked); + MY_ASSERT(programLinked); + + if (programLinked) + { + glUseProgram(m_glslProgram); + + // DAR FIXME Put these into a struct. + m_locAttrPosition = glGetAttribLocation(m_glslProgram, "attrPosition"); + m_locAttrTexCoord = glGetAttribLocation(m_glslProgram, "attrTexCoord"); + m_locProjection = glGetUniformLocation(m_glslProgram, "projection"); + + MY_ASSERT(m_locAttrPosition != -1); + MY_ASSERT(m_locAttrTexCoord != -1); + MY_ASSERT(m_locProjection != -1); + + m_locSamplerHDR = glGetUniformLocation(m_glslProgram, "samplerHDR"); + MY_ASSERT(m_locSamplerHDR != -1); + glUniform1i(m_locSamplerHDR, 0); // The rasterizer uses texture image unit 0 to display the HDR image. + +#if USE_TIME_VIEW + m_locSamplerColorRamp = glGetUniformLocation(m_glslProgram, "samplerColorRamp"); + MY_ASSERT(m_locSamplerColorRamp != -1); + glUniform1i(m_locSamplerColorRamp, 1); // The rasterizer uses texture image unit 1 for the color ramp when USE_TIME_VIEW is enabled. +#else + m_locInvGamma = glGetUniformLocation(m_glslProgram, "invGamma"); + m_locColorBalance = glGetUniformLocation(m_glslProgram, "colorBalance"); + m_locInvWhitePoint = glGetUniformLocation(m_glslProgram, "invWhitePoint"); + m_locBurnHighlights = glGetUniformLocation(m_glslProgram, "burnHighlights"); + m_locCrushBlacks = glGetUniformLocation(m_glslProgram, "crushBlacks"); + m_locSaturation = glGetUniformLocation(m_glslProgram, "saturation"); + + MY_ASSERT(m_locInvGamma != -1); + MY_ASSERT(m_locColorBalance != -1); + MY_ASSERT(m_locInvWhitePoint != -1); + MY_ASSERT(m_locBurnHighlights != -1); + MY_ASSERT(m_locCrushBlacks != -1); + MY_ASSERT(m_locSaturation != -1); + + // Set neutral Tonemapper defaults. This will show the linear HDR image. + glUniform1f(m_locInvGamma, 1.0f); + glUniform3f(m_locColorBalance, 1.0f, 1.0f, 1.0f); + glUniform1f(m_locInvWhitePoint, 1.0f); + glUniform1f(m_locBurnHighlights, 1.0f); + glUniform1f(m_locCrushBlacks, 1.0f); + glUniform1f(m_locSaturation, 1.0f); +#endif + + glUseProgram(0); + } + } + + if (glslVS) + { + glDeleteShader(glslVS); + } + if (glslFS) + { + glDeleteShader(glslFS); + } +} + + +void Rasterizer::updateProjectionMatrix() +{ + // No need to set this when using shaders only. + //glMatrixMode(GL_PROJECTION); + //glLoadIdentity(); + //glOrtho(0.0, GLdouble(m_width), 0.0, GLdouble(m_height), -1.0, 1.0); + + //glMatrixMode(GL_MODELVIEW); + + // Full projection matrix calculation: + //const float l = 0.0f; + const float r = float(m_width); + //const float b = 0.0f; + const float t = float(m_height); + //const float n = -1.0f; + //const float f = 1.0; + + //const float m00 = 2.0f / (r - l); // == 2.0f / r with l == 0.0f + //const float m11 = 2.0f / (t - b); // == 2.0f / t with b == 0.0f + //const float m22 = -2.0f / (f - n); // Always -1.0f with f == 1.0f and n == -1.0f + //const float tx = -(r + l) / (r - l); // Always -1.0f with l == 0.0f + //const float ty = -(t + b) / (t - b); // Always -1.0f with b == 0.0f + //const float tz = -(f + n) / (f - n); // Always 0.0f with f = -n + + // Row-major layout, needs transpose in glUniformMatrix4fv. + //const float projection[16] = + //{ + // m00, 0.0f, 0.0f, tx, + // 0.0f, m11, 0.0f, ty, + // 0.0f, 0.0f, m22, tz, + // 0.0f, 0.0f, 0.0f, 1.0f + //}; + + // Optimized version and colum-major layout: + const float projection[16] = + { + 2.0f / r, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / t, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 1.0f + }; + + glUseProgram(m_glslProgram); + glUniformMatrix4fv(m_locProjection, 1, GL_FALSE, projection); // Column-major memory layout, no transpose. + glUseProgram(0); +} + + +void Rasterizer::updateVertexAttributes() +{ + // This routine calculates the vertex attributes for the diplay routine. + // It calculates screen space vertex coordinates to display the full rendered image + // in the correct aspect ratio independently of the window client size. + // The image gets scaled down when it's bigger than the client window. + + // The final screen space vertex coordinates for the texture blit. + float x0; + float y0; + float x1; + float y1; + + // This routine picks the required filtering mode for this texture. + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + if (m_widthResolution <= m_width && m_heightResolution <= m_height) + { + // Texture fits into viewport without scaling. + // Calculate the amount of cleared border pixels. + int w1 = m_width - m_widthResolution; + int h1 = m_height - m_heightResolution; + // Halve the border size to get the lower left offset + int w0 = w1 >> 1; + int h0 = h1 >> 1; + // Subtract from the full border to get the right top offset. + w1 -= w0; + h1 -= h0; + // Calculate the texture blit screen space coordinates. + x0 = float(w0); + y0 = float(h0); + x1 = float(m_width - w1); + y1 = float(m_height - h1); + + // Fill the background with black to indicate that all pixels are visible without scaling. + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + // Use nearest filtering to display the pixels exactly. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else // Case + { + // Texture needs to be scaled down to fit into client window. + // Check which extent defines the necessary scaling factor. + const float wC = float(m_width); + const float hC = float(m_height); + const float wR = float(m_widthResolution); + const float hR = float(m_heightResolution); + + const float scale = std::min(wC / wR, hC / hR); + + const float swR = scale * wR; + const float shR = scale * hR; + + x0 = 0.5f * (wC - swR); + y0 = 0.5f * (hC - shR); + x1 = x0 + swR; + y1 = y0 + shR; + + // Render surrounding pixels in dark red to indicate that the image is scaled down. + glClearColor(0.2f, 0.0f, 0.0f, 0.0f); + + // Use linear filtering to smooth the downscaling. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + // Update the vertex attributes with the new texture blit screen space coordinates. + const float attributes[16] = + { + // vertex2f + x0, y0, + x1, y0, + x1, y1, + x0, y1, + // texcoord2f + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) sizeof(float) * 16, (GLvoid const*) attributes, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); // PERF It should be faster to keep them bound. +} diff --git a/apps/bench_shared/src/Raytracer.cpp b/apps/bench_shared/src/Raytracer.cpp new file mode 100644 index 00000000..87bb2999 --- /dev/null +++ b/apps/bench_shared/src/Raytracer.cpp @@ -0,0 +1,912 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Raytracer.h" + +#include "inc/CheckMacros.h" + +#include +#include +#include +#include +#include + +Raytracer::Raytracer(const int maskDevices, + const int miss, + const int interop, + const unsigned int tex, + const unsigned int pbo, + const size_t sizeArena, + const int p2p) +: m_maskDevices(maskDevices) +, m_miss(miss) +, m_interop(interop) +, m_tex(tex) +, m_pbo(pbo) +, m_sizeArena(sizeArena) +, m_peerToPeer(p2p) +, m_isValid(false) +, m_numDevicesVisible(0) +, m_indexDeviceOGL(-1) +, m_maskDevicesActive(0) +, m_iterationIndex(0) +, m_samplesPerPixel(1) +{ + CU_CHECK( cuInit(0) ); // Initialize CUDA driver API. + + int versionDriver = 0; + CU_CHECK( cuDriverGetVersion(&versionDriver) ); + + // The version is returned as (1000 * major + 10 * minor). + int major = versionDriver / 1000; + int minor = (versionDriver - 1000 * major) / 10; + std::cout << "CUDA Driver Version = " << major << "." << minor << '\n'; + + CU_CHECK( cuDeviceGetCount(&m_numDevicesVisible) ); + std::cout << "CUDA Device Count = " << m_numDevicesVisible << '\n'; + + // Match user defined m_maskDevices with the number of visible devices. + // Builds m_maskActiveDevices and fills m_devicesActive which defines the device count. + selectDevices(); + + // This Raytracer is all about sharing data in peer-to-peer islands on multi-GPU setups. + // While that can be individually enabled for texture array and/or GAS and vertex attribute data sharing, + // the compositing of the final image is also done with peer-to-peer copies. + (void) enablePeerAccess(); + + m_isValid = !m_devicesActive.empty(); +} + + +Raytracer::~Raytracer() +{ + try + { + // This function contains throw() calls. + disablePeerAccess(); // Just for cleanliness, the Devices are destroyed anyway after this. + + // The GeometryData is either created on each device or only on one device of an NVLINK island. + // In any case GeometryData is unique and must only be destroyed by the device owning the data. + for (auto& data : m_geometryData) + { + m_devicesActive[data.owner]->destroyGeometry(data); + } + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + delete m_devicesActive[i]; + } + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + +} + +int Raytracer::matchUUID(const char* uuid) +{ + const int size = static_cast(m_devicesActive.size()); + + for (int i = 0; i < size; ++i) + { + if (m_devicesActive[i]->matchUUID(uuid)) + { + // Use the first device which matches with the OpenGL UUID. + // DEBUG This might not be the right thing to do with multicast enabled. + m_indexDeviceOGL = i; + break; + } + } + + std::cout << "OpenGL on active device index " << m_indexDeviceOGL << '\n'; // DEBUG + + return m_indexDeviceOGL; // If this stays -1, the active devices do not contain the one running the OpenGL implementation. +} + +int Raytracer::matchLUID(const char* luid, const unsigned int nodeMask) +{ + const int size = static_cast(m_devicesActive.size()); + + for (int i = 0; i < size; ++i) + { + if (m_devicesActive[i]->matchLUID(luid, nodeMask)) + { + // Use the first device which matches with the OpenGL LUID and test of the node mask bit. + // DEBUG This might not be the right thing to do with multicast enabled. + m_indexDeviceOGL = i; + break; + } + } + + std::cout << "OpenGL on active device index " << m_indexDeviceOGL << '\n'; // DEBUG + + return m_indexDeviceOGL; // If this stays -1, the active devices do not contain the one running the OpenGL implementation. +} + + +int Raytracer::findActiveDevice(const unsigned int domain, const unsigned int bus, const unsigned int device) const +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + const DeviceAttribute& attribute = m_devicesActive[i]->m_deviceAttribute; + + if (attribute.pciDomainId == domain && + attribute.pciBusId == bus && + attribute.pciDeviceId == device) + { + return static_cast(i); + } + } + + return -1; +} + + +bool Raytracer::activeNVLINK(const int home, const int peer) const +{ + // All NVML calls related to NVLINK are only supported by Pascal (SM 6.0) and newer. + if (m_devicesActive[home]->m_deviceAttribute.computeCapabilityMajor < 6) + { + return false; + } + + nvmlDevice_t deviceHome; + + if (m_nvml.m_api.nvmlDeviceGetHandleByPciBusId(m_devicesActive[home]->m_devicePciBusId.c_str(), &deviceHome) != NVML_SUCCESS) + { + return false; + } + + // The NVML deviceHome is part of the active devices at index "home". + for (unsigned int link = 0; link < NVML_NVLINK_MAX_LINKS; ++link) + { + // First check if this link is supported at all and if it's active. + nvmlEnableState_t enableState = NVML_FEATURE_DISABLED; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkState(deviceHome, link, &enableState) != NVML_SUCCESS) + { + continue; + } + if (enableState != NVML_FEATURE_ENABLED) + { + continue; + } + + // Is peer-to-peer over NVLINK supported by this link? + // The requirement for peer-to-peer over NVLINK under Windows is Windows 10 (WDDM2), 64-bit, SLI enabled. + unsigned int capP2P = 0; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkCapability(deviceHome, link, NVML_NVLINK_CAP_P2P_SUPPORTED, &capP2P) != NVML_SUCCESS) + { + continue; + } + if (capP2P == 0) + { + continue; + } + + nvmlPciInfo_t pciInfoPeer; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkRemotePciInfo(deviceHome, link, &pciInfoPeer) != NVML_SUCCESS) + { + continue; + } + + // Check if the NVML remote device matches the desired peer devcice. + if (peer == findActiveDevice(pciInfoPeer.domain, pciInfoPeer.bus, pciInfoPeer.device)) + { + return true; + } + } + + return false; +} + + +bool Raytracer::enablePeerAccess() +{ + bool success = true; + + // Build a peer-to-peer connection matrix which only allows peer-to-peer access over NVLINK bridges. + const int size = static_cast(m_devicesActive.size()); + MY_ASSERT(size <= 32); + + // Peer-to-peer access is encoded in a bitfield of uint32 entries. + // Indexed by [home] device and peer devices are the bit indices, accessed with (1 << peer) masks. + m_peerConnections.resize(size); + + // Initialize the connection matrix diagonal with the trivial case (home == peer). + // This let's building the islands still work if there are any exceptions. + for (int home = 0; home < size; ++home) + { + m_peerConnections[home] = (1 << home); + } + + // Check if the system configuration option "peerToPeer" allowed peer-to-peer via PCI-E irrespective of the NVLINK topology. + // In that case the activeNVLINK() function is not called below. + // DAR FIXME PERF In that case NVML wouldn't be needed at all. + const bool allowPCI = ((m_peerToPeer & P2P_PCI) != 0); + + // The NVML_CHECK and CU_CHECK macros can throw exceptions. + // Keep them local in this routine because not having NVLINK islands with peer-to-peer access + // is not a fatal condition for the renderer. It just won't be able to share resources. + try + { + if (m_nvml.initFunctionTable()) + { + NVML_CHECK( m_nvml.m_api.nvmlInit() ); + + for (int home = 0; home < size; ++home) // Home device index. + { + for (int peer = 0; peer < size; ++peer) // Peer device index. + { + if (home != peer && (allowPCI || activeNVLINK(home, peer))) + { + int canAccessPeer = 0; + + CU_CHECK( cuDeviceCanAccessPeer(&canAccessPeer, + m_devicesActive[home]->m_cudaDevice, // If this current home device + m_devicesActive[peer]->m_cudaDevice) ); // can access the peer device's memory. + if (canAccessPeer != 0) + { + // Note that this function changes the current context! + CU_CHECK( cuCtxSetCurrent(m_devicesActive[home]->m_cudaContext) ); + + CUresult result = cuCtxEnablePeerAccess(m_devicesActive[peer]->m_cudaContext, 0); // Flags must be 0! + if (result == CUDA_SUCCESS) + { + m_peerConnections[home] |= (1 << peer); // Set the connection bit if the enable succeeded. + } + else + { + // Print the ordinal here to be consistent with the other output about used devices. + std::cerr << "WARNING: cuCtxEnablePeerAccess() between device ordinals (" + << m_devicesActive[home]->m_ordinal << ", " + << m_devicesActive[peer]->m_ordinal << ") failed with CUresult " << result << '\n'; + } + } + } + } + } + + NVML_CHECK( m_nvml.m_api.nvmlShutdown() ); + } + } + catch (const std::exception& e) + { + // FIXME Reaching this from CU_CHECK macros above means nvmlShutdown() hasn't been called. + std::cerr << e.what() << '\n'; + // No return here. Always build the m_islands from the existing connection matrix information. + success = false; + } + + // Now use the peer-to-peer connection matrix to build peer-to-peer islands. + // First fill a vector with all device indices which have not been assigned to an island. + std::vector unassigned(size); + + for (int i = 0; i < size; ++i) + { + unassigned[i] = i; + } + + while (!unassigned.empty()) + { + std::vector island; + std::vector::const_iterator it = unassigned.begin(); + + island.push_back(*it); + unassigned.erase(it); // This device has been assigned to an island. + + it = unassigned.begin(); // The next unassigned device. + while (it != unassigned.end()) + { + bool isAccessible = true; + + const int peer = *it; + + // Check if this peer device is accessible by all other devices in the island. + for (size_t i = 0; i < island.size(); ++i) + { + const int home = island[i]; + + if ((m_peerConnections[home] & (1 << peer)) == 0 || + (m_peerConnections[peer] & (1 << home)) == 0) + { + isAccessible = false; + } + } + + if (isAccessible) + { + island.push_back(*it); + unassigned.erase(it); // This device has been assigned to an island. + + it = unassigned.begin(); // The next unassigned device. + } + else + { + ++it; // The next unassigned device, without erase in between. + } + } + m_islands.push_back(island); + } + + std::ostringstream text; + + text << m_islands.size() << " peer-to-peer island"; + if (1 < m_islands.size()) + { + text << 's'; + } + text << ": "; + for (size_t i = 0; i < m_islands.size(); ++i) + { + const std::vector& island = m_islands[i]; + + text << "("; + for (size_t j = 0; j < island.size(); ++j) + { + // Print the ordinal here to be consistent with the other output about used devices. + text << m_devicesActive[island[j]]->m_ordinal; + if (j + 1 < island.size()) + { + text << ", "; + } + } + text << ")"; + if (i + 1 < m_islands.size()) + { + text << " + "; + } + } + std::cout << text.str() << '\n'; + + return success; +} + +void Raytracer::disablePeerAccess() +{ + const int size = static_cast(m_devicesActive.size()); + MY_ASSERT(size <= 32); + + // Peer-to-peer access is encoded in a bitfield of uint32 entries. + for (int home = 0; home < size; ++home) // Home device index. + { + for (int peer = 0; peer < size; ++peer) // Peer device index. + { + if (home != peer && (m_peerConnections[home] & (1 << peer)) != 0) + { + // Note that this function changes the current context! + CU_CHECK( cuCtxSetCurrent(m_devicesActive[home]->m_cudaContext) ); // Home context. + CU_CHECK( cuCtxDisablePeerAccess(m_devicesActive[peer]->m_cudaContext) ); // Peer context. + + m_peerConnections[home] &= ~(1 << peer); + } + } + } + + m_islands.clear(); // No peer-to-peer islands anymore. + + // Each device is its own island now. + for (int i = 0; i < size; ++i) + { + std::vector island; + + island.push_back(i); + + m_islands.push_back(island); + + m_peerConnections[i] |= (1 << i); // Should still be set from above. + } +} + +void Raytracer::synchronize() +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->activateContext(); + m_devicesActive[i]->synchronizeStream(); + } +} + +// DAR FIXME This cannot handle cases where the same Picture would be used for different texture objects, but that is not happening in this example. +void Raytracer::initTextures(const std::map& mapPictures) +{ + const bool allowSharingTex = ((m_peerToPeer & P2P_TEX) != 0); // Material texture sharing (very cheap). + const bool allowSharingEnv = ((m_peerToPeer & P2P_ENV) != 0); // HDR Environment and CDF sharing (CDF binary search is expensive). + + for (std::map::const_iterator it = mapPictures.begin(); it != mapPictures.end(); ++it) + { + const Picture* picture = it->second; + + const bool isEnv = ((picture->getFlags() & IMAGE_FLAG_ENV) != 0); + + if ((allowSharingTex && !isEnv) || (allowSharingEnv && isEnv)) + { + for (const auto& island : m_islands) // Resource sharing only works across devices inside a peer-to-peer island. + { + const int deviceHome = getDeviceHome(island); + + const Texture* texture = m_devicesActive[deviceHome]->initTexture(it->first, picture, picture->getFlags()); + + for (auto device : island) + { + if (device != deviceHome) + { + m_devicesActive[device]->shareTexture(it->first, texture); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + (void) m_devicesActive[device]->initTexture(it->first, picture, picture->getFlags()); + } + } + } +} + + +void Raytracer::initCameras(const std::vector& cameras) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initCameras(cameras); + } +} + +void Raytracer::initLights(const std::vector& lights) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initLights(lights); + } +} + +void Raytracer::initMaterials(const std::vector& materialsGUI) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initMaterials(materialsGUI); + } +} + +// Traverse the SceneGraph and store Groups, Instances and Triangles nodes in the raytracer representation. +void Raytracer::initScene(std::shared_ptr root, const unsigned int numGeometries) +{ + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); // GAS and vertex attribute sharing (GAS sharing is very expensive). + + if (allowSharingGas) + { + // Allocate the number of GeometryData per island. + m_geometryData.resize(numGeometries * m_islands.size()); // Sharing data per island. + } + else + { + // Allocate the number of GeometryData per active device. + m_geometryData.resize(numGeometries * m_devicesActive.size()); // Not sharing, all devices hold all geometry data. + } + + InstanceData instanceData(~0u, -1, -1); + + float matrix[12]; + + // Set the affine matrix to identity by default. + memset(matrix, 0, sizeof(float) * 12); + matrix[ 0] = 1.0f; + matrix[ 5] = 1.0f; + matrix[10] = 1.0f; + + traverseNode(root, instanceData, matrix); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + for (auto device : island) // Device index in this island. + { + // The IAS and SBT are not shared in this example. + m_devicesActive[device]->createTLAS(); + m_devicesActive[device]->createHitGroupRecords(m_geometryData, numIslands, indexIsland); + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + m_devicesActive[device]->createTLAS(); + m_devicesActive[device]->createHitGroupRecords(m_geometryData, numDevices, device); + } + } +} + + +void Raytracer::initState(const DeviceState& state) +{ + m_samplesPerPixel = (unsigned int)(state.samplesSqrt * state.samplesSqrt); + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->setState(state); + } +} + +void Raytracer::updateCamera(const int idCamera, const CameraDefinition& camera) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateCamera(idCamera, camera); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateLight(const int idLight, const LightDefinition& light) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateLight(idLight, light); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateMaterial(const int idMaterial, const MaterialGUI& materialGUI) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateMaterial(idMaterial, materialGUI); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateState(const DeviceState& state) +{ + m_samplesPerPixel = (unsigned int)(state.samplesSqrt * state.samplesSqrt); + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->setState(state); + } + m_iterationIndex = 0; // Restart accumulation. +} + + +// The public function which does the multi-GPU wrapping. +// Returns the count of renderered iterations (m_iterationIndex after it has been incremented). +unsigned int Raytracer::render() +{ + // Continue manual accumulation rendering if the samples per pixel have not been reached. + if (m_iterationIndex < m_samplesPerPixel) + { + void* buffer = nullptr; + + // Make sure the OpenGL device is allocating the full resolution backing storage. + const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // This is the device which needs to allocate the peer-to-peer buffer to reside on the same device as the PBO or Texture + m_devicesActive[index]->render(m_iterationIndex, &buffer); // Interactive rendering. All devices work on the same iteration index. + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + if (index != static_cast(i)) + { + // If buffer is still nullptr here, the first device will allocate the full resolution buffer. + m_devicesActive[i]->render(m_iterationIndex, &buffer); + } + } + + ++m_iterationIndex; + } + return m_iterationIndex; +} + +void Raytracer::updateDisplayTexture() +{ + const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // First, copy the texelBuffer of the primary device into its tileBuffer and then place the tiles into the outputBuffer. + m_devicesActive[index]->compositor(m_devicesActive[index]); + + // Now copy the other devices' texelBuffers over to the main tileBuffer and repeat the compositing for that other device. + // The cuMemcpyPeerAsync done in that case is fast when the devices are in the same peer island, otherwise it's copied via PCI-E, but only N-1 copies of 1/N size are done. + // The saving here is no peer-to-peer read-modify-write when rendering, because everything is happening in GPU local buffers, which are also tightly packed. + // The final compositing is just a kernel implementing a tiled memcpy. + // PERF If all tiles are copied to the main device at once, such kernel would only need to be called once. + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + if (index != static_cast(i)) + { + m_devicesActive[index]->compositor(m_devicesActive[i]); + } + } + + // Finally copy the primary device outputBuffer to the display texture. + // FIXME DEBUG Does that work when m_indexDeviceOGL is not in the list of active devices? + m_devicesActive[index]->updateDisplayTexture(); +} + +const void* Raytracer::getOutputBufferHost() +{ + // Same initial steps to fill the outputBuffer on the primary device as in updateDisplayTexture() + const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // First, copy the texelBuffer of the primary device into its tileBuffer and then place the tiles into the outputBuffer. + m_devicesActive[index]->compositor(m_devicesActive[index]); + + // Now copy the other devices' texelBuffers over to the main tileBuffer and repeat the compositing for that other device. + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + if (index != static_cast(i)) + { + m_devicesActive[index]->compositor(m_devicesActive[i]); + } + } + + // The full outputBuffer resides on device "index" and the host buffer is also only resized by that device. + return m_devicesActive[index]->getOutputBufferHost(); +} + +// Private functions. + +void Raytracer::selectDevices() +{ + // Need to determine the number of active devices first to have it available as device constructor argument. + int count = 0; + int ordinal = 0; + + while (ordinal < m_numDevicesVisible) // Don't try to enable more devices than visible to CUDA. + { + const unsigned int mask = (1 << ordinal); + + if (m_maskDevices & mask) + { + // Track which and how many devices have actually been enabled. + m_maskDevicesActive |= mask; + ++count; + } + + ++ordinal; + } + + // Now really construct the Device objects. + ordinal = 0; + + while (ordinal < m_numDevicesVisible) + { + const unsigned int mask = (1 << ordinal); + + if (m_maskDevicesActive & mask) + { + const int index = static_cast(m_devicesActive.size()); + + Device* device = new Device(ordinal, index, count, m_miss, m_interop, m_tex, m_pbo, m_sizeArena); + + m_devicesActive.push_back(device); + + std::cout << "Device ordinal " << ordinal << ": " << device->m_deviceName << " selected as active device index " << index << '\n'; + } + + ++ordinal; + } + + if (m_devicesActive.size() == 1) + { + std::cerr << "WARNING: selectDevices() Only one device active! This renderer is designed for multi-GPU with NVLINK.\n"; + } +} + +#if 1 +// This implementation does not consider the actually free amount of VRAM on the individual devices in an island, but assumes they are equally loaded. +// This method works more fine grained with the arena allocator. +int Raytracer::getDeviceHome(const std::vector& island) const +{ + // Find the device inside each island which has the least amount of allocated memory. + size_t sizeMin = ~0ull; // Biggest unsigned 64-bit number. + int deviceHome = 0; // Default to zero if all devices are OOM. That will fail in CU_CHECK later. + + for (auto device : island) + { + const size_t size = m_devicesActive[device]->getMemoryAllocated(); + + if (size < sizeMin) + { + sizeMin = size; + deviceHome = device; + } + } + + //std::cout << "deviceHome = " << deviceHome << ", allocated [MiB] = " << double(sizeMin) / (1024.0 * 1024.0) << '\n'; // DEBUG + + return deviceHome; +} + +#else + +// This implementation uses the actual free amount of VRAM on the individual devices in an NVLINK island. +// With the arena allocator this will result in less fine grained distribution of resources because the free memory only changes when a new arena is allocated. +// Using a smaller arena size would switch allocations between devices more often in this case. +int Raytracer::getDeviceHome(const std::vector& island) const +{ + // Find the device inside each island which has the most free memory. + size_t sizeMax = 0; + int deviceHome = 0; // Default to zero if all devices are OOM. That will fail in CU_CHECK later. + + for (auto device : island) + { + const size_t size = m_devicesActive[device]->getMemoryFree(); // Actual free VRAM overall. + + if (sizeMax < size) + { + sizeMax = size; + deviceHome = device; + } + } + + //std::cout << "deviceHome = " << deviceHome << ", free [MiB] = " << double(sizeMax) / (1024.0 * 1024.0) << '\n'; // DEBUG + + return deviceHome; +} +#endif + +// m = a * b; +static void multiplyMatrix(float* m, const float* a, const float* b) +{ + m[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8]; // + a[3] * 0 + m[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9]; // + a[3] * 0 + m[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10]; // + a[3] * 0 + m[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[3]; // * 1 + + m[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8]; // + a[7] * 0 + m[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9]; // + a[7] * 0 + m[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10]; // + a[7] * 0 + m[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[7]; // * 1 + + m[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; // + a[11] * 0 + m[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; // + a[11] * 0 + m[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; // + a[11] * 0 + m[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; // * 1 +} + + +// Depth-first traversal of the scene graph to flatten all unique paths to a geometry node to one-level instancing inside the OptiX render graph. +void Raytracer::traverseNode(std::shared_ptr node, InstanceData instanceData, float matrix[12]) +{ + switch (node->getType()) + { + case sg::NodeType::NT_GROUP: + { + std::shared_ptr group = std::dynamic_pointer_cast(node); + + for (size_t i = 0; i < group->getNumChildren(); ++i) + { + traverseNode(group->getChild(i), instanceData, matrix); + } + } + break; + + case sg::NodeType::NT_INSTANCE: + { + std::shared_ptr instance = std::dynamic_pointer_cast(node); + + // Track the assigned material and light indices. The bottom-most node wins. + const int idMaterial = instance->getMaterial(); + if (0 <= idMaterial) + { + instanceData.idMaterial = idMaterial; + } + + const int idLight = instance->getLight(); + if (0 <= idLight) + { + instanceData.idLight = idLight; + } + + // Concatenate the transformations along the path. + float trafo[12]; + + multiplyMatrix(trafo, matrix, instance->getTransform()); + + traverseNode(instance->getChild(), instanceData, trafo); + } + break; + + case sg::NodeType::NT_TRIANGLES: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + const int deviceHome = getDeviceHome(island); + + // GeometryData is always shared and tracked per island. + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numIslands + indexIsland]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry in this island, try to create one on the home device. + { + geometryData = m_devicesActive[deviceHome]->createGeometry(geometry); + } + else + { + std::cout << "traverseNode() Geometry " << instanceData.idGeometry << " reused\n"; // DEBUG + } + + m_devicesActive[deviceHome]->createInstance(geometryData, instanceData, matrix); + + // Now share the GeometryData on the other devices in this island. + for (const auto device : island) + { + if (device != deviceHome) + { + // Create the instance referencing the shared GAS traversable on the peer device in this island. + // This is only host data. The IAS is created after gathering all flattened instances in the scene. + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numDevices + device]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry on this device, try to create one. + { + geometryData = m_devicesActive[device]->createGeometry(geometry); + } + + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + break; + } +} diff --git a/apps/bench_shared/src/SceneGraph.cpp b/apps/bench_shared/src/SceneGraph.cpp new file mode 100644 index 00000000..2cc34ee8 --- /dev/null +++ b/apps/bench_shared/src/SceneGraph.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/SceneGraph.h" + +#include +#include +#include + +#include "inc/MyAssert.h" + +namespace sg +{ + // ========== Node + Node::Node(const unsigned int id) + : m_id(id) + { + } + + //Node::~Node() + //{ + //} + + // ========== Group + Group::Group(const unsigned int id) + : Node(id) + { + } + + //Group::~Group() + //{ + //} + + sg::NodeType Group::getType() const + { + return NT_GROUP; + } + + void Group::addChild(std::shared_ptr instance) + { + m_children.push_back(instance); + } + + size_t Group::getNumChildren() const + { + return m_children.size(); + } + + std::shared_ptr Group::getChild(const size_t index) + { + MY_ASSERT(index < m_children.size()); + return m_children[index]; + } + + + // ========== Instance + Instance::Instance(const unsigned int id) + : Node(id) + , m_material(-1) // No material index set by default. Last one >= 0 along a path wins. + , m_light(-1) // No light index set by default. Not a light. + { + // Set the affine matrix to identity by default. + memset(m_matrix, 0, sizeof(float) * 12); + m_matrix[ 0] = 1.0f; + m_matrix[ 5] = 1.0f; + m_matrix[10] = 1.0f; + } + + //Instance::~Instance() + //{ + //} + + sg::NodeType Instance::getType() const + { + return NT_INSTANCE; + } + + void Instance::setTransform(const float m[12]) + { + memcpy(m_matrix, m, sizeof(float) * 12); + } + + const float* Instance::getTransform() const + { + return m_matrix; + } + + void Instance::setChild(std::shared_ptr node) // Instances can hold all other groups. + { + m_child = node; + } + + std::shared_ptr Instance::getChild() + { + return m_child; + } + + void Instance::setMaterial(const int index) + { + m_material = index; + } + + int Instance::getMaterial() const + { + return m_material; + } + + void Instance::setLight(const int index) + { + m_light = index; + } + + int Instance::getLight() const + { + return m_light; + } + + // ========== Triangles + Triangles::Triangles(const unsigned int id) + : Node(id) + { + } + + //Triangles::~Triangles() + //{ + //} + + sg::NodeType Triangles::getType() const + { + return NT_TRIANGLES; + } + + void Triangles::setAttributes(const std::vector& attributes) + { + m_attributes.resize(attributes.size()); + memcpy(m_attributes.data(), attributes.data(), sizeof(TriangleAttributes) * attributes.size()); + } + + const std::vector& Triangles::getAttributes() const + { + return m_attributes; + } + + void Triangles::setIndices(const std::vector& indices) + { + m_indices.resize(indices.size()); + memcpy(m_indices.data(), indices.data(), sizeof(unsigned int) * indices.size()); + } + + const std::vector& Triangles::getIndices() const + { + return m_indices; + } + + +} // namespace sg + diff --git a/apps/bench_shared/src/Sphere.cpp b/apps/bench_shared/src/Sphere.cpp new file mode 100644 index 00000000..1fd3391f --- /dev/null +++ b/apps/bench_shared/src/Sphere.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + void Triangles::createSphere(const unsigned int tessU, const unsigned int tessV, const float radius, const float maxTheta) + { + MY_ASSERT(3 <= tessU && 3 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + m_attributes.reserve((tessU + 1) * tessV); + m_indices.reserve(6 * tessU * (tessV - 1)); + + float phi_step = 2.0f * M_PIf / (float) tessU; + float theta_step = maxTheta / (float) (tessV - 1); + + // Latitudinal rings. + // Starting at the south pole going upwards on the y-axis. + for (unsigned int latitude = 0; latitude < tessV; ++latitude) // theta angle + { + float theta = (float) latitude * theta_step; + float sinTheta = sinf(theta); + float cosTheta = cosf(theta); + + float texv = (float) latitude / (float) (tessV - 1); // Range [0.0f, 1.0f] + + // Generate vertices along the latitudinal rings. + // On each latitude there are tessU + 1 vertices. + // The last one and the first one are on identical positions, but have different texture coordinates! + // FIXME Note that each second triangle connected to the two poles has zero area! + for (unsigned int longitude = 0; longitude <= tessU; ++longitude) // phi angle + { + float phi = (float) longitude * phi_step; + float sinPhi = sinf(phi); + float cosPhi = cosf(phi); + + float texu = (float) longitude / (float) tessU; // Range [0.0f, 1.0f] + + // Unit sphere coordinates are the normals. + float3 normal = make_float3(cosPhi * sinTheta, + -cosTheta, // -y to start at the south pole. + -sinPhi * sinTheta); + TriangleAttributes attrib; + + attrib.vertex = normal * radius; + attrib.tangent = make_float3(-sinPhi, 0.0f, -cosPhi); + attrib.normal = normal; + attrib.texcoord = make_float3(texu, texv, 0.0f); + + m_attributes.push_back(attrib); + } + } + + // We have generated tessU + 1 vertices per latitude. + const unsigned int columns = tessU + 1; + + // Calculate m_indices. + for (unsigned int latitude = 0; latitude < tessV - 1; ++latitude) + { + for (unsigned int longitude = 0; longitude < tessU; ++longitude) + { + m_indices.push_back( latitude * columns + longitude); // lower left + m_indices.push_back( latitude * columns + longitude + 1); // lower right + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + m_indices.push_back((latitude + 1) * columns + longitude); // upper left + m_indices.push_back( latitude * columns + longitude); // lower left + } + } + } + +} // namespace sg diff --git a/apps/bench_shared/src/Texture.cpp b/apps/bench_shared/src/Texture.cpp new file mode 100644 index 00000000..afb468cb --- /dev/null +++ b/apps/bench_shared/src/Texture.cpp @@ -0,0 +1,2150 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "shaders/vector_math.h" + +#include "inc/Device.h" +//#include "inc/Texture.h" // Device.h includes Texture.h +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include +#include + + +// The ENC_RED|GREEN|BLUE|ALPHA|LUM codes define from which source channel is read when writing R, G, B, A or L. +static unsigned int determineHostEncoding(int format, int type) // format and type are DevIL defines.. +{ + unsigned int encoding; + + switch (format) + { + case IL_RGB: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_3; + break; + case IL_RGBA: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; + break; + case IL_BGR: + encoding = ENC_RED_2 | ENC_GREEN_1 | ENC_BLUE_0 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_3; + break; + case IL_BGRA: + encoding = ENC_RED_2 | ENC_GREEN_1 | ENC_BLUE_0 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; + break; + case IL_LUMINANCE: + // encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_0 | ENC_CHANNELS_1; + encoding = ENC_RED_0 | ENC_GREEN_0 | ENC_BLUE_0 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_1; // Source RGB from L to expand to (L, L, L, 1). + break; + case IL_ALPHA: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_0 | ENC_LUM_NONE | ENC_CHANNELS_1; + break; + case IL_LUMINANCE_ALPHA: + // encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_1 | ENC_LUM_0 | ENC_CHANNELS_2; + encoding = ENC_RED_0 | ENC_GREEN_0 | ENC_BLUE_0 | ENC_ALPHA_1 | ENC_LUM_NONE | ENC_CHANNELS_2; // Source RGB from L to expand to (L, L, L, A). + break; + default: + MY_ASSERT(!"Unsupported user pixel format."); + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_INVALID; // Error! Invalid encoding. + break; + } + + switch (type) + { + case IL_UNSIGNED_BYTE: + encoding |= ENC_TYPE_UNSIGNED_CHAR; + break; + case IL_UNSIGNED_SHORT: + encoding |= ENC_TYPE_UNSIGNED_SHORT; + break; + case IL_UNSIGNED_INT: + encoding |= ENC_TYPE_UNSIGNED_INT; + break; + case IL_BYTE: + encoding |= ENC_TYPE_CHAR; + break; + case IL_SHORT: + encoding |= ENC_TYPE_SHORT; + break; + case IL_INT: + encoding |= ENC_TYPE_INT; + break; + case IL_FLOAT: + encoding |= ENC_TYPE_FLOAT; + break; + default: + MY_ASSERT(!"Unsupported user data format."); + encoding |= ENC_INVALID; // Error! Invalid encoding. + break; + } + + return encoding; +} + +// For OpenGL interop these formats are supported by CUDA according to the current manual on cudaGraphicsGLRegisterImage: +// GL_RED, GL_RG, GL_RGBA, GL_LUMINANCE, GL_ALPHA, GL_LUMINANCE_ALPHA, GL_INTENSITY +// {GL_R, GL_RG, GL_RGBA} x {8, 16, 16F, 32F, 8UI, 16UI, 32UI, 8I, 16I, 32I} +// {GL_LUMINANCE, GL_ALPHA, GL_LUMINANCE_ALPHA, GL_INTENSITY} x {8, 16, 16F_ARB, 32F_ARB, 8UI_EXT, 16UI_EXT, 32UI_EXT, 8I_EXT, 16I_EXT, 32I_EXT} + +// The following mapping is done for host textures. RGB formats will be expanded to RGBA. + +// While single and dual channel textures can easily be uploaded, the texture doesn't know what the destination format actually is, +// that is, a LUMINANCE_ALPHA texture returns the luminance in the red channel and the alpha in the green channel. +// That doesn't work the same way as OpenGL which copies luminance to all three RGB channels automatically. +// DAR DEBUG Check how the tex*<>(obj, ...) templates react when asking for more data than in the texture. +static unsigned int determineEncodingDevice(int format, int type) // format and type are DevIL defines. +{ + unsigned int encoding; + + switch (format) + { + case IL_RGB: + case IL_BGR: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE; // (R, G, B, 1) + break; + case IL_RGBA: + case IL_BGRA: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // (R, G, B, A) + break; + case IL_LUMINANCE: + //encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_0 | ENC_CHANNELS_1; // L in R + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE; // Expands to (L, L, L, 1) + break; + case IL_ALPHA: + //encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_0 | ENC_LUM_NONE | ENC_CHANNELS_1; // A in R + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // Expands to (0, 0, 0, A) + break; + case IL_LUMINANCE_ALPHA: + //encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_1 | ENC_LUM_0 | ENC_CHANNELS_2; // LA in RG + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // Expands to (L, L, L, A) + break; + default: + MY_ASSERT(!"Unsupported user pixel format."); + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_INVALID; // Error! Invalid encoding. + break; + } + + switch (type) + { + case IL_BYTE: + encoding |= ENC_TYPE_CHAR | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_BYTE: + encoding |= ENC_TYPE_UNSIGNED_CHAR | ENC_FIXED_POINT; + break; + case IL_SHORT: + encoding |= ENC_TYPE_SHORT | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_SHORT: + encoding |= ENC_TYPE_UNSIGNED_SHORT | ENC_FIXED_POINT; + break; + case IL_INT: + encoding |= ENC_TYPE_INT | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_INT: + encoding |= ENC_TYPE_UNSIGNED_INT | ENC_FIXED_POINT; + break; + case IL_FLOAT: + encoding |= ENC_TYPE_FLOAT; + break; + // FIXME Add IL_HALF for EXR images. Why are they loaded as IL_FLOAT (in DevIL 1.7.8)? + default: + MY_ASSERT(!"Unsupported user data format."); + encoding |= ENC_INVALID; // Error! Invalid encoding. + break; + } + + return encoding; +} + +// Helper function calculating the CUarray_format +static void determineFormatChannels(const unsigned int encodingDevice, CUarray_format& format, unsigned int& numChannels) +{ + const unsigned int type = encodingDevice & (ENC_MASK << ENC_TYPE_SHIFT); + + switch (type) + { + case ENC_TYPE_CHAR: + format = CU_AD_FORMAT_SIGNED_INT8; + break; + case ENC_TYPE_UNSIGNED_CHAR: + format = CU_AD_FORMAT_UNSIGNED_INT8; + break; + case ENC_TYPE_SHORT: + format = CU_AD_FORMAT_SIGNED_INT16; + break; + case ENC_TYPE_UNSIGNED_SHORT: + format = CU_AD_FORMAT_UNSIGNED_INT16; + break; + case ENC_TYPE_INT: + format = CU_AD_FORMAT_SIGNED_INT32; + break; + case ENC_TYPE_UNSIGNED_INT: + format = CU_AD_FORMAT_UNSIGNED_INT32; + break; + //case ENC_TYPE_HALF: // FIXME Implement. + // format = CU_AD_FORMAT_HALF; + // break; + case ENC_TYPE_FLOAT: + format = CU_AD_FORMAT_FLOAT; + break; + default: + MY_ASSERT(!"determineFormatChannels() Unexpected data type."); + break; + } + + numChannels = (encodingDevice >> ENC_CHANNELS_SHIFT) & ENC_MASK; +} + + +static unsigned int getElementSize(const unsigned int encodingDevice) +{ + unsigned int bytes = 0; + + const unsigned int type = encodingDevice & (ENC_MASK << ENC_TYPE_SHIFT); + switch (type) + { + case ENC_TYPE_CHAR: + case ENC_TYPE_UNSIGNED_CHAR: + bytes = 1; + break; + case ENC_TYPE_SHORT: + case ENC_TYPE_UNSIGNED_SHORT: + //case ENC_TYPE_HALF: // FIXME Implement. + bytes = 2; + break; + case ENC_TYPE_INT: + case ENC_TYPE_UNSIGNED_INT: + case ENC_TYPE_FLOAT: + bytes = 4; + break; + default: + MY_ASSERT(!"getElementSize() Unexpected data type."); + break; + } + + const unsigned int numChannels = (encodingDevice >> ENC_CHANNELS_SHIFT) & ENC_MASK; + + return bytes * numChannels; +} + + +// Texture format conversion routines. + +template +T getAlphaOne() +{ + return (std::numeric_limits::is_integer ? std::numeric_limits::max() : T(1)); +} + +// Fixed point adjustment for integer data D and S. +template +D adjust(S value) +{ + int dstBits = int(sizeof(D)) * 8; + int srcBits = int(sizeof(S)) * 8; + + D result = D(0); // Clear bits to allow OR operations. + + if (std::numeric_limits::is_signed) + { + if (std::numeric_limits::is_signed) + { + // D signed, S signed + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits)); + } + else + { + // Shift value into the most significant bits of result and replicate value into the lower bits until all are touched. + int shifts = dstBits - srcBits; + result = D(value << shifts); // This sets the destination sign bit as well. + value &= std::numeric_limits::max(); // Clear the sign bit inside the source value. + srcBits--; // Reduce the number of srcBits used to replicate the remaining data. + shifts -= srcBits; // Subtracting the now one smaller srcBits from shifts means the next shift will fill up with the remaining non-sign bits as intended. + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) // There can be one to three empty bits left blank in the result now. + { + result |= D(value >> -shifts); // Shift to the right to get the most significant bits of value into the least significant destination bits. + } + } + } + else + { + // D signed, S unsigned + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits + 1)); // + 1 because the destination is signed and the value needs to remain positive. + } + else + { + // Shift value into the most significant bits of result, keep the sign clear, and replicate value into the lower bits until all are touched. + int shifts = dstBits - srcBits - 1; // - 1 because the destination is signed and the value needs to remain positive. + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) + { + result |= D(value >> -shifts); + } + } + } + } + else + { + if (std::numeric_limits::is_signed) + { + // D unsigned, S signed + value = std::max(S(0), value); // Only the positive values will be transferred. + srcBits--; // Skip the sign bit. Means equal bit size won't happen here. + if (dstBits <= srcBits) // When it's really bigger it has at least 7 bits more, no need to care for dangling bits + { + result = D(value >> (srcBits - dstBits)); + } + else + { + int shifts = dstBits - srcBits; + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) + { + result |= D(value >> -shifts); + } + } + } + else + { + // D unsigned, S unsigned + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits)); + } + else + { + // Shift value into the most significant bits of result and replicate into the lower ones until all bits are touched. + int shifts = dstBits - srcBits; + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + // Both bit sizes are even multiples of 8, there are no trailing bits here. + MY_ASSERT(shifts == -srcBits); + } + } + } + return result; +} + + +template +void remapAdjust(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const S *psrc = reinterpret_cast(src); + D *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool fixedPoint = !!(dstEncoding & ENC_FIXED_POINT); + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) // Five possible channels: R, G, B, A, L + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + // If destination alpha was added to support this format or if no source data is given for alpha, fill it with 1. + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + if (s < 4) // There is data for this channel inside the source. (This could be a luminance to RGB mapping as well). + { + S value = psrc[s]; + pdst[d] = (fixedPoint) ? adjust(value) : D(value); + } + else // no value provided + { + pdst[d] = D(0); + } + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +// Straight channel copy with no adjustment. Since the data types match, fixed point doesn't matter. +template +void remapCopy(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const T *psrc = reinterpret_cast(src); + T *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) // Five possible channels: R, G, B, A, L + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + pdst[d] = (s < 4) ? psrc[s] : T(0); + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +template +void remapFromFloat(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const float *psrc = reinterpret_cast(src); + D *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool fixedPoint = !!(dstEncoding & ENC_FIXED_POINT); + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + if (s < 4) // This data channel exists inside the source. + { + float value = psrc[s]; + if (fixedPoint) + { + MY_ASSERT(std::numeric_limits::is_integer); // Destination with float format cannot be fixed point. + + float minimum = (std::numeric_limits::is_signed) ? -1.0f : 0.0f; + value = std::min(std::max(minimum, value), 1.0f); + pdst[d] = D(std::numeric_limits::max() * value); // Scaled copy. + } + else // element type, clamped copy. + { + float maximum = float(std::numeric_limits::max()); // This will run out of precision for int and unsigned int. + float minimum = -maximum; + pdst[d] = D(std::min(std::max(minimum, value), maximum)); + } + } + else // no value provided + { + pdst[d] = D(0); + } + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +template +void remapToFloat(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const S *psrc = reinterpret_cast(src); + float *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = 1.0f; + } + else + { + // If there is data for this channel just cast it straight in. + // This will run out of precision for int and unsigned int source data. + pdst[d] = (s < 4) ? float(psrc[s]) : 0.0f; + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + + +typedef void (*PFNREMAP)(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count); + +// Function table with 49 texture format conversion routines from loaded image data to supported CUDA texture formats. +// Index is [destination type][source type] +PFNREMAP remappers[7][7] = +{ + { + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapFromFloat + }, + { + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapCopy + } +}; + + +// Finally the function which converts any loaded image into a texture format supported by CUDA (1, 2, 4 channels only). +static void convert(void *dst, unsigned int encodingDevice, const void *src, unsigned int hostEncoding, size_t elements) +{ + // Only destination encoding knows about the fixed-point encoding. For straight data memcpy() cases that is irrelevant. + // FIXME PERF Avoid this conversion altogether when it's just a memcpy(). + if ((encodingDevice & ~ENC_FIXED_POINT) == hostEncoding) + { + memcpy(dst, src, elements * getElementSize(encodingDevice)); // The fastest path. + } + else + { + unsigned int dstType = (encodingDevice >> ENC_TYPE_SHIFT) & ENC_MASK; + unsigned int srcType = (hostEncoding >> ENC_TYPE_SHIFT) & ENC_MASK; + MY_ASSERT(dstType < 7 && srcType < 7); + + PFNREMAP pfn = remappers[dstType][srcType]; + + (*pfn)(dst, encodingDevice, src, hostEncoding, elements); + } +} + +Texture::Texture(Device* device) +: m_owner(device) +, m_width(0) +, m_height(0) +, m_depth(0) +, m_flags(0) +, m_encodingHost(ENC_INVALID) +, m_encodingDevice(ENC_INVALID) +, m_sizeBytesPerElement(0) +, m_textureObject(0) +, m_d_array(0) +, m_d_mipmappedArray(0) +, m_sizeBytesArray(0) +, m_d_envCDF_U(0) +, m_d_envCDF_V(0) +, m_integral(1.0f) +{ + m_descArray3D.Width = 0; + m_descArray3D.Height = 0; + m_descArray3D.Depth = 0; + m_descArray3D.Format = CU_AD_FORMAT_UNSIGNED_INT8; + m_descArray3D.NumChannels = 0; + m_descArray3D.Flags = 0; // Things like CUDA_ARRAY3D_SURFACE_LDST, ... + + // Clear the resource description once. They will be set inside the individual Texture::create*() functions. + memset(&m_resourceDescription, 0, sizeof(CUDA_RESOURCE_DESC)); + + // Note that cuTexObjectCreate() fails if the "int reserved[12]" data is not zero! Not mentioned in the CUDA documentation. + memset(&m_textureDescription, 0, sizeof(CUDA_TEXTURE_DESC)); + + // Setup CUDA_TEXTURE_DESC defaults. + // The developer can override these at will before calling Texture::create(). Afterwards they are immutable + // If the flag CU_TRSF_NORMALIZED_COORDINATES is not set, the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP. + m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_WRAP; + + m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + + // Possible flags: CU_TRSF_READ_AS_INTEGER, CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_SRGB + m_textureDescription.flags = CU_TRSF_NORMALIZED_COORDINATES; + + m_textureDescription.maxAnisotropy = 1; + + // LOD 0 only by default. + // This means when using mipmaps it's the developer's responsibility to set at least + // maxMipmapLevelClamp > 0.0f before calling Texture::create() to make sure mipmaps can be sampled! + m_textureDescription.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + m_textureDescription.mipmapLevelBias = 0.0f; + m_textureDescription.minMipmapLevelClamp = 0.0f; + m_textureDescription.maxMipmapLevelClamp = 0.0f; // This should be set to Picture::getNumberOfLevels() when using mipmaps. + + m_textureDescription.borderColor[0] = 0.0f; + m_textureDescription.borderColor[1] = 0.0f; + m_textureDescription.borderColor[2] = 0.0f; + m_textureDescription.borderColor[3] = 0.0f; +} + +Texture::~Texture() +{ + // Note that destroy() handles the shared data destruction. + if (m_textureObject) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(m_textureObject) ); + } +} + +size_t Texture::destroy(Device* device) +{ + if (m_owner == device) + { + if (m_d_array) + { + CU_CHECK_NO_THROW( cuArrayDestroy(m_d_array) ); + } + if (m_d_mipmappedArray) + { + CU_CHECK_NO_THROW( cuMipmappedArrayDestroy(m_d_mipmappedArray) ); + } + + // DAR FIXME Move these into a derived TextureEnvironment class. + if (m_d_envCDF_U) + { + m_owner->memFree(m_d_envCDF_U); + } + if (m_d_envCDF_V) + { + m_owner->memFree(m_d_envCDF_V); + } + + return m_sizeBytesArray; // Return the size of the CUarray or CUmipmappedArray data in bytes for memory tracking. + } + + return 0; +} + +// For all functions changing the m_textureDescription values, +// make sure they are called before the texture object has been created, +// otherwise the texture object would need to be recreated. + +// Set the whole description. +void Texture::setTextureDescription(const CUDA_TEXTURE_DESC& descr) +{ + m_textureDescription = descr; +} + +void Texture::setAddressMode(CUaddress_mode s, CUaddress_mode t, CUaddress_mode r) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.addressMode[0] = s; + m_textureDescription.addressMode[1] = t; + m_textureDescription.addressMode[2] = r; +} + +void Texture::setFilterMode(CUfilter_mode filter, CUfilter_mode filterMipmap) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.filterMode = filter; + m_textureDescription.mipmapFilterMode = filterMipmap; +} + +void Texture::setBorderColor(float r, float g, float b, float a) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.borderColor[0] = r; + m_textureDescription.borderColor[1] = g; + m_textureDescription.borderColor[2] = b; + m_textureDescription.borderColor[3] = a; +} + +void Texture::setMaxAnisotropy(unsigned int aniso) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.maxAnisotropy = aniso; +} + +void Texture::setMipmapLevelBiasMinMax(float bias, float minimum, float maximum) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.mipmapLevelBias = bias; + m_textureDescription.minMipmapLevelClamp = minimum; + m_textureDescription.maxMipmapLevelClamp = maximum; +} + +void Texture::setReadMode(bool asInteger) +{ + MY_ASSERT(m_textureObject == 0); + + if (asInteger) + { + m_textureDescription.flags |= CU_TRSF_READ_AS_INTEGER; + } + else + { + m_textureDescription.flags &= ~CU_TRSF_READ_AS_INTEGER; + } +} + +void Texture::setSRGB(bool srgb) +{ + MY_ASSERT(m_textureObject == 0); + + if (srgb) + { + m_textureDescription.flags |= CU_TRSF_SRGB; + } + else + { + m_textureDescription.flags &= ~CU_TRSF_SRGB; + } +} + +void Texture::setNormalizedCoords(bool normalized) +{ + MY_ASSERT(m_textureObject == 0); + + if (normalized) + { + m_textureDescription.flags |= CU_TRSF_NORMALIZED_COORDINATES; // Default in this app. + } + else + { + // Note that if the flag CU_TRSF_NORMALIZED_COORDINATES is not set, + // the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP! + m_textureDescription.flags &= ~CU_TRSF_NORMALIZED_COORDINATES; + } +} + + +bool Texture::create1D(const Picture* picture) +{ + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = 0; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Depth = m_depth; // Mind that the layers are always defined via the depth extent. + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + // I don't know of a program which creates image files with 1D layered mipmapped textures, but the Picture class handles that just fine. + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 1D (layered) mipmapped texture. + { + // A 1D mipmapped array is allocated if Height and Depth extents are both zero. + // A 1D layered CUDA mipmapped array is allocated if only Height is zero and the CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 1D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 1D (layered) texture. + { + // A 1D array is allocated if the height and depth extents are both zero. + // A 1D layered CUDA array is allocated if only the height extent is zero and the cudaArrayLayered flag is set. + // Each layer is a 1D array. The number of layers is determined by the depth extent. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +bool Texture::create2D(const Picture* picture) +{ + // Default initialization for a 2D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Depth = m_depth; // Mind that the layers are always defined via the depth extent. + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 2D (layered) mipmapped texture // FIXME Add a mechanism to generate mipmaps if there are none. + { + // A 2D mipmapped array is allocated if only Depth extent is zero. + // A 2D layered CUDA mipmapped array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 2D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 2D (layered) texture. + { + // A 2D array is allocated if only Depth extent is zero. + // A 2D layered CUDA array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 2D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::create3D(const Picture* picture) +{ + MY_ASSERT((m_flags & IMAGE_FLAG_LAYER) == 0); // There are no layered 3D textures. The flag is ignored. + + // Default initialization for a 3D texture. There are no layers! + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = m_depth; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 3D mipmapped texture + { + // A 3D mipmapped array is allocated if all three extents are non-zero. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 3D texture. + { + // A 3D array is allocated if all three extents are non-zero. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_height * m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::createCube(const Picture* picture) +{ + if (!picture->isCubemap()) // This implies picture->getNumberOfImages() == 6. + { + std::cerr << "ERROR: Texture::createCube() picture is not a cubemap.\n"; + return false; + } + + if (m_width != m_height || m_depth % 6 != 0) + { + std::cerr << "ERROR: Texture::createCube() invalid cubemap image dimensions (" << m_width << ", " << m_height << ", " << m_depth << ")\n"; + return false; + } + + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = m_depth; // depth == 6 * layers. + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = CUDA_ARRAY3D_CUBEMAP; + + const unsigned int numLayers = m_depth / 6; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags |= CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; // LOD 0 size in bytes. + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // cubemap (layered) mipmapped texture + { + // A cubemap CUDA mipmapped array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_CUBEMAP flag is set. + // Width must be equal to \p Height, and Depth must be six. + // A cubemap is a special type of 2D layered CUDA array, where the six layers represent the six faces of a cube. + // The order of the six layers in memory is the same as that listed in ::CUarray_cubemap_face. (+x, -x, +y, -y, +z, -z) + // A cubemap layered CUDA mipmapped array is allocated if all three extents are non-zero, and both, ::CUDA_ARRAY3D_CUBEMAP and ::CUDA_ARRAY3D_LAYERED flags are set. + // Width must be equal to Height, and Depth must be a multiple of six. + // A cubemap layered CUDA array is a special type of 2D layered CUDA array that consists of a collection of cubemaps. + // The first six layers represent the first cubemap, the next six layers form the second cubemap, and so on. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + const Image* image; // The last image of each level defines the extent. + + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + for (unsigned int face = 0; face < 6; ++face) + { + image = picture->getImageLevel(face, level); // image face, LOD level + + const size_t sizeElementsLayer = image->m_width * image->m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytesLayer * numLayers; // Memory tracking. + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // cubemap (layered) texture. + { + // A cubemap CUDA array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_CUBEMAP flag is set. + // Width must be equal to Height, and Depth must be six. + // A cubemap is a special type of 2D layered CUDA array, where the six layers represent the six faces of a cube. + // The order of the six layers in memory is the same as that listed in ::CUarray_cubemap_face. (+x, -x, +y, -y, +z, -z) + // A cubemap layered CUDA array is allocated if all three extents are non-zero, and both, ::CUDA_ARRAY3D_CUBEMAP and ::CUDA_ARRAY3D_LAYERED flags are set. + // Width must be equal to Height, and Depth must be a multiple of six. + // A cubemap layered CUDA array is a special type of 2D layered CUDA array that consists of a collection of cubemaps. + // The first six layers represent the first cubemap, the next six layers form the second cubemap, and so on. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + for (unsigned int face = 0; face < 6; ++face) + { + const Image* image = picture->getImageLevel(face, 0); // image face, LOD 0 + + const size_t sizeElementsLayer = m_width * m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytesLayer * numLayers; // Memory tracking. + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::createEnv(const Picture* picture) +{ + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + //size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeElements * m_sizeBytesPerElement; // Memory tracking. + + float* data = new float[sizeElements * 4]; // RGBA32F + + // A 2D array is allocated if only Depth extent is zero. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + + // Generate the CDFs for direct environment lighting and the environment texture sampler itself. + calculateSphericalCDF(data); + + delete[] data; + + // Setup CUDA_TEXTURE_DESC for the spherical environment. + // The defaults are set for a bilinear filtered 2D texture already. + // The spherical environment texture only needs to change the addessmode[1] to clamp. + //m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + //m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_WRAP; + + //m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + + //m_textureDescription.flags = CU_TRSF_NORMALIZED_COORDINATES; + + //m_textureDescription.maxAnisotropy = 1; + + //m_textureDescription.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + //m_textureDescription.mipmapLevelBias = 0.0f; + //m_textureDescription.minMipmapLevelClamp = 0.0f; + //m_textureDescription.maxMipmapLevelClamp = 0.0f; // This should be set to Picture::getNumberOfLevels() when using mipmaps. + + //m_textureDescription.borderColor[0] = 0.0f; + //m_textureDescription.borderColor[1] = 0.0f; + //m_textureDescription.borderColor[2] = 0.0f; + //m_textureDescription.borderColor[3] = 0.0f; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +// The Texture::create() functions set up all immutable members. +// The Texture::update() functions expect the exact same input and only upload new CUDA array data. +bool Texture::create(const Picture* picture, const unsigned int flags) +{ + bool success = false; + + if (m_textureObject != 0) + { + std::cerr << "ERROR: Texture::create() texture object already created.\n"; + return success; + } + + if (picture == nullptr) + { + std::cerr << "ERROR: Texture::create() called with nullptr picture.\n"; + return success; + } + + // The LOD 0 image of the first face defines the basic settings, including the texture m_width, m_height, m_depth. + // This returns nullptr when this image doesn't exist. Everything else in this function relies on it. + const Image* image = picture->getImageLevel(0, 0); + + if (image == nullptr) + { + std::cerr << "ERROR: Texture::create() Picture doesn't contain image 0 level 0.\n"; + return success; + } + + // Precalculate some values which are required for all create*() functions. + m_encodingHost = determineHostEncoding(image->m_format, image->m_type); + + m_flags = flags; + + if (m_flags & IMAGE_FLAG_ENV) + { + // Hardcode the device encoding to a floating point HDR image. The input is expected to be an HDR or EXR image. + // Fixed point LDR textures will remain unnormalized, e.g. unsigned byte 255 will be converted to float 255.0f. + // (Just because there is no suitable conversion routine implemented for that.) + m_encodingDevice = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE | ENC_TYPE_FLOAT; + } + else + { + m_encodingDevice = determineEncodingDevice(image->m_format, image->m_type); + } + + if ((m_encodingHost | m_encodingDevice) & ENC_INVALID) // If either of the encodings is invalid, bail out. + { + return false; + } + + m_sizeBytesPerElement = getElementSize(m_encodingDevice); + + if (m_flags & IMAGE_FLAG_1D) + { + m_width = image->m_width; + m_height = 1; + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth : 1; + success = create1D(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == IMAGE_FLAG_2D) // Standard 2D texture. + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth : 1; + success = create2D(picture); + } + else if (m_flags & IMAGE_FLAG_3D) + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = image->m_depth; + success = create3D(picture); + } + else if (m_flags & IMAGE_FLAG_CUBE) + { + m_width = image->m_width; + m_height = image->m_height; + // Note that the Picture class holds the six cubemap faces in six separate mipmap chains! + // The LOD 0 image depth is the number of layers. The resulting 3D image is six times that depth. + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth * 6 : 6; + success = createCube(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) // 2D spherical environment texture. + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = 1; // No layers for the environment map. + success = createEnv(picture); + } + + MY_ASSERT(success); + return success; +} + + +// Texture peer-to-peer sharing! +// Note that the m_owner field indicates which device originally allocated the memory! +// Use all other data of the shared texture on the peer device. Only the m_textureObject is per device! +bool Texture::create(const Texture* shared) +{ + // Copy all elements from the existing shared texture. + // This includes the owner which is the peer device, not the currently active one! + *this = *shared; + + m_textureObject = 0; // Clear the copied texture object. + + // Only create the m_textureObject per Device by using the resource and texture descriptions + // just copied from the shared texture which contains the device pointers to the shared array data. + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +Device* Texture::getOwner() const +{ + return m_owner; +} + +unsigned int Texture::getWidth() const +{ + return m_width; +} + +unsigned int Texture::getHeight() const +{ + return m_height; +} + +unsigned int Texture::getDepth() const +{ + return m_depth; +} + +cudaTextureObject_t Texture::getTextureObject() const +{ + return m_textureObject; +} + +size_t Texture::getSizeBytes() const +{ + return m_sizeBytesArray; +} + +// The following functions are used to build the data needed for an importance sampled spherical HDR environment map. + +// Implement a simple Gaussian 3x3 filter with sigma = 0.5 +// Needed for the CDF generation of the importance sampled HDR environment texture light. +static float gaussianFilter(const float* rgba, unsigned int width, unsigned int height, unsigned int x, unsigned int y) +{ + // Lookup is repeated in x and clamped to edge in y. + unsigned int left = (0 < x) ? x - 1 : width - 1; // repeat + unsigned int right = (x < width - 1) ? x + 1 : 0; // repeat + unsigned int bottom = (0 < y) ? y - 1 : y; // clamp + unsigned int top = (y < height - 1) ? y + 1 : y; // clamp + + // Center + const float *p = rgba + (width * y + x) * 4; + float intensity = (p[0] + p[1] + p[2]) * 0.619347f; + + // 4-neighbours + p = rgba + (width * bottom + x) * 4; + float f = p[0] + p[1] + p[2]; + p = rgba + (width * y + left) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * y + right) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + x) * 4; + f += p[0] + p[1] + p[2]; + intensity += f * 0.0838195f; + + // 8-neighbours corners + p = rgba + (width * bottom + left) * 4; + f = p[0] + p[1] + p[2]; + p = rgba + (width * bottom + right) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + left) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + right) * 4; + f += p[0] + p[1] + p[2]; + intensity += f * 0.0113437f; + + return intensity / 3.0f; +} + +// Create cumulative distribution function for importance sampling of spherical environment lights. +// This is a textbook implementation for the CDF generation of a spherical HDR environment. +// See "Physically Based Rendering" v2, chapter 14.6.5 on Infinite Area Lights. +void Texture::calculateSphericalCDF(const float* rgba) +{ + // The original data needs to be retained to calculate the PDF. + float *funcU = new float[m_width * m_height]; + float *funcV = new float[m_height + 1]; + + float sum = 0.0f; + // First generate the function data. + for (unsigned int y = 0; y < m_height; ++y) + { + // Scale distibution by the sine to get the sampling uniform. (Avoid sampling more values near the poles.) + // See Physically Based Rendering v2, chapter 14.6.5 on Infinite Area Lights, page 728. + float sinTheta = float(sin(M_PI * (double(y) + 0.5) / double(m_height))); // Make this as accurate as possible. + + for (unsigned int x = 0; x < m_width; ++x) + { + // Filter to keep the piecewise linear function intact for samples with zero value next to non-zero values. + const float value = gaussianFilter(rgba, m_width, m_height, x, y); + funcU[y * m_width + x] = value * sinTheta; + + // Compute integral over the actual function. + const float *p = rgba + (y * m_width + x) * 4; + const float intensity = (p[0] + p[1] + p[2]) / 3.0f; + sum += intensity * sinTheta; + } + } + + // This integral is used inside the light sampling function (see sysData.envIntegral). + m_integral = sum * 2.0f * M_PIf * M_PIf / float(m_width * m_height); + + // Now generate the CDF data. + // Normalized 1D distributions in the rows of the 2D buffer, and the marginal CDF in the 1D buffer. + // Include the starting 0.0f and the ending 1.0f to avoid special cases during the continuous sampling. + float *cdfU = new float[(m_width + 1) * m_height]; + float *cdfV = new float[m_height + 1]; + + for (unsigned int y = 0; y < m_height; ++y) + { + unsigned int row = y * (m_width + 1); // Watch the stride! + cdfU[row + 0] = 0.0f; // CDF starts at 0.0f. + + for (unsigned int x = 1; x <= m_width; ++x) + { + unsigned int i = row + x; + cdfU[i] = cdfU[i - 1] + funcU[y * m_width + x - 1]; // Attention, funcU is only m_width wide! + } + + const float integral = cdfU[row + m_width]; // The integral over this row is in the last element. + funcV[y] = integral; // Store this as function values of the marginal CDF. + + if (integral != 0.0f) + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] /= integral; + } + } + else // All texels were black in this row. Generate an equal distribution. + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] = float(x) / float(m_width); + } + } + } + + // Now do the same thing with the marginal CDF. + cdfV[0] = 0.0f; // CDF starts at 0.0f. + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = cdfV[y - 1] + funcV[y - 1]; + } + + const float integral = cdfV[m_height]; // The integral over this marginal CDF is in the last element. + funcV[m_height] = integral; // For completeness, actually unused. + + if (integral != 0.0f) + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] /= integral; + } + } + else // All texels were black in the whole image. Seriously? :-) Generate an equal distribution. + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = float(y) / float(m_height); + } + } + + // Upload the CDFs into CUDA buffers. + size_t sizeBytes = (m_width + 1) * m_height * sizeof(float); + m_d_envCDF_U = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_envCDF_U, cdfU, sizeBytes) ); + + sizeBytes = (m_height + 1) * sizeof(float); + m_d_envCDF_V = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_envCDF_V, cdfV, sizeBytes) ); + + delete[] cdfV; + delete[] cdfU; + + delete[] funcV; + delete[] funcU; +} + +CUdeviceptr Texture::getCDF_U() const +{ + return m_d_envCDF_U; +} + +CUdeviceptr Texture::getCDF_V() const +{ + return m_d_envCDF_V; +} + +float Texture::getIntegral() const +{ + // This is the sum of the piecewise linear function values (roughly the texels' intensity) divided by the number of texels m_width * m_height. + return m_integral; +} + + +bool Texture::update1D(const Picture* picture) +{ + size_t sizeElements = m_width; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 1D (layered) mipmapped texture. + { + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 1D (layered) texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + + +bool Texture::update2D(const Picture* picture) +{ + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 2D (layered) mipmapped texture // FIXME Add a mechanism to generate mipmaps if there are none. + { + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 2D (layered) texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::update3D(const Picture* picture) +{ + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 3D mipmapped texture + { + // A 3D mipmapped array is allocated if all three extents are non-zero. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 3D texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_height * m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::updateCube(const Picture* picture) +{ + if (!picture->isCubemap()) // isCubemap() implies picture->getNumberOfImages() == 6. + { + std::cerr << "ERROR: Texture::updateCube() picture is not a cubemap.\n"; + return false; + } + + if (m_width != m_height || m_depth % 6 != 0) + { + std::cerr << "ERROR: Texture::updateCube() invalid cubemap image dimensions (" << m_width << ", " << m_height << ", " << m_depth << ")\n"; + return false; + } + + const unsigned int numLayers = m_depth / 6; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags |= CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; // LOD 0 size in bytes. + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // cubemap (layered) mipmapped texture + { + for (unsigned int level = 0; level < numLevels; ++level) + { + const Image* image; // The last image of each level defines the extent. + + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + for (unsigned int face = 0; face < 6; ++face) + { + image = picture->getImageLevel(face, level); // image face, LOD level + + const size_t sizeElementsLayer = image->m_width * image->m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // cubemap (layered) texture. + { + for (unsigned int face = 0; face < 6; ++face) + { + const Image* image = picture->getImageLevel(face, 0); // image face, LOD 0 + + const size_t sizeElementsLayer = m_width * m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::updateEnv(const Picture* picture) +{ + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + //size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + float* data = new float[sizeElements * 4]; // RGBA32F + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + + // Generate the CDFs for direct environment lighting and the environment texture sampler itself. + calculateSphericalCDF(data); + + delete[] data; + + return (m_textureObject != 0); +} + + +bool Texture::update(const Picture* picture) +{ + bool success = false; + + if (m_textureObject == 0) + { + std::cerr << "ERROR: Texture::update() texture object not reated.\n"; + return success; + } + + if (picture == nullptr) + { + std::cerr << "ERROR: Texture::update() called with nullptr picture.\n"; + return success; + } + + // The LOD 0 image of the first face defines the basic settings, including the texture m_width, m_height, m_depth. + // This returns nullptr when this image doesn't exist. Everything else in this function relies on it. + const Image* image = picture->getImageLevel(0, 0); + + if (image == nullptr) + { + std::cerr << "ERROR: Texture::update() Picture doesn't contain image 0 level 0.\n"; + return success; + } + + const unsigned int hostEncoding = determineHostEncoding(image->m_format, image->m_type); + + if (m_encodingHost != hostEncoding) + { + std::cerr << "ERROR: Texture::update() Picture host encoding doesn't match existing texture\n"; + return success; + } + + if (m_flags & IMAGE_FLAG_1D) + { + success = update1D(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == IMAGE_FLAG_2D) // Standard 2D texture. + { + success = update2D(picture); + } + else if (m_flags & IMAGE_FLAG_3D) + { + success = update3D(picture); + } + else if (m_flags & IMAGE_FLAG_CUBE) + { + success = updateCube(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) // 2D spherical environment texture. + { + success = updateEnv(picture); + } + + MY_ASSERT(success); + return success; +} diff --git a/apps/bench_shared/src/Timer.cpp b/apps/bench_shared/src/Timer.cpp new file mode 100644 index 00000000..2a2c0451 --- /dev/null +++ b/apps/bench_shared/src/Timer.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2011-2019, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +// This code is part of the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + +#include "inc/Timer.h" + +#if defined(_WIN32) +# define GETTIME(x) QueryPerformanceCounter(x) +#else +# define GETTIME(x) gettimeofday( x, 0 ) +#endif + +Timer::Timer() + : m_running(false) + , m_seconds(0) +{ +#if defined(_WIN32) + QueryPerformanceFrequency(&m_freq); +#endif +} + +Timer::~Timer() +{ +} + +void Timer::start() +{ + if( !m_running ) + { + m_running = true; + // starting a timer: store starting time last + GETTIME( &m_begin ); + } +} + +void Timer::stop() +{ + // stopping a timer: store stopping time first + Time tmp; + GETTIME( &tmp ); + if( m_running ) + { + m_seconds += calcDuration(m_begin, tmp); + m_running = false; + } +} + +void Timer::reset() +{ + m_running = false; + m_seconds = 0; +} + +void Timer::restart() +{ + reset(); + start(); +} + +double Timer::getTime() const +{ + Time tmp; + GETTIME( &tmp ); + if( m_running ) + { + return m_seconds + calcDuration(m_begin, tmp); + } + else + { + return m_seconds; + } +} + +double Timer::calcDuration(Time begin, Time end) const +{ + double seconds; +#if defined(_WIN32) + LARGE_INTEGER diff; + diff.QuadPart = (end.QuadPart - begin.QuadPart); + seconds = (double)diff.QuadPart / (double)m_freq.QuadPart; +#else + timeval diff; + if( begin.tv_usec <= end.tv_usec ) + { + diff.tv_sec = end.tv_sec - begin.tv_sec; + diff.tv_usec = end.tv_usec - begin.tv_usec; + } + else + { + diff.tv_sec = end.tv_sec - begin.tv_sec - 1; + diff.tv_usec = end.tv_usec - begin.tv_usec + (int)1e6; + } + seconds = diff.tv_sec + diff.tv_usec/1e6; +#endif + return seconds; +} diff --git a/apps/bench_shared/src/Torus.cpp b/apps/bench_shared/src/Torus.cpp new file mode 100644 index 00000000..d92b21a9 --- /dev/null +++ b/apps/bench_shared/src/Torus.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // The torus is a ring with radius outerRadius rotated around the y-axis along the circle with innerRadius. + /* +y + ___ | ___ + / \ / \ + | | | | | + | | | | + \ ___ / | \ ___ / + <---> + outerRadius + <-------> + innerRadius + */ + void Triangles::createTorus(const unsigned int tessU, const unsigned int tessV, const float innerRadius, const float outerRadius) + { + MY_ASSERT(3 <= tessU && 3 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + m_attributes.reserve((tessU + 1) * (tessV + 1)); + m_indices.reserve(8 * tessU * tessV); + + const float u = (float) tessU; + const float v = (float) tessV; + + float phi_step = 2.0f * M_PIf / u; + float theta_step = 2.0f * M_PIf / v; + + // Setup vertices and normals. + // Generate the torus exactly like the sphere with rings around the origin along the latitudes. + for (unsigned int latitude = 0; latitude <= tessV; ++latitude) // theta angle + { + const float theta = (float) latitude * theta_step; + const float sinTheta = sinf(theta); + const float cosTheta = cosf(theta); + + const float radius = innerRadius + outerRadius * cosTheta; + + for (unsigned int longitude = 0; longitude <= tessU; ++longitude) // phi angle + { + const float phi = (float) longitude * phi_step; + const float sinPhi = sinf(phi); + const float cosPhi = cosf(phi); + + TriangleAttributes attrib; + + attrib.vertex = make_float3(radius * cosPhi, outerRadius * sinTheta, radius * -sinPhi); + attrib.tangent = make_float3(-sinPhi, 0.0f, -cosPhi); + attrib.normal = make_float3(cosPhi * cosTheta, sinTheta, -sinPhi * cosTheta); + attrib.texcoord = make_float3((float) longitude / u, (float) latitude / v, 0.0f); + + m_attributes.push_back(attrib); + } + } + + // We have generated tessU + 1 vertices per latitude. + const int columns = tessU + 1; + + // Setup m_indices + for (unsigned int latitude = 0; latitude < tessV; ++latitude) + { + for (unsigned int longitude = 0; longitude < tessU; ++longitude) + { + m_indices.push_back(latitude * columns + longitude); // lower left + m_indices.push_back(latitude * columns + longitude + 1); // lower right + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + m_indices.push_back((latitude + 1) * columns + longitude); // upper left + m_indices.push_back(latitude * columns + longitude); // lower left + } + } + } + +} // namespace sg diff --git a/apps/bench_shared/src/main.cpp b/apps/bench_shared/src/main.cpp new file mode 100644 index 00000000..5b3dbe0b --- /dev/null +++ b/apps/bench_shared/src/main.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Application.h" + +#include + +#include +#include + + +static Application* g_app = nullptr; + +static void callbackError(int error, const char* description) +{ + std::cerr << "ERROR: "<< error << ": " << description << '\n'; +} + + +static int runApp(const Options& options) +{ + int width = std::max(1, options.getWidth()); + int height = std::max(1, options.getHeight()); + + GLFWwindow* window = glfwCreateWindow(width, height, "bench_shared - Copyright (c) 2021 NVIDIA Corporation", NULL, NULL); + if (!window) + { + callbackError(APP_ERROR_CREATE_WINDOW, "glfwCreateWindow() failed."); + return APP_ERROR_CREATE_WINDOW; + } + + glfwMakeContextCurrent(window); + + if (glewInit() != GL_NO_ERROR) + { + callbackError(APP_ERROR_GLEW_INIT, "GLEW failed to initialize."); + return APP_ERROR_GLEW_INIT; + } + + ilInit(); // Initialize DevIL once. + + g_app = new Application(window, options); + + if (!g_app->isValid()) + { + std::cerr << "ERROR: Application() failed to initialize successfully.\n"; + ilShutDown(); + return APP_ERROR_APP_INIT; + } + + const int mode = std::max(0, options.getMode()); + + if (mode == 0) // Interactive, default. + { + // Main loop + bool finish = false; + while (!finish && !glfwWindowShouldClose(window)) + { + glfwPollEvents(); // Render continuously. Battery drainer! + + glfwGetFramebufferSize(window, &width, &height); + + g_app->reshape(width, height); + g_app->guiNewFrame(); + //g_app->guiReferenceManual(); // HACK The ImGUI "Programming Manual" as example code. + g_app->guiWindow(); // This application's GUI window rendering commands. + g_app->guiEventHandler(); // SPACE to toggle the GUI windows and all mouse tracking via GuiState. + finish = g_app->render(); // OptiX rendering, returns true when benchmark is enabled and the samples per pixel have been rendered. + g_app->display(); // OpenGL display always required to lay the background for the GUI. + g_app->guiRender(); // Render all ImGUI elements at last. + + glfwSwapBuffers(window); + + //glfwWaitEvents(); // Render only when an event is happening. Needs some glfwPostEmptyEvent() to prevent GUI lagging one frame behind when ending an action. + } + } + else if (mode == 1) // Batched benchmark single shot. // FIXME When not using anything OpenGL, the whole window and OpenGL setup could be removed. + { + g_app->benchmark(); + } + + delete g_app; + + ilShutDown(); + + return APP_EXIT_SUCCESS; +} + + +int main(int argc, char *argv[]) +{ + glfwSetErrorCallback(callbackError); + + if (!glfwInit()) + { + callbackError(APP_ERROR_GLFW_INIT, "GLFW failed to initialize."); + return APP_ERROR_GLFW_INIT; + } + + int result = APP_ERROR_UNKNOWN; + + Options options; + + if (options.parseCommandLine(argc, argv)) + { + result = runApp(options); + } + + glfwTerminate(); + + return result; +} diff --git a/apps/bench_shared_offscreen/CMakeLists.txt b/apps/bench_shared_offscreen/CMakeLists.txt new file mode 100644 index 00000000..a5c98510 --- /dev/null +++ b/apps/bench_shared_offscreen/CMakeLists.txt @@ -0,0 +1,273 @@ +# Copyright (c) 2013-2022, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +# FindCUDA.cmake is deprecated since CMake 3.10. +# Use FindCUDAToolkit.cmake added in CMake 3.17 instead. +cmake_minimum_required(VERSION 3.17) + +project( bench_shared_offscreen ) +message("\nPROJECT_NAME = " "${PROJECT_NAME}") + +find_package(OpenGL REQUIRED) +find_package(GLFW REQUIRED) +find_package(GLEW REQUIRED) +find_package(CUDAToolkit 10.0 REQUIRED) +find_package(DevIL_1_8_0 REQUIRED) +find_package(ASSIMP REQUIRED) + +# OptiX SDK 7.x and 8.x versions are searched inside the top-level CMakeLists.txt. +# Make the build work with all currently released OptiX SDK 7.x and 8.x versions. +if(OptiX80_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX80_INCLUDE_DIR}") +elseif(OptiX77_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX77_INCLUDE_DIR}") +elseif(OptiX76_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX76_INCLUDE_DIR}") +elseif(OptiX75_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX75_INCLUDE_DIR}") +elseif(OptiX74_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX74_INCLUDE_DIR}") +elseif(OptiX73_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX73_INCLUDE_DIR}") +elseif(OptiX72_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX72_INCLUDE_DIR}") +elseif(OptiX71_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX71_INCLUDE_DIR}") +elseif(OptiX70_FOUND) + set(OPTIX_INCLUDE_DIR "${OPTIX70_INCLUDE_DIR}") +else() + message(FATAL_ERROR "No OptiX SDK 7.x found.") +endif() +#message("OPTIX_INCLUDE_DIR = " "${OPTIX_INCLUDE_DIR}") + +# OptiX SDK 7.5.0 and CUDA 11.7 added support for a new OptiX IR target, which is a binary intermediate format for the module input. +# The default module build target is PTX. +set(USE_OPTIX_IR FALSE) +set(OPTIX_MODULE_EXTENSION ".ptx") +set(OPTIX_PROGRAM_TARGET "--ptx") + +if (OptiX80_FOUND OR OptiX77_FOUND OR OptiX76_FOUND OR OptiX75_FOUND) + # Define USE_OPTIX_IR and change the target to OptiX IR if the combination of OptiX SDK and CUDA Toolkit versions supports this mode. + if ((${CUDAToolkit_VERSION_MAJOR} GREATER 11) OR ((${CUDAToolkit_VERSION_MAJOR} EQUAL 11) AND (${CUDAToolkit_VERSION_MINOR} GREATER_EQUAL 7))) + set(USE_OPTIX_IR TRUE) + set(OPTIX_MODULE_EXTENSION ".optixir") + set(OPTIX_PROGRAM_TARGET "--optix-ir") + endif() +endif() + +set( IMGUI + imgui/imconfig.h + imgui/imgui.h + imgui/imgui_impl_glfw_gl3.h + imgui/imgui_internal.h + imgui/stb_rect_pack.h + imgui/stb_textedit.h + imgui/stb_truetype.h + imgui/imgui.cpp + imgui/imgui_demo.cpp + imgui/imgui_draw.cpp + imgui/imgui_impl_glfw_gl3.cpp +) + +# Reusing some routines from the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline +# Not built as a library, just using classes and functions directly. +# Asserts replaced with my own versions. +set( NVPRO_MATH + # Math functions: + dp/math/Config.h + dp/math/math.h + dp/math/Matmnt.h + dp/math/Quatt.h + dp/math/Trafo.h + dp/math/Vecnt.h + dp/math/src/Math.cpp + dp/math/src/Matmnt.cpp + dp/math/src/Quatt.cpp + dp/math/src/Trafo.cpp +) + +set( HEADERS + inc/Application.h + inc/Arena.h + inc/Camera.h + inc/CheckMacros.h + inc/Device.h + inc/MaterialGUI.h + inc/MyAssert.h + inc/NVMLImpl.h + inc/Options.h + inc/Parser.h + inc/Picture.h + #inc/Rasterizer.h + inc/Raytracer.h + inc/SceneGraph.h + inc/Texture.h + inc/Timer.h + inc/TonemapperGUI.h +) + +set( SOURCES + src/Application.cpp + src/Arena.cpp + src/Assimp.cpp + src/Box.cpp + src/Camera.cpp + src/Device.cpp + src/main.cpp + src/NVMLImpl.cpp + src/Options.cpp + src/Parallelogram.cpp + src/Parser.cpp + src/Picture.cpp + src/Plane.cpp + #src/Rasterizer.cpp + src/Raytracer.cpp + src/SceneGraph.cpp + src/Sphere.cpp + src/Texture.cpp + src/Timer.cpp + src/Torus.cpp +) + +# Prefix the shaders with the full path name to allow stepping through errors with F8. +set( SHADERS + # Core shaders. + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/anyhit.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/closesthit.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/exception.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/miss.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/raygeneration.cu + + # Direct callables + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/lens_shader.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/light_sample.cu + # BxDFs (BRDF, BTDF, BSDF implementations) + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bxdf_diffuse.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bxdf_ggx_smith.cu + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/bxdf_specular.cu +) + +set( KERNELS + # Native CUDA kernels + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/compositor.cu +) + +set( SHADERS_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/camera_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/compositor_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/config.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/function_indices.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/light_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/material_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/per_ray_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/random_number_generators.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shader_common.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/system_data.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vector_math.h + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vertex_attributes.h +) + +# When using OptiX SDK 7.5.0 and CUDA 11.7 or higher, the modules can either be built from OptiX IR input or from PTX input. +# OPTIX_PROGRAM_TARGET and OPTIX_MODULE_EXTENSION switch the NVCC compilation between the two options. +NVCUDA_COMPILE_MODULE( + SOURCES ${SHADERS} + DEPENDENCIES ${SHADERS_HEADERS} + TARGET_PATH "${MODULE_TARGET_DIR}/bench_shared_offscreen_core" + EXTENSION "${OPTIX_MODULE_EXTENSION}" + GENERATED_FILES PROGRAM_MODULES + NVCC_OPTIONS "${OPTIX_PROGRAM_TARGET}" "--machine=64" "--gpu-architecture=compute_50" "--use_fast_math" "--relocatable-device-code=true" "--generate-line-info" "-Wno-deprecated-gpu-targets" "--allow-unsupported-compiler" "-I${OPTIX_INCLUDE_DIR}" "-I${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +# The native CUDA Kernels will be translated to *.ptx unconditionally. +NVCUDA_COMPILE_MODULE( + SOURCES ${KERNELS} + DEPENDENCIES ${SHADERS_HEADERS} + TARGET_PATH "${MODULE_TARGET_DIR}/bench_shared_offscreen_core" + EXTENSION ".ptx" + GENERATED_FILES KERNEL_MODULES + NVCC_OPTIONS "--ptx" "--machine=64" "--gpu-architecture=compute_50" "--use_fast_math" "--relocatable-device-code=true" "--generate-line-info" "-Wno-deprecated-gpu-targets" "--allow-unsupported-compiler" "-I${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +#source_group( "imgui" FILES ${IMGUI} ) +source_group( "nvpro_math" FILES ${NVPRO_MATH} ) +source_group( "headers" FILES ${HEADERS} ) +source_group( "sources" FILES ${SOURCES} ) +source_group( "shaders" FILES ${SHADERS} ) +source_group( "shaders_headers" FILES ${SHADERS_HEADERS} ) +source_group( "prg" FILES ${PROGRAM_MODULES} ) +source_group( "ptx" FILES ${KERNEL_MODULES} ) + +include_directories( + "." + "inc" + #"imgui" + #${GLEW_INCLUDE_DIRS} + #${GLFW_INCLUDE_DIR} + ${OPTIX_INCLUDE_DIR} + ${CUDAToolkit_INCLUDE_DIRS} + ${IL_INCLUDE_DIR} + ${ASSIMP_INCLUDE_DIRS} +) + +add_definitions( + # Disable warnings for file operations fopen etc. + "-D_CRT_SECURE_NO_WARNINGS" +) + +if(USE_OPTIX_IR) +add_definitions( + # This define switches the OptiX program module filenames to either *.optixir or *.ptx extensions at compile time. + "-DUSE_OPTIX_IR" +) +endif() + +add_executable( bench_shared_offscreen + #${IMGUI} + ${NVPRO_MATH} + ${HEADERS} + ${SOURCES} + ${SHADERS_HEADERS} + ${SHADERS} + ${PROGRAM_MODULES} + ${KERNEL_MODULES} +) + +target_link_libraries( bench_shared_offscreen + #OpenGL::GL + #${GLEW_LIBRARIES} + #${GLFW_LIBRARIES} + CUDA::cuda_driver + ${IL_LIBRARIES} + ${ILU_LIBRARIES} + ${ILUT_LIBRARIES} + ${ASSIMP_LIBRARIES} +) + +if (UNIX) + target_link_libraries( bench_shared_offscreen dl ) +endif() + +set_target_properties( bench_shared_offscreen PROPERTIES FOLDER "apps") diff --git a/apps/bench_shared_offscreen/dp/math/Config.h b/apps/bench_shared_offscreen/dp/math/Config.h new file mode 100644 index 00000000..55901c01 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/Config.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once + +// #include + +//#ifdef DP_OS_WINDOWS +//// microsoft specific storage-class defines +//# ifdef DP_MATH_EXPORTS +//# define DP_MATH_API __declspec(dllexport) +//# else +//# define DP_MATH_API __declspec(dllimport) +//# endif +//#else +// DAR No need for a library, just use the dp::math functions as inline code. +# define DP_MATH_API +//#endif diff --git a/apps/bench_shared_offscreen/dp/math/Matmnt.h b/apps/bench_shared_offscreen/dp/math/Matmnt.h new file mode 100644 index 00000000..902f0535 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/Matmnt.h @@ -0,0 +1,1434 @@ +// Copyright (c) 2009-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include + +namespace dp +{ + namespace math + { + + template class Quatt; + + /*! \brief Matrix class of fixed size and type. + * \remarks This class is templated by size and type. It holds \a m rows times \a n columns values of type \a + * T. There are typedefs for the most common usage with 3x3 and 4x4 values of type \c float and \c + * + * The layout in memory is is row-major. + * Vectors have to be multiplied from the left (result = v*M). + * The last row [12-14] contains the translation. + * + * double: Mat33f, Mat33d, Mat44f, Mat44d. */ + template class Matmnt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons, no initialization is performed. */ + Matmnt(); + + /*! \brief Copy constructor from a matrix of possibly different size and type. + * \param rhs A matrix with \a m times \a m values of type \a S. + * \remarks The minimum \a x of \a m and \a k, and the minimum \a y of \a n and \a l is determined. The + * first \a y values of type \a S in the first \a x rows from \a rhs are converted to type \a T and + * assigned as the first \a y values in the first \a x rows of \c this. If \a x is less than \a m, the + * last rows of \a this are not initialized. If \a y is less than \a n, the last values of the first \a + * x rows are not initialized. */ + template + explicit Matmnt( const Matmnt & rhs ); + + /*! \brief Constructor for a matrix by an array of m rows of type Vecnt. + * \param rows An array of m rows of type Vecnt + * \remarks This constructor can easily be called, using an initializer-list + * \code + * Mat33f m33f( { xAxis, yAxis, zAxis } ); + * \endcode */ + explicit Matmnt( const std::array,m> & rows ); + + /*! \brief Constructor for a matrix by an array of 2 rows of type Vecnt. + * \param v1,v2 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2 ); + + /*! \brief Constructor for a matrix by an array of 3 rows of type Vecnt. + * \param v1,v2,v3 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3 ); + + /*! \brief Constructor for a matrix by an array of 4 rows of type Vecnt. + * \param v1,v2,v3,v4 The vectors for the rows + */ + explicit Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3, Vecnt const& v4 ); + + + /*! \brief Constructor for a matrix by an array of m*n scalars of type T. + * \param scalars An array of m*n scalars of type T + * \remarks This constructor can easily be called, using an initializer-list + * \code + * Mat33f m33f( { m00, m01, m02, m10, m11, m12, m20, m21, m22 } ); + * \endcode */ + explicit Matmnt( const std::array & scalars ); + + /*! \brief Constructor for a 3 by 3 rotation matrix out of an axis and an angle. + * \param axis A reference to the constant axis to rotate about. + * \param angle The angle, in radians, to rotate. + * \remarks The resulting 3 by 3 matrix is a pure rotation. + * \note The behavior is undefined, if \a axis is not normalized. + * \par Example: + * \code + * Mat33f rotZAxisBy45Degrees( Vec3f( 0.0f, 0.0f, 1.0f ), PI/4 ); + * \endcode */ + Matmnt( const Vecnt<3,T> & axis, T angle ); + + /*! \brief Constructor for a 3 by 3 rotation matrix out of a normalized quaternion. + * \param ori A reference to the normalized quaternion representing the rotation. + * \remarks The resulting 3 by 3 matrix is a pure rotation. + * \note The behavior is undefined, if \a ori is not normalized. */ + explicit Matmnt( const Quatt & ori ); + + /*! \brief Constructor for a 4 by 4 transformation matrix out of a quaternion and a translation. + * \param ori A reference to the normalized quaternion representing the rotational part. + * \param trans A reference to the vector representing the translational part. + * \note The behavior is undefined, if \ ori is not normalized. */ + Matmnt( const Quatt & ori, const Vecnt<3,T> & trans ); + + /*! \brief Constructor for a 4 by 4 transformation matrix out of a quaternion, a translation, + * and a scaling. + * \param ori A reference to the normalized quaternion representing the rotational part. + * \param trans A reference to the vector representing the translational part. + * \param scale A reference to the vector representing the scaling along the three axis directions. + * \note The behavior is undefined, if \ ori is not normalized. */ + Matmnt( const Quatt & ori, const Vecnt<3,T> & trans, const Vecnt<3,T> & scale ); + + public: + /*! \brief Get a constant pointer to the n times n values of the matrix. + * \return A constant pointer to the matrix elements. + * \remarks The matrix elements are stored in row-major order. This function returns the + * address of the first element of the first row. It is assured, that the other elements of + * the matrix follow linearly. + * \par Example: + * If \c m is a 3 by 3 matrix, m.getPtr() gives a pointer to the 9 elements m00, m01, m02, m10, + * m11, m12, m20, m21, m22, in that order. */ + const T * getPtr() const; + + /*! \brief Invert the matrix. + * \return \c true, if the matrix was successfully inverted, otherwise \c false. */ + bool invert(); + + /*! \brief Non-constant subscript operator. + * \param i Index of the row to address. + * \return A reference to the \a i th row of the matrix. */ + Vecnt & operator[]( unsigned int i ); + + /*! \brief Constant subscript operator. + * \param i Index of the row to address. + * \return A constant reference to the \a i th row of the matrix. */ + const Vecnt & operator[]( unsigned int i ) const; + + /*! \brief Matrix addition and assignment operator. + * \param mat A constant reference to the matrix to add. + * \return A reference to \c this. + * \remarks The matrix \a mat has to be of the same size as \c this, but may hold values of a + * different type. The matrix elements of type \a S of \a mat are converted to type \a T and + * added to the corresponding matrix elements of \c this. */ + template + Matmnt & operator+=( const Matmnt & mat ); + + /*! \brief Matrix subtraction and assignment operator. + * \param mat A constant reference to the matrix to subtract. + * \return A reference to \c this. + * \remarks The matrix \a mat has to be of the same size as \c this, but may hold values of a + * different type. The matrix elements of type \a S of \a mat are converted to type \a T and + * subtracted from the corresponding matrix elements of \c this. */ + template + Matmnt & operator-=( const Matmnt & mat ); + + /*! \brief Scalar multiplication and assignment operator. + * \param s A scalar value to multiply with. + * \return A reference to \c this. + * \remarks The type of \a s may be of different type as the elements of the \c this. \a s is + * converted to type \a T and each element of \c this is multiplied with it. */ + template + Matmnt & operator*=( S s ); + + /*! \brief Matrix multiplication and assignment operator. + * \param mat A constant reference to the matrix to multiply with. + * \return A reference to \c this. + * \remarks The matrix multiplication \code *this * mat \endcode is calculated and assigned to + * \c this. */ + Matmnt & operator*=( const Matmnt & mat ); + + /*! \brief Scalar division and assignment operator. + * \param s A scalar value to divide by. + * \return A reference to \c this. + * \remarks The type of \a s may be of different type as the elements of the \c this. \a s is + * converted to type \a T and each element of \c this is divided by it. + * \note The behavior is undefined if \a s is very close to zero. */ + template + Matmnt & operator/=( S s ); + + private: + Vecnt m_mat[m]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine the determinant of a matrix. + * \param mat A constant reference to the matrix to determine the determinant from. + * \return The determinant of \a mat. */ + template + T determinant( const Matmnt & mat ); + + /*! \brief Invert a matrix. + * \param mIn A constant reference to the matrix to invert. + * \param mOut A reference to the matrix to hold the inverse. + * \return \c true, if the matrix \a mIn was successfully inverted, otherwise \c false. + * \note If the mIn was not successfully inverted, the values in mOut are undefined. */ + template + bool invert( const Matmnt & mIn, Matmnt & mOut ); + + /*! \brief Test if a matrix is the identity. + * \param mat A constant reference to the matrix to test for identity. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true, if the matrix is the identity, otherwise \c false. + * \remarks A matrix is considered to be the identity, if each of the diagonal elements differ + * less than \a eps from one, and each of the other matrix elements differ less than \a eps from + * zero. + * \sa isNormalized, isNull, isOrthogonal, isSingular */ + template + bool isIdentity( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool identity = true; + for ( unsigned int i=0 ; identity && i + bool isNormalized( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool normalized = true; + for ( unsigned int i=0 ; normalized && i v; + for ( unsigned int j=0 ; j + bool isNull( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool null = true; + for ( unsigned int i=0 ; null && i + bool isOrthogonal( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + bool orthogonal = true; + for ( unsigned int i=0 ; orthogonal && i+1 tm = ~mat; + for ( unsigned int i=0 ; orthogonal && i+1 + bool isSingular( const Matmnt & mat, T eps = std::numeric_limits::epsilon() ) + { + return( abs( determinant( mat ) ) <= eps ); + } + + /*! \brief Get the value of the maximal absolute element of a matrix. + * \param mat A constant reference to a matrix to get the maximal element from. + * \return The value of the maximal absolute element of \a mat. + * \sa minElement */ + template + T maxElement( const Matmnt & mat ); + + /*! \brief Get the value of the minimal absolute element of a matrix. + * \param mat A constant reference to a matrix to get the minimal element from. + * \return The value of the minimal absolute element of \a mat. + * \sa maxElement */ + template + T minElement( const Matmnt & mat ); + + /*! \brief Matrix equality operator. + * \param m0 A constant reference to the first matrix to compare. + * \param m1 A constant reference to the second matrix to compare. + * \return \c true, if \a m0 and \a m1 are equal, otherwise \c false. + * \remarks Two matrices are considered to be equal, if each element of \a m0 differs less than + * the type dependent epsilon from the the corresponding element of \a m1. */ + template + bool operator==( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix inequality operator. + * \param m0 A constant reference to the first matrix to compare. + * \param m1 A constant reference to the second matrix to compare. + * \return \c true, if \a m0 and \a m1 are not equal, otherwise \c false. + * \remarks Two matrices are considered to be not equal, if at least one element of \a m0 differs + * more than the type dependent epsilon from the the corresponding element of \a m1. */ + template + bool operator!=( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix transpose operator. + * \param mat A constant reference to the matrix to transpose. + * \return The transposed version of \a m. */ + template + Matmnt operator~( const Matmnt & mat ); + + /*! \brief Matrix addition operator. + * \param m0 A constant reference to the first matrix to add. + * \param m1 A constant reference to the second matrix to add. + * \return A matrix representing the sum of \code m0 + m1 \endcode */ + template + Matmnt operator+( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Matrix negation operator. + * \param mat A constant reference to the matrix to negate. + * \return A matrix representing the negation of \a mat. */ + template + Matmnt operator-( const Matmnt & mat ); + + /*! \brief Matrix subtraction operator. + * \param m0 A constant reference to the first argument of the subtraction. + * \param m1 A constant reference to the second argument of the subtraction. + * \return A matrix representing the difference \code m0 - m1 \endcode */ + template + Matmnt operator-( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Scalar multiplication operator. + * \param mat A constant reference to the matrix to multiply. + * \param s The scalar value to multiply with. + * \return A matrix representing the product \code mat * s \endcode */ + template + Matmnt operator*( const Matmnt & mat, T s ); + + /*! \brief Scalar multiplication operator. + * \param s The scalar value to multiply with. + * \param mat A constant reference to the matrix to multiply. + * \return A matrix representing the product \code s * mat \endcode */ + template + Matmnt operator*( T s, const Matmnt & mat ); + + /*! \brief Vector multiplication operator. + * \param mat A constant reference to the matrix to multiply. + * \param v A constant reference to the vector to multiply with. + * \return A vector representing the product \code mat * v \endcode */ + template + Vecnt operator*( const Matmnt & mat, const Vecnt & v ); + + /*! \brief Vector multiplication operator. + * \param v A constant reference to the vector to multiply with. + * \param mat A constant reference to the matrix to multiply. + * \return A vector representing the product \code v * mat \endcode */ + template + Vecnt operator*( const Vecnt & v, const Matmnt & mat ); + + /*! \brief Matrix multiplication operator. + * \param m0 A constant reference to the first operand of the multiplication. + * \param m1 A constant reference to the second operand of the multiplication. + * \return A matrix representing the product \code m0 * m1 \endcode */ + template + Matmnt operator*( const Matmnt & m0, const Matmnt & m1 ); + + /*! \brief Scalar division operator. + * \param mat A constant reference to the matrix to divide. + * \param s The scalar value to divide by. + * \return A matrix representing the matrix \a mat divided by \a s. */ + template + Matmnt operator/( const Matmnt & mat, T s ); + + /*! \brief Set a matrix to be the identity. + * \param mat The matrix to set to identity. + * \remarks Each diagonal element of \a mat is set to one, each other element is set to zero. */ + template + void setIdentity( Matmnt & mat ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Test if a 3 by 3 matrix represents a rotation. + * \param mat A constant reference to the matrix to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true, if the matrix represents a rotation, otherwise \c false. + * \remarks A 3 by 3 matrix is considered to be a rotation, if it is normalized, orthogonal, and its + * determinant is one. + * \sa isIdentity, isNull, isNormalized, isOrthogonal */ + template + bool isRotation( const Matmnt<3,3,T> & mat, T eps = 9 * std::numeric_limits::epsilon() ) + { + return( isNormalized( mat, eps ) + && isOrthogonal( mat, eps ) + && ( std::abs( determinant( mat ) - 1 ) <= eps ) ); + } + + /*! \brief Set the values of a 3 by 3 matrix using a normalized quaternion. + * \param mat A reference to the matrix to set. + * \param q A constant reference to the normalized quaternion to use. + * \return A reference to \a mat. + * \remarks The matrix is set to represent the same rotation as the normalized quaternion \a q. + * \note The behavior is undefined if \a q is not normalized. */ + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Quatt & q ); + + /*! \brief Set the values of a 3 by 3 matrix using a normalized rotation axis and an angle. + * \param mat A reference to the matrix to set. + * \param axis A constant reference to the normalized rotation axis. + * \param angle The angle in radians to rotate around \a axis. + * \return A reference to \a mat. + * \remarks The matrix is set to represent the rotation by \a angle around \a axis. + * \note The behavior is undefined if \a axis is not normalized. */ + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Vecnt<3,T> & axis, T angle ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 3, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a 3 by 3 matrix. + * \param mat A constant reference to the matrix to decompose. + * \param orientation A reference to the quaternion getting the rotational part of the matrix. + * \param scaling A reference to the vector getting the scaling part of the matrix. + * \param scaleOrientation A reference to the quaternion getting the orientation of the scaling. + * \note The behavior is undefined, if the determinant of \a mat is too small, or the rank of \a mat + * is less than three. */ + DP_MATH_API void decompose( const Matmnt<3,3,float> &mat, Quatt &orientation + , Vecnt<3,float> &scaling, Quatt &scaleOrientation ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*!\brief Test if a 4 by 4 matrix represents a mirror transform + * \param mat A const reference to the matrix to test. + * \return \c true if the given matrix is a mirror transform, otherwise \c false. */ + template + bool isMirrorMatrix( const Matmnt<4,4,T>& mat ) + { + const T* ptr = mat.getPtr(); + + const Vecnt<3,T> &v0 = reinterpret_cast&>(ptr[0]); + const Vecnt<3,T> &v1 = reinterpret_cast&>(ptr[4]); + const Vecnt<3,T> &v2 = reinterpret_cast&>(ptr[8]); + + return (scalarTripleProduct(v0, v1, v2)) < 0; + } + + /*! \brief Set the values of a 4 by 4 matrix by the constituents of a transformation. + * \param mat A reference to the matrix to set. + * \param ori A constant reference of the rotation part of the transformation. + * \param trans An optional constant reference to the translational part of the transformation. The + * default is a null vector. + * \param scale An optional constant reference to the scaling part of the transformation. The default + * is the identity scaling. + * \return A reference to \a mat. */ + template + Matmnt<4,4,T> & setMat( Matmnt<4,4,T> & mat, const Quatt & ori + , const Vecnt<3,T> & trans = Vecnt<3,T>(0,0,0) + , const Vecnt<3,T> & scale = Vecnt<3,T>(1,1,1) ) + { + Matmnt<3,3,T> m3( ori ); + mat[0] = Vecnt<4,T>( scale[0] * m3[0], 0 ); + mat[1] = Vecnt<4,T>( scale[1] * m3[1], 0 ); + mat[2] = Vecnt<4,T>( scale[2] * m3[2], 0 ); + mat[3] = Vecnt<4,T>( trans, 1 ); + return( mat ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for m,n == 4, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a 4 by 4 matrix of floats. + * \param mat A constant reference to the matrix to decompose. + * \param translation A reference to the vector getting the translational part of the matrix. + * \param orientation A reference to the quaternion getting the rotational part of the matrix. + * \param scaling A reference to the vector getting the scaling part of the matrix. + * \param scaleOrientation A reference to the quaternion getting the orientation of the scaling. + * \note The behavior is undefined, if the determinant of \a mat is too small. + * \note Currently, the behavior is undefined, if the rank of \a mat is less than three. */ + DP_MATH_API void decompose( const Matmnt<4,4,float> &mat, Vecnt<3,float> &translation + , Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Matmnt<3,3,float> Mat33f; + typedef Matmnt<3,3,double> Mat33d; + typedef Matmnt<4,4,float> Mat44f; + typedef Matmnt<4,4,double> Mat44d; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Matmnt::Matmnt() + { + } + + template + template + inline Matmnt::Matmnt( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i(rhs[i]); + } + } + + template + inline Matmnt::Matmnt( const std::array,m> & rows ) + { + for ( unsigned int i=0 ; i + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2 ) + { + MY_STATIC_ASSERT( m == 2 ); + m_mat[0] = v1; + m_mat[1] = v2; + } + + template + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3 ) + { + MY_STATIC_ASSERT( m == 3 ); + m_mat[0] = v1; + m_mat[1] = v2; + m_mat[2] = v3; + } + + template + inline Matmnt::Matmnt( Vecnt const& v1, Vecnt const& v2, Vecnt const& v3, Vecnt const& v4 ) + { + MY_STATIC_ASSERT( m == 4 ); + m_mat[0] = v1; + m_mat[1] = v2; + m_mat[2] = v3; + m_mat[3] = v4; + } + + template + inline Matmnt::Matmnt( const std::array & scalars ) + { + for ( unsigned int i=0, idx=0 ; i + inline Matmnt::Matmnt( const Vecnt<3,T> & axis, T angle ) + { + MY_STATIC_ASSERT( ( m == 3 ) && ( n == 3 ) ); + setMat( *this, axis, angle ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori ) + { + MY_STATIC_ASSERT( ( m == 3 ) && ( n == 3 ) ); + setMat( *this, ori ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori, const Vecnt<3,T> & trans ) + { + MY_STATIC_ASSERT( ( m == 4 ) && ( n == 4 ) ); + setMat( *this, ori, trans ); + } + + template + inline Matmnt::Matmnt( const Quatt & ori, const Vecnt<3,T> & trans, const Vecnt<3,T> & scale ) + { + MY_STATIC_ASSERT( ( m == 4 ) && ( n == 4 ) ); + setMat( *this, ori, trans, scale ); + } + + template + inline const T * Matmnt::getPtr() const + { + return( m_mat[0].getPtr() ); + } + + template + inline bool Matmnt::invert() + { + MY_STATIC_ASSERT( m == n ); + Matmnt tmp; + bool ok = dp::math::invert( *this, tmp ); + if ( ok ) + { + *this = tmp; + } + return( ok ); + } + + template + inline Vecnt & Matmnt::operator[]( unsigned int i ) + { + MY_ASSERT( i < m ); + return( m_mat[i] ); + } + + template + inline const Vecnt & Matmnt::operator[]( unsigned int i ) const + { + MY_ASSERT( i < m ); + return( m_mat[i] ); + } + + template + template + inline Matmnt & Matmnt::operator+=( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Matmnt & Matmnt::operator-=( const Matmnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Matmnt & Matmnt::operator*=( S s ) + { + for ( unsigned int i=0 ; i + inline Matmnt & Matmnt::operator*=( const Matmnt & rhs ) + { + *this = *this * rhs; + return( *this ); + } + + template + template + inline Matmnt & Matmnt::operator/=( S s ) + { + for ( unsigned int i=0 ; i + inline T calculateDeterminant( const Matmnt & mat, const Vecnt & first, const Vecnt & second ) + { + Vecnt subFirst, subSecond; + for ( unsigned int i=1 ; i + inline T calculateDeterminant( const Matmnt & mat, const Vecnt<1,unsigned int> & first, const Vecnt<1,unsigned int> & second ) + { + return( mat[first[0]][second[0]] ); + } + + template + inline T determinant( const Matmnt & mat ) + { + Vecnt first, second; + for ( unsigned int i=0 ; i + inline bool invert( const Matmnt & mIn, Matmnt & mOut ) + { + mOut = mIn; + + unsigned int p[n]; + + bool ok = true; + for ( unsigned int k=0 ; ok && k::epsilon() < std::abs(s) ); + if ( ok ) + { + T q = std::abs( mOut[i][k] ) / s; + if ( q > max ) + { + max = q; + p[k] = i; + } + } + } + + ok = ( std::numeric_limits::epsilon() < max ); + if ( ok ) + { + if ( p[k] != k ) + { + for ( unsigned int j=0 ; j::epsilon() < std::abs( pivot ) ); + if ( ok ) + { + for ( unsigned int j=0 ; j ( int k >= 0 ) + { + if ( p[k] != k ) + { + for ( unsigned int i=0 ; i + inline T maxElement( const Matmnt & mat ) + { + T me = maxElement( mat[0] ); + for ( unsigned int i=1 ; i + inline T minElement( const Matmnt & mat ) + { + T me = minElement( mat[0] ); + for ( unsigned int i=1 ; i + inline bool operator==( const Matmnt & m0, const Matmnt & m1 ) + { + bool eq = true; + for ( unsigned int i=0 ; i + inline bool operator!=( const Matmnt & m0, const Matmnt & m1 ) + { + return( ! ( m0 == m1 ) ); + } + + template + inline Matmnt operator~( const Matmnt & mat ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator+( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret(m0); + ret += m1; + return( ret ); + } + + template + inline Matmnt operator-( const Matmnt & mat ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator-( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret(m0); + ret -= m1; + return( ret ); + } + + template + inline Matmnt operator*( const Matmnt & mat, T s ) + { + Matmnt ret(mat); + ret *= s; + return( ret ); + } + + template + inline Matmnt operator*( T s, const Matmnt & mat ) + { + return( mat * s ); + } + + template + inline Vecnt operator*( const Matmnt & mat, const Vecnt & v ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Vecnt operator*( const Vecnt & v, const Matmnt & mat ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator*( const Matmnt & m0, const Matmnt & m1 ) + { + Matmnt ret; + for ( unsigned int i=0 ; i + inline Matmnt operator/( const Matmnt & mat, T s ) + { + Matmnt ret(mat); + ret /= s; + return( ret ); + } + + template + void setIdentity( Matmnt & mat ) + { + for ( unsigned int i=0 ; i + inline T determinant( const Matmnt<3,3,T> & mat ) + { + return( mat[0] * ( mat[1] ^ mat[2] ) ); + } + + template + inline bool invert( const Matmnt<3,3,T> & mIn, Matmnt<3,3,T> & mOut ) + { + double adj00 = ( mIn[1][1] * mIn[2][2] - mIn[1][2] * mIn[2][1] ); + double adj10 = - ( mIn[1][0] * mIn[2][2] - mIn[1][2] * mIn[2][0] ); + double adj20 = ( mIn[1][0] * mIn[2][1] - mIn[1][1] * mIn[2][0] ); + double det = mIn[0][0] * adj00 + mIn[0][1] * adj10 + mIn[0][2] * adj20; + bool ok = ( std::numeric_limits::epsilon() < abs( det ) ); + if ( ok ) + { + double invDet = 1.0 / det; + mOut[0][0] = T( adj00 * invDet ); + mOut[0][1] = T( - ( mIn[0][1] * mIn[2][2] - mIn[0][2] * mIn[2][1] ) * invDet ); + mOut[0][2] = T( ( mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1] ) * invDet ); + mOut[1][0] = T( adj10 * invDet ); + mOut[1][1] = T( ( mIn[0][0] * mIn[2][2] - mIn[0][2] * mIn[2][0] ) * invDet ); + mOut[1][2] = T( - ( mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0] ) * invDet ); + mOut[2][0] = T( adj20 * invDet ); + mOut[2][1] = T( - ( mIn[0][0] * mIn[2][1] - mIn[0][1] * mIn[2][0] ) * invDet ); + mOut[2][2] = T( ( mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0] ) * invDet ); + } + return( ok ); + } + + template + Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Vecnt<3,T> & axis, T angle ) + { + T c = cos( angle ); + T s = sin( angle ); + T t = 1 - c; + T x = axis[0]; + T y = axis[1]; + T z = axis[2]; + + mat[0] = Vecnt<3,T>( t * x * x + c, t * x * y + s * z, t * x * z - s * y ); + mat[1] = Vecnt<3,T>( t * x * y - s * z, t * y * y + c, t * y * z + s * x ); + mat[2] = Vecnt<3,T>( t * x * z + s * y, t * y * z - s * x, t * z * z + c ); + + return( mat ); + } + + template + inline Matmnt<3,3,T> & setMat( Matmnt<3,3,T> & mat, const Quatt & q ) + { + T x = q[0]; + T y = q[1]; + T z = q[2]; + T w = q[3]; + + mat[0] = Vecnt<3,T>( 1 - 2 * ( y * y + z * z ), 2 * ( x * y + z * w ), 2 * ( x * z - y * w ) ); + mat[1] = Vecnt<3,T>( 2 * ( x * y - z * w ), 1 - 2 * ( x * x + z * z ), 2 * ( y * z + x * w ) ); + mat[2] = Vecnt<3,T>( 2 * ( x * z + y * w ), 2 * ( y * z - x * w ), 1 - 2 * ( x * x + y * y ) ); + + return( mat ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for m,n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline T determinant( const Matmnt<4,4,T> & mat ) + { + const T a0 = mat[0][0]*mat[1][1] - mat[0][1]*mat[1][0]; + const T a1 = mat[0][0]*mat[1][2] - mat[0][2]*mat[1][0]; + const T a2 = mat[0][0]*mat[1][3] - mat[0][3]*mat[1][0]; + const T a3 = mat[0][1]*mat[1][2] - mat[0][2]*mat[1][1]; + const T a4 = mat[0][1]*mat[1][3] - mat[0][3]*mat[1][1]; + const T a5 = mat[0][2]*mat[1][3] - mat[0][3]*mat[1][2]; + const T b0 = mat[2][0]*mat[3][1] - mat[2][1]*mat[3][0]; + const T b1 = mat[2][0]*mat[3][2] - mat[2][2]*mat[3][0]; + const T b2 = mat[2][0]*mat[3][3] - mat[2][3]*mat[3][0]; + const T b3 = mat[2][1]*mat[3][2] - mat[2][2]*mat[3][1]; + const T b4 = mat[2][1]*mat[3][3] - mat[2][3]*mat[3][1]; + const T b5 = mat[2][2]*mat[3][3] - mat[2][3]*mat[3][2]; + return( a0*b5 - a1*b4 + a2*b3 + a3*b2 - a4*b1 + a5*b0 ); + } + + template + inline bool invert( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + T s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; T c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + T s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; T c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + T s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; T c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + T s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; T c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + T s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; T c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + T s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; T c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + T det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( det != T(0) ) + { + T invDet = T(1.0) / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[0][1] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[0][2] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[0][3] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[1][0] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[1][2] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[1][3] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[2][0] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[2][1] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[2][3] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[3][0] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[3][1] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[3][2] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + template + inline bool invertTranspose( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + T s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; T c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + T s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; T c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + T s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; T c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + T s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; T c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + T s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; T c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + T s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; T c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + T det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( det != 0 ) + { + T invDet = T(1.0) / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[1][0] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[2][0] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[3][0] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[0][1] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[2][1] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[3][1] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[0][2] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[1][2] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[3][2] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[0][3] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[1][3] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[2][3] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + template + inline bool invertDouble( const Matmnt<4,4,T> & mIn, Matmnt<4,4,T> & mOut ) + { + double s0 = mIn[0][0] * mIn[1][1] - mIn[0][1] * mIn[1][0]; double c5 = mIn[2][2] * mIn[3][3] - mIn[2][3] * mIn[3][2]; + double s1 = mIn[0][0] * mIn[1][2] - mIn[0][2] * mIn[1][0]; double c4 = mIn[2][1] * mIn[3][3] - mIn[2][3] * mIn[3][1]; + double s2 = mIn[0][0] * mIn[1][3] - mIn[0][3] * mIn[1][0]; double c3 = mIn[2][1] * mIn[3][2] - mIn[2][2] * mIn[3][1]; + double s3 = mIn[0][1] * mIn[1][2] - mIn[0][2] * mIn[1][1]; double c2 = mIn[2][0] * mIn[3][3] - mIn[2][3] * mIn[3][0]; + double s4 = mIn[0][1] * mIn[1][3] - mIn[0][3] * mIn[1][1]; double c1 = mIn[2][0] * mIn[3][2] - mIn[2][2] * mIn[3][0]; + double s5 = mIn[0][2] * mIn[1][3] - mIn[0][3] * mIn[1][2]; double c0 = mIn[2][0] * mIn[3][1] - mIn[2][1] * mIn[3][0]; + double det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + if ( ( std::numeric_limits::epsilon() < abs( det ) ) ) + { + double invDet = 1.0 / det; + mOut[0][0] = T( ( mIn[1][1] * c5 - mIn[1][2] * c4 + mIn[1][3] * c3 ) * invDet ); + mOut[0][1] = T( ( - mIn[0][1] * c5 + mIn[0][2] * c4 - mIn[0][3] * c3 ) * invDet ); + mOut[0][2] = T( ( mIn[3][1] * s5 - mIn[3][2] * s4 + mIn[3][3] * s3 ) * invDet ); + mOut[0][3] = T( ( - mIn[2][1] * s5 + mIn[2][2] * s4 - mIn[2][3] * s3 ) * invDet ); + mOut[1][0] = T( ( - mIn[1][0] * c5 + mIn[1][2] * c2 - mIn[1][3] * c1 ) * invDet ); + mOut[1][1] = T( ( mIn[0][0] * c5 - mIn[0][2] * c2 + mIn[0][3] * c1 ) * invDet ); + mOut[1][2] = T( ( - mIn[3][0] * s5 + mIn[3][2] * s2 - mIn[3][3] * s1 ) * invDet ); + mOut[1][3] = T( ( mIn[2][0] * s5 - mIn[2][2] * s2 + mIn[2][3] * s1 ) * invDet ); + mOut[2][0] = T( ( mIn[1][0] * c4 - mIn[1][1] * c2 + mIn[1][3] * c0 ) * invDet ); + mOut[2][1] = T( ( - mIn[0][0] * c4 + mIn[0][1] * c2 - mIn[0][3] * c0 ) * invDet ); + mOut[2][2] = T( ( mIn[3][0] * s4 - mIn[3][1] * s2 + mIn[3][3] * s0 ) * invDet ); + mOut[2][3] = T( ( - mIn[2][0] * s4 + mIn[2][1] * s2 - mIn[2][3] * s0 ) * invDet ); + mOut[3][0] = T( ( - mIn[1][0] * c3 + mIn[1][1] * c1 - mIn[1][2] * c0 ) * invDet ); + mOut[3][1] = T( ( mIn[0][0] * c3 - mIn[0][1] * c1 + mIn[0][2] * c0 ) * invDet ); + mOut[3][2] = T( ( - mIn[3][0] * s3 + mIn[3][1] * s1 - mIn[3][2] * s0 ) * invDet ); + mOut[3][3] = T( ( mIn[2][0] * s3 - mIn[2][1] * s1 + mIn[2][2] * s0 ) * invDet ); + return( true ); + } + return( false ); + } + + /*! \brief makeLookAt defines a viewing transformation. + * \param eye The position of the eye point. + * \param center The position of the reference point. + * \param up The direction of the up vector. + * \remarks The makeLookAt function creates a viewing matrix derived from an eye point, a reference point indicating the center + * of the scene, and an up vector. The matrix maps the reference point to the negative z-axis and the eye point to the + * origin, so that when you use a typical projection matrix, the center of the scene maps to the center of the viewport. + * Similarly, the direction described by the up vector projected onto the viewing plane is mapped to the positive y-axis so that + * it points upward in the viewport. The up vector must not be parallel to the line of sight from the eye to the reference point. + * \note This documentation is adapted from gluLookAt, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeLookAt( const Vecnt<3,T> & eye, const Vecnt<3,T> & center, const Vecnt<3,T> & up ) + { + Vecnt<3,T> f = center - eye; + normalize( f ); + + #ifndef NDEBUG + // assure up is not parallel to vector from eye to center + Vecnt<3,T> nup = up; + normalize( nup ); + T dot = f * nup; + MY_ASSERT( dot != T(1) && dot != T(-1) ); + #endif + + Vecnt<3,T> s = f ^ up; + normalize( s ); + Vecnt<3,T> u = s ^ f; + + Matmnt<4,4,T> transmat; + transmat[0] = Vec4f( T(1), T(0), T(0), T(0) ); + transmat[1] = Vec4f( T(0), T(1), T(0), T(0) ); + transmat[2] = Vec4f( T(0), T(0), T(1), T(0) ); + transmat[3] = Vec4f( -eye[0], -eye[1], -eye[2], T(1) ); + + Matmnt<4,4,T> orimat; + orimat[0] = Vec4f( s[0], u[0], -f[0], T(0) ); + orimat[1] = Vec4f( s[1], u[1], -f[1], T(0) ); + orimat[2] = Vec4f( s[2], u[2], -f[2], T(0) ); + orimat[3] = Vec4f( T(0), T(0), T(0), T(1) ); + + // must premultiply translation + return transmat * orimat; + } + + /*! \brief makeOrtho defines an orthographic projection matrix. + * \param left Coordinate for the left vertical clipping plane. + * \param right Coordinate for the right vertical clipping plane. + * \param bottom Coordinate for the bottom horizontal clipping plane. + * \param top Coordinate for the top horizontal clipping plane. + * \param znear The distance to the near clipping plane. This distance is negative if the plane is behind the viewer. + * \param zfar The distance to the far clipping plane. This distance is negative if the plane is behind the viewer. + * \remarks The makeOrtho function describes a perspective matrix that produces a parallel projection. Assuming this function + * will be used to build a camera's Projection matrix, the (left, bottom, znear) and (right, top, znear) parameters specify the + * points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, + * assuming that the eye is located at (0, 0, 0). The far parameter specifies the location of the far clipping plane. Both znear + * and zfar can be either positive or negative. + * \note This documentation is adapted from glOrtho, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeOrtho( T left, T right, + T bottom, T top, + T znear, T zfar ) + { + MY_ASSERT( (left != right) && (bottom != top) && (znear != zfar) && (zfar > znear) ); + + return Matmnt<4,4,T>( { T(2)/(right-left), T(0), T(0), T(0) + , T(0), T(2)/(top-bottom), T(0), T(0) + , T(0), T(0), T(-2)/(zfar-znear), T(0) + , -(right+left)/(right-left), -(top+bottom)/(top-bottom), -(zfar+znear)/(zfar-znear), T(1) } ); + } + + /*! \brief makeFrustum defines a perspective projection matrix. + * \param left Coordinate for the left vertical clipping plane. + * \param right Coordinate for the right vertical clipping plane. + * \param bottom Coordinate for the bottom horizontal clipping plane. + * \param top Coordinate for the top horizontal clipping plane. + * \param znear The distance to the near clipping plane. The value must be greater than zero. + * \param zfar The distance to the far clipping plane. The value must be greater than znear. + * \remarks The makeFrustum function describes a perspective matrix that produces a perspective projection. Assuming this function + * will be used to build a camera's Projection matrix, the (left, bottom, znear) and (right, top, znear) parameters specify the + * points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, + * assuming that the eye is located at (0,0,0). The zfar parameter specifies the location of the far clipping plane. Both znear + * and zfar must be positive. + * \note This documentation is adapted from glFrustum, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makeFrustum( T left, T right, + T bottom, T top, + T znear, T zfar ) + { + // near and far must be greater than zero + MY_ASSERT( (znear > T(0)) && (zfar > T(0)) && (zfar > znear) ); + MY_ASSERT( (left != right) && (bottom != top) && (znear != zfar) ); + + T v0 = (right+left)/(right-left); + T v1 = (top+bottom)/(top-bottom); + T v2 = -(zfar+znear)/(zfar-znear); + T v3 = T(-2)*zfar*znear/(zfar-znear); + T v4 = T(2)*znear/(right-left); + T v5 = T(2)*znear/(top-bottom); + + return Matmnt<4,4,T>( { v4, T(0), T(0), T(0) + , T(0), v5, T(0), T(0) + , v0, v1, v2, T(-1) + , T(0), T(0), v3, T(0) } ); + } + + /*! \brief makePerspective builds a perspective projection matrix. + * \param fovy The vertical field of view, in degrees. + * \param aspect The ratio of the viewport width / height. + * \param znear The distance to the near clipping plane. The value must be greater than zero. + * \param zfar The distance to the far clipping plane. The value must be greater than znear. + * \remarks Assuming makePerspective will be used to build a camera's Projection matrix, it specifies a viewing frustum into the + * world coordinate system. In general, the aspect ratio in makePerspective should match the aspect ratio of the associated + * viewport. For example, aspect = 2.0 means the viewer's angle of view is twice as wide in x as it is in y. If the viewport + * is twice as wide as it is tall, it displays the image without distortion. + * \note This documentation is adapted from gluPerspective, and is courtesy of SGI. + */ + template + inline Matmnt<4,4,T> makePerspective( T fovy, T aspect, T znear, T zfar ) + { + MY_ASSERT( (znear > (T)0) && (zfar > (T)0) ); + + T tanfov = tan( degToRad( fovy ) * (T)0.5 ); + T r = tanfov * aspect * znear; + T l = -r; + T t = tanfov * znear; + T b = -t; + + return makeFrustum( l, r, b, t, znear, zfar ); + } + + template + inline Vecnt<4,T> operator*( const Vecnt<4,T>& v, const Matmnt<4,4,T>& mat ) + { + return Vecnt<4,T> ( + v[0] * mat[0][0] + v[1]*mat[1][0] + v[2]*mat[2][0] + v[3]*mat[3][0], + v[0] * mat[0][1] + v[1]*mat[1][1] + v[2]*mat[2][1] + v[3]*mat[3][1], + v[0] * mat[0][2] + v[1]*mat[1][2] + v[2]*mat[2][2] + v[3]*mat[3][2], + v[0] * mat[0][3] + v[1]*mat[1][3] + v[2]*mat[2][3] + v[3]*mat[3][3] ); + } + + template + inline Matmnt<4,4,T> operator*( const Matmnt<4,4,T> & m0, const Matmnt<4,4,T> & m1 ) + { + Matmnt<4,4,T> result; + + result[0] = Vecnt<4,T>( + m0[0][0]*m1[0][0] + m0[0][1]*m1[1][0] + m0[0][2]*m1[2][0] + m0[0][3]*m1[3][0], + m0[0][0]*m1[0][1] + m0[0][1]*m1[1][1] + m0[0][2]*m1[2][1] + m0[0][3]*m1[3][1], + m0[0][0]*m1[0][2] + m0[0][1]*m1[1][2] + m0[0][2]*m1[2][2] + m0[0][3]*m1[3][2], + m0[0][0]*m1[0][3] + m0[0][1]*m1[1][3] + m0[0][2]*m1[2][3] + m0[0][3]*m1[3][3] + ); + + result[1] = Vecnt<4,T>( + m0[1][0]*m1[0][0] + m0[1][1]*m1[1][0] + m0[1][2]*m1[2][0] + m0[1][3]*m1[3][0], + m0[1][0]*m1[0][1] + m0[1][1]*m1[1][1] + m0[1][2]*m1[2][1] + m0[1][3]*m1[3][1], + m0[1][0]*m1[0][2] + m0[1][1]*m1[1][2] + m0[1][2]*m1[2][2] + m0[1][3]*m1[3][2], + m0[1][0]*m1[0][3] + m0[1][1]*m1[1][3] + m0[1][2]*m1[2][3] + m0[1][3]*m1[3][3] + ); + + result[2] = Vecnt<4,T>( + m0[2][0]*m1[0][0] + m0[2][1]*m1[1][0] + m0[2][2]*m1[2][0] + m0[2][3]*m1[3][0], + m0[2][0]*m1[0][1] + m0[2][1]*m1[1][1] + m0[2][2]*m1[2][1] + m0[2][3]*m1[3][1], + m0[2][0]*m1[0][2] + m0[2][1]*m1[1][2] + m0[2][2]*m1[2][2] + m0[2][3]*m1[3][2], + m0[2][0]*m1[0][3] + m0[2][1]*m1[1][3] + m0[2][2]*m1[2][3] + m0[2][3]*m1[3][3] + ); + + result[3] = Vecnt<4,T>( + m0[3][0]*m1[0][0] + m0[3][1]*m1[1][0] + m0[3][2]*m1[2][0] + m0[3][3]*m1[3][0], + m0[3][0]*m1[0][1] + m0[3][1]*m1[1][1] + m0[3][2]*m1[2][1] + m0[3][3]*m1[3][1], + m0[3][0]*m1[0][2] + m0[3][1]*m1[1][2] + m0[3][2]*m1[2][2] + m0[3][3]*m1[3][2], + m0[3][0]*m1[0][3] + m0[3][1]*m1[1][3] + m0[3][2]*m1[2][3] + m0[3][3]*m1[3][3] + ); + + return result; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for m,n == 4, T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline void decompose( const Matmnt<4,4,float> &mat, Vecnt<3,float> &translation + , Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ) + { + translation = Vecnt<3,float>( mat[3] ); + Matmnt<3,3,float> m33( mat ); + decompose( m33, orientation, scaling, scaleOrientation ); + } + + + + //! global identity matrix. + extern DP_MATH_API const Mat44f cIdentity44f; + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/Quatt.h b/apps/bench_shared_offscreen/dp/math/Quatt.h new file mode 100644 index 00000000..8ace2d8f --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/Quatt.h @@ -0,0 +1,551 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include + +namespace dp +{ + namespace math + { + + template class Matmnt; + + /*! \brief Quaternion class. + * \remarks Quaternions are an alternative to the 3x3 matrices that are typically used for 3-D + * rotations. A unit quaternion represents an axis in 3-D space and a rotation around that axis. + * Every rotation can be expressed that way. There are typedefs for the most common usage with \c + * float and \c double: Quatf, Quatd. + * \note Only unit quaternions represent a rotation. */ + template class Quatt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons no initialization is performed. */ + Quatt(); + + /*! \brief Constructor using four scalar values. + * \param x X-component of the quaternion. + * \param y Y-component of the quaternion. + * \param z Z-component of the quaternion. + * \param w W-component of the quaternion. + * \note \c x, \c y, and \c z are \b not the x,y,z-component of the rotation axis, and \c w + * is \b not the rotation angle. If you have such values handy, use the constructor that + * takes the axis as a Vecnt<3,T> and an angle. */ + Quatt( T x, T y, T z, T w ); + + /*! \brief Constructor using a Vecnt<4,T>. + * \param v Vector to construct the quaternion from. + * \remarks The four values of \c v are just copied over to the quaternion. It is assumed + * that this operation gives a normalized quaternion. */ + explicit Quatt( const Vecnt<4,T> & v ); + + /*! \brief Copy constructor using a Quaternion of possibly different type. + * \param q The quaternion to copy. */ + template + explicit Quatt( const Quatt & q ); + + /*! \brief Constructor using an axis and an angle. + * \param axis Axis to rotate around. + * \param angle Angle in radians to rotate. + * \remarks The resulting quaternion represents a rotation by \c angle (in radians) around + * \c axis. */ + Quatt( const Vecnt<3,T> & axis, T angle ); + + /*! \brief Constructor by two vectors. + * \param v0 Start vector. + * \param v1 End vector. + * \remarks The resulting quaternion represents the rotation that maps \a vec0 to \a vec1. + * \note The quaternion out of two anti-parallel vectors is not uniquely defined. We select just + * one out of the possible candidates, which might not be the one you would expect. For better + * control on the quaternion in such a case, we recommend to use the constructor out of an axis + * and an angle. */ + Quatt( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ); + + /*! \brief Constructor by a rotation matrix. + * \param rot The rotation matrix to convert to a unit quaternion. + * \remarks The resulting quaternion represents the same rotation as the matrix. */ + explicit Quatt( const Matmnt<3,3,T> & rot ); + + /*! \brief Normalize the quaternion. + * \return A reference to \c this, as the normalized quaternion. + * \remarks It is always assumed, that a quaternion is normalized. But when getting data from + * outside or due to numerically instabilities, a quaternion might become unnormalized. You + * can use this function then to normalize it again. */ + Quatt & normalize(); + + /*! \brief Non-constant subscript operator. + * \param i Index to the element to use (i=0,1,2,3). + * \return A reference to the \a i th element of the quaternion. */ + template + T & operator[]( S i ); + + /*! \brief Constant subscript operator. + * \param i Index to the element to use (i=0,1,2,3). + * \return A reference to the constant \a i th element of the quaternion. */ + template + const T & operator[]( S i ) const; + + /*! \brief Quaternion assignment operator from a Quaternion of possibly different type. + * \param q The quaternion to assign. + * \return A reference to \c this, as the assigned Quaternion from q. */ + template + Quatt & operator=( const Quatt & q ); + + /*! \brief Quaternion multiplication with a quaternion and assignment operator. + * \param q A Quaternion to multiply with. + * \return A reference to \c this, as the product (or concatenation) of \c this and \a q. + * \remarks Multiplying two quaternions give a quaternion that represents the concatenation + * of the rotations represented by the two quaternions. */ + Quatt & operator*=( const Quatt & q ); + + /*! \brief Quaternion division by a quaternion and assignment operator. + * \param q A Quaternion to divide by. + * \return A reference to \c this, as the division of \c this by \a q. + * \remarks Dividing a quaternion by an other gives a quaternion that represents the + * concatenation of the rotation represented by the numerator quaternion and the rotation + * represented by the conjugated denumerator quaternion. */ + Quatt & operator/=( const Quatt & q ); + + private: + T m_quat[4]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Decompose a quaternion into an axis and an angle. + * \param q A reference to the constant quaternion to decompose. + * \param axis A reference to the resulting axis. + * \param angle A reference to the resulting angle. */ + template + void decompose( const Quatt & q, Vecnt<3,T> & axis, T & angle ); + + /*! \brief Determine the distance between two quaternions. + * \param q0 A reference to the left constant quaternion. + * \param q1 A reference to the right constant quaternion. + * \return The euclidean distance between \a q0 and \c q1, interpreted as Vecnt<4,T>. */ + template + T distance( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Test if a quaternion is normalized. + * \param q A reference to the constant quaternion to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true if the quaternion is normalized, otherwise \c false. + * \remarks A quaternion \a q is considered to be normalized, when it's magnitude differs less + * than some small value \a eps from one. */ + template + bool isNormalized( const Quatt & q, T eps = std::numeric_limits::epsilon() * 8 ) + { + return( std::abs( magnitude( q ) - 1 ) <= eps ); + } + + /*! \brief Test if a quaternion is null. + * \param q A reference to the constant quaternion to test. + * \param eps An optional value giving the allowed epsilon. The default is a type dependent value. + * \return \c true if the quaternion is null, otherwise \c false. + * \remarks A quaternion \a q is considered to be null, if it's magnitude is less than some small + * value \a eps. */ + template + bool isNull( const Quatt & q, T eps = std::numeric_limits::epsilon() ) + { + return( magnitude( q ) <= eps ); + } + + /*! \brief Determine the magnitude of a quaternion. + * \param q A reference to the quaternion to determine the magnitude of. + * \return The magnitude of the quaternion \a q. + * \remarks The magnitude of a normalized quaternion is one. */ + template + T magnitude( const Quatt & q ); + + /*! \brief Quaternion equality operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return \c true if the quaternions \a q0 and \a q are equal, otherwise \c false. + * \remarks Two quaternions are considered to be equal, if each component of the one quaternion + * deviates less than epsilon from the corresponding element of the other quaternion. */ + template + bool operator==( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion inequality operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return \c true if \a q0 is not equal to \a q1, otherwise \c false + * \remarks Two quaternions are considered to be equal, if each component of the one quaternion + * deviates less than epsilon from the corresponding element of the other quaternion. */ + template + bool operator!=( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion conjugation operator. + * \param q A reference to the constant quaternion to conjugate. + * \return The conjugation of \a q. + * \remarks The conjugation of a quaternion is a rotation of the same angle around the negated + * axis. */ + template + Quatt operator~( const Quatt & q ); + + /*! \brief Negation operator. + * \param q A reference to the constant quaternion to get the negation from. + * \return The negation of \a q. + * \remarks The negation of a quaternion is a rotation around the same axis by the negated angle. */ + template + Quatt operator-( const Quatt & q ); + + /*! \brief Quaternion multiplication operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return The product of \a q0 with \a a1. + * \remarks Multiplying two quaternions gives a quaternion that represents the concatenation of + * the rotation represented by the two quaternions. Besides rounding errors, the following + * equation holds: + * \code + * Matmnt<3,3,T>( q0 * q1 ) == Matmnt<3,3,T>( q0 ) * Matmnt<3,3,T>( q1 ) + * \endcode */ + template + Quatt operator*( const Quatt & q0, const Quatt & q1 ); + + /*! \brief Quaternion multiplication operator with a vector. + * \param q A reference to the constant left operand. + * \param v A reference to the constant right operand. + * \return The vector \a v rotated by the inverse of \a q. + * \remarks Multiplying a quaternion \a q with a vector \a v applies the inverse rotation + * represented by \a q to \a v. */ + template + Vecnt<3,T> operator*( const Quatt & q, const Vecnt<3,T> & v ); + + /*! \brief Vector multiplication operator with a quaternion. + * \param v A reference to the constant left operand. + * \param q A reference to the constant right operand. + * \return The vector \a v rotated by \a q. + * \remarks Multiplying a vector \a v by a quaternion \a q applies the rotation represented by + * \a q to \a v. */ + template + Vecnt<3,T> operator*( const Vecnt<3,T> & v, const Quatt & q ); + + /*! \brief Quaternion division operator. + * \param q0 A reference to the constant left operand. + * \param q1 A reference to the constant right operand. + * \return A quaternion representing the quotient of \a q0 and \a q1. + * \remarks Dividing a quaternion by an other gives a quaternion that represents the + * concatenation of the rotation represented by the numerator quaternion and the rotation + * represented by the conjugated denumerator quaternion. */ + template + Quatt operator/( const Quatt & q0, const Quatt & q1 ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for T == float + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Spherical linear interpolation between two quaternions \a q0 and \a q1. + * \param alpha The interpolation parameter. + * \param q0 The starting value. + * \param q1 The ending value. + * \return The quaternion that represents the spherical linear interpolation between \a q0 and \a + * q1. */ + DP_MATH_API Quatt lerp( float alpha, const Quatt & q0, const Quatt & q1 ); + + /*! \brief Spherical linear interpolation between two quaternions \a q0 and \a q1. + * \param alpha The interpolation parameter. + * \param q0 The starting value. + * \param q1 The ending value. + * \param qr The quaternion that represents the spherical linear interpolation between \a q0 and \a + * q1. */ + DP_MATH_API void lerp( float alpha, const Quatt & q0, const Quatt & q1, Quatt &qr ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Quatt Quatf; + typedef Quatt Quatd; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline Quatt::Quatt() + { + } + + template + inline Quatt::Quatt( T x, T y, T z, T w ) + { + m_quat[0] = x; + m_quat[1] = y; + m_quat[2] = z; + m_quat[3] = w; + } + + template + inline Quatt::Quatt( const Vecnt<4,T> & v ) + { + m_quat[0] = v[0]; + m_quat[1] = v[1]; + m_quat[2] = v[2]; + m_quat[3] = v[3]; + } + + template + template + inline Quatt::Quatt( const Quatt & q ) + { + *this = q; + } + + template + inline Quatt::Quatt( const Vecnt<3,T> & axis, T angle ) + { + T dummy = sin( T(0.5) * angle ); + m_quat[0] = axis[0] * dummy; + m_quat[1] = axis[1] * dummy; + m_quat[2] = axis[2] * dummy; + m_quat[3] = cos( T(0.5) * angle ); + } + + template + inline Quatt::Quatt( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ) + { + Vecnt<3,T> axis = v0 ^ v1; + axis.normalize(); + T cosAngle = clamp( v0 * v1, -1.0f, 1.0f ); // make sure, cosine is in [-1.0,1.0]! + if ( cosAngle + 1.0f < std::numeric_limits::epsilon() ) + { + // In case v0 and v1 are (closed to) anti-parallel, the standard + // procedure would not create a valid quaternion. + // As the rotation axis isn't uniquely defined in that case, we + // just pick one. + axis = orthonormal( v0 ); + } + T s = sqrt( T(0.5) * ( 1 - cosAngle ) ); + m_quat[0] = axis[0] * s; + m_quat[1] = axis[1] * s; + m_quat[2] = axis[2] * s; + m_quat[3] = sqrt( T(0.5) * ( 1 + cosAngle ) ); + } + + template + inline Quatt::Quatt( const Matmnt<3,3,T> & rot ) + { + T tr = rot[0][0] + rot[1][1] + rot[2][2] + 1; + if ( std::numeric_limits::epsilon() < tr ) + { + T s = sqrt( tr ); + m_quat[3] = T(0.5) * s; + s = T(0.5) / s; + m_quat[0] = ( rot[1][2] - rot[2][1] ) * s; + m_quat[1] = ( rot[2][0] - rot[0][2] ) * s; + m_quat[2] = ( rot[0][1] - rot[1][0] ) * s; + } + else + { + unsigned int i = 0; + if ( rot[i][i] < rot[1][1] ) + { + i = 1; + } + if ( rot[i][i] < rot[2][2] ) + { + i = 2; + } + unsigned int j = ( i + 1 ) % 3; + unsigned int k = ( j + 1 ) % 3; + T s = sqrt( rot[i][i] - rot[j][j] - rot[k][k] + 1 ); + m_quat[i] = T(0.5) * s; + s = T(0.5) / s; + m_quat[j] = ( rot[i][j] + rot[j][i] ) * s; + m_quat[k] = ( rot[i][k] + rot[k][i] ) * s; + m_quat[3] = ( rot[k][j] - rot[j][k] ) * s; + } + normalize(); + } + + template + inline Quatt & Quatt::normalize() + { + T mag = sqrt( square(m_quat[0]) + square(m_quat[1]) + square(m_quat[2]) + square(m_quat[3]) ); + T invMag = T(1) / mag; + for ( int i=0 ; i<4 ; i++ ) + { + m_quat[i] = m_quat[i] * invMag; + } + return( *this ); + } + + template<> + inline Quatt & Quatt::normalize() + { + *this = (Quatt(*this)).normalize(); + return( *this ); + } + + template + template + inline T & Quatt::operator[]( S i ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i && i <= 3 ); + return( m_quat[i] ); + } + + template + template + inline const T & Quatt::operator[]( S i ) const + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i && i <= 3 ); + return( m_quat[i] ); + } + + template + template + inline Quatt & Quatt::operator=( const Quatt & q ) + { + m_quat[0] = T(q[0]); + m_quat[1] = T(q[1]); + m_quat[2] = T(q[2]); + m_quat[3] = T(q[3]); + return( *this ); + } + + template + inline Quatt & Quatt::operator*=( const Quatt & q ) + { + *this = *this * q; + return( *this ); + } + + template + inline Quatt & Quatt::operator/=( const Quatt & q ) + { + *this = *this / q; + return( *this ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template + inline void decompose( const Quatt & q, Vecnt<3,T> & axis, T & angle ) + { + angle = 2 * acos( q[3] ); + if ( angle < std::numeric_limits::epsilon() ) + { + // no angle to rotate about => take just any one + axis[0] = 0.0f; + axis[1] = 0.0f; + axis[2] = 1.0f; + } + else + { + T dummy = 1 / sin( T(0.5) * angle ); + axis[0] = q[0] * dummy; + axis[1] = q[1] * dummy; + axis[2] = q[2] * dummy; + axis.normalize(); + } + } + + template + inline T distance( const Quatt & q0, const Quatt & q1 ) + { + return( sqrt( square( q0[0] - q1[0] ) + + square( q0[1] - q1[1] ) + + square( q0[2] - q1[2] ) + + square( q0[3] - q1[3] ) ) ); + } + + template + inline T magnitude( const Quatt & q ) + { + return( sqrt( q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3] ) ); + } + + template + inline bool operator==( const Quatt & q0, const Quatt & q1 ) + { + return( ( std::abs( q0[0] - q1[0] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[1] - q1[1] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[2] - q1[2] ) < std::numeric_limits::epsilon() ) + && ( std::abs( q0[3] - q1[3] ) < std::numeric_limits::epsilon() ) ); + } + + template + inline bool operator!=( const Quatt & q0, const Quatt & q1 ) + { + return( ! ( q0 == q1 ) ); + } + + template + inline Quatt operator~( const Quatt & q ) + { + return( Quatt( -q[0], -q[1], -q[2], q[3] ) ); + } + + template + inline Quatt operator-( const Quatt & q ) + { + return( Quatt( q[0], q[1], q[2], -q[3] ) ); + } + + template + inline Quatt operator*( const Quatt & q0, const Quatt & q1 ) + { + Quatt q( q0[3]*q1[0] + q0[0]*q1[3] - q0[1]*q1[2] + q0[2]*q1[1] + , q0[3]*q1[1] + q0[0]*q1[2] + q0[1]*q1[3] - q0[2]*q1[0] + , q0[3]*q1[2] - q0[0]*q1[1] + q0[1]*q1[0] + q0[2]*q1[3] + , q0[3]*q1[3] - q0[0]*q1[0] - q0[1]*q1[1] - q0[2]*q1[2] ); + q.normalize(); + return( q ); + } + + template + inline Vecnt<3,T> operator*( const Quatt & q, const Vecnt<3,T> & v ) + { + return( Matmnt<3,3,T>(q) * v ); + } + + template + inline Vecnt<3,T> operator*( const Vecnt<3,T> & v, const Quatt & q ) + { + return( v * Matmnt<3,3,T>(q) ); + } + + template + inline Quatt operator/( const Quatt & q0, const Quatt & q1 ) + { + return( q0 * ~q1 /*/ magnitude( q1 )*/ ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/Trafo.h b/apps/bench_shared_offscreen/dp/math/Trafo.h new file mode 100644 index 00000000..a4eb1d33 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/Trafo.h @@ -0,0 +1,264 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include + +namespace dp +{ + namespace math + { + + //! Transformation class. + /** This class is used to ease transformation handling. It has an interface to rotate, scale, + * and translate and can produce a \c Mat44f that combines them. */ + class Trafo + { + public: + //! Constructor: initialized to identity + DP_MATH_API Trafo( void ); + + //! Copy Constructor + DP_MATH_API Trafo( const Trafo &rhs ); //!< Trafo to copy + + //! Get the center of rotation of this transformation. + /** \returns \c Vec3f that describes the center or rotation. */ + DP_MATH_API const Vec3f & getCenter( void ) const; + + //! Get the rotational part of this transformation. + /** \returns \c Quatf that describes the rotational part */ + DP_MATH_API const Quatf & getOrientation( void ) const; + + //! Get the scale orientation part of this transform. + /** \return \c Quatf that describes the scale orientational part */ + DP_MATH_API const Quatf & getScaleOrientation( void ) const; + + //! Get the scaling part of this transformation. + /** \returns \c Vec3f that describes the scaling part */ + DP_MATH_API const Vec3f & getScaling( void ) const; + + //! Get the translational part of this transformation. + /** \returns \c Vec3f that describes the translational part */ + DP_MATH_API const Vec3f & getTranslation( void ) const; + + /*! \brief Get the current transformation. + * \return The \c Mat44f that describes the transformation. + * \remarks The transformation is the concatenation of the center translation C, scale + * orientation SO, scaling S, rotation R, and translation T, by the following formula: + * \code + * M = -C * SO^-1 * S * SO * R * C * T + * \endcode + * \sa getInverse */ + DP_MATH_API const Mat44f& getMatrix( void ) const; + + /*! \brief Get the current inverse transformation. + * \return The \c Mat44f that describes the inverse transformation. + * \remarks The inverse transformation is the concatenation of the center translation C, + * scale orientation SO, scaling S, rotation R, and translation T, by the following + * formula: + * \code + * M = T^-1 * C^-1 * R^-1 * SO^-1 * S^-1 * SO * -C^-1 + * \endcode + * \sa getMatrix */ + DP_MATH_API Mat44f getInverse( void ) const; + + //! Set the center of ration of the transformation. + DP_MATH_API void setCenter( const Vec3f ¢er //!< center of rotation + ); + + //! Set the \c Trafo to identity. + DP_MATH_API void setIdentity( void ); + + //! Set the rotational part of the transformation, using a quaternion. + DP_MATH_API void setOrientation( const Quatf &orientation //!< rotational part of transformation + ); + + //! Set the scale orientational part of the transformation. + DP_MATH_API void setScaleOrientation( const Quatf &scaleOrientation //!< scale orientational part of transform + ); + + //! Set the scaling part of the transformation. + DP_MATH_API void setScaling( const Vec3f &scaling //!< scaling part of transformation + ); + + //! Set the translational part of the transformation. + DP_MATH_API void setTranslation( const Vec3f &translation //!< translational part of transformation + ); + + //! Set the complete transformation by a Mat44f. + /** The matrix is internally decomposed into a translation, rotation, scaleOrientation, and scaling. */ + DP_MATH_API void setMatrix( const Mat44f &matrix //!< complete transformation + ); + + //! Copy operator. + DP_MATH_API Trafo & operator=( const Trafo &rhs //!< Trafo to copy + ); + + //! Equality operator. + /** \returns \c true if \c this is equal to \a t, otherwise \c false */ + DP_MATH_API bool operator==( const Trafo &t //!< \c Trafo to compare with + ) const; + + DP_MATH_API bool operator!=( const Trafo &t ) const; + + private: + DP_MATH_API void decompose() const; + + mutable Mat44f m_matrix; + mutable Vec3f m_center; //!< center of rotation + mutable Quatf m_orientation; //!< orientational part of the transformation + mutable Quatf m_scaleOrientation; //!< scale orientation + mutable Vec3f m_scaling; //!< scaling part of the transformation + mutable Vec3f m_translation; //!< translational part of the transformation + + mutable bool m_matrixValid; + mutable bool m_decompositionValid; + }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*! \relates dp::math::Trafo + * This calculates the linear interpolation \code ( 1 - alpha ) * t0 + alpha * t1 \endcode */ + DP_MATH_API Trafo lerp( float alpha //!< interpolation parameter + , const Trafo &t0 //!< starting value + , const Trafo &t1 //!< ending value + ); + + DP_MATH_API void lerp( float alpha, const Trafo & t0, const Trafo & t1, Trafo & tr ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlines + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline const Vec3f & Trafo::getCenter( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_center ); + } + + inline const Quatf & Trafo::getOrientation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_orientation ); + } + + inline const Quatf & Trafo::getScaleOrientation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_scaleOrientation ); + } + + inline const Vec3f & Trafo::getScaling( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_scaling ); + } + + inline const Vec3f & Trafo::getTranslation( void ) const + { + if ( !m_decompositionValid ) + { + decompose(); + } + return( m_translation ); + } + + inline void Trafo::setCenter( const Vec3f ¢er ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_center = center; + } + + inline void Trafo::setOrientation( const Quatf &orientation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_orientation = orientation; + } + + inline void Trafo::setScaleOrientation( const Quatf &scaleOrientation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_scaleOrientation = scaleOrientation; + } + + inline void Trafo::setScaling( const Vec3f &scaling ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_scaling = scaling; + } + + inline void Trafo::setTranslation( const Vec3f &translation ) + { + if ( !m_decompositionValid ) + { + decompose(); + } + m_matrixValid = false; + m_translation = translation; + } + + inline bool Trafo::operator!=( const Trafo & t ) const + { + return( ! ( *this == t ) ); + } + + inline void lerp( float alpha, const Trafo & t0, const Trafo & t1, Trafo & tr ) + { + tr = lerp( alpha, t0, t1 ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/Vecnt.h b/apps/bench_shared_offscreen/dp/math/Vecnt.h new file mode 100644 index 00000000..8c1061dc --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/Vecnt.h @@ -0,0 +1,1223 @@ +// Copyright (c) 2002-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include +#include +#include + +//#include +#include "inc/MyAssert.h" + +namespace dp +{ + namespace math + { + + template class Spherent; + + /*! \brief Vector class of fixed size and type. + * \remarks This class is templated by size and type. It holds \a n values of type \a T. There + * are typedefs for the most common usage with 2, 3, and 4 values of type \c float and \c double: + * Vec2f, Vec2d, Vec3f, Vec3d, Vec4f, Vec4d. */ + template class Vecnt + { + public: + /*! \brief Default constructor. + * \remarks For performance reasons, no initialization is performed. */ + Vecnt(); + + /*! \brief Copy constructor from a vector of possibly different size and type. + * \param rhs A vector with \a m values of type \a S. + * \remarks The minimum \a k of \a n and \a m is determined. The first \a k values of type \a + * S from \a rhs are converted to type \a T and assigned as the first \a k values of \c this. + * If \a k is less than \a n, the \a n - \a k last values of \c this are not initialized. */ + template + explicit Vecnt( const Vecnt & rhs ); + + /*! \brief Copy constructor from a vector with one less value than \c this, and an explicit last value. + * \param rhs A vector with \a m values of type \a S, where \a m has to be one less than \a n. + * \param last A single value of type \a R, that will be set as the last value of \c this. + * \remarks This constructor contains a compile-time assertion, to make sure that \a m is one + * less than \a n. The values of \a rhs of type \a S are converted to type \a T and assigned + * as the first values of \c this. The value \a last of type \a R also is converted to type + * \a T and assigned as the last value of \c this. + * \par Example: + * \code + * Vec3f v3f(0.0f,0.0f,0.0f); + * Vec4f v4f(v3f,1.0f); + * \endcode */ + template + Vecnt( const Vecnt & rhs, R last ); + + /*! \brief Constructor for a one-element vector. + * \param x First element of the vector. + * \remarks This constructor can only be used with one-element vectors. + * \par Example: + * \code + * Vec1f v1f( 1.0f ); + * Vecnt<1,int> v1i( 0 ); + * \endcode */ + Vecnt( T x); + + /*! \brief Constructor for a two-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \remarks This constructor can only be used with two-element vectors. + * \par Example: + * \code + * Vec2f v2f( 1.0f, 2.0f ); + * Vecnt<2,int> v2i( 0, 1 ); + * \endcode */ + Vecnt( T x, T y ); + + /*! \brief Constructor for a three-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \param z Third element of the vector. + * \remarks This constructor contains a compile-time assertion, to make sure it is used for + * three-element vectors, like Vec3f, only. + * \par Example: + * \code + * Vec3f v3f( 1.0f, 2.0f, 3.0f ); + * Vecnt<3,int> v3i( 0, 1, 2 ); + * \endcode */ + Vecnt( T x, T y, T z ); + + /*! \brief Constructor for a four-element vector. + * \param x First element of the vector. + * \param y Second element of the vector. + * \param z Third element of the vector. + * \param w Fourth element of the vector. + * \remarks This constructor contains a compile-time assertion, to make sure it is used for + * four-element vectors, like Vec4f, only. + * \par Example: + * \code + * Vec4f v4f( 1.0f, 2.0f, 3.0f, 4.0f ); + * Vecnt<4,int> v4i( 0, 1, 2, 3 ); + * \endcode */ + Vecnt( T x, T y, T z, T w ); + + public: + /*! \brief Get a pointer to the constant values of this vector. + * \return A pointer to the constant values of this vector. + * \remarks It is assured, that the values of a vector are contiguous. + * \par Example: + * \code + * GLColor3fv( p->getDiffuseColor().getPtr() ); + * \endcode */ + const T * getPtr() const; + + /*! \brief Normalize this vector and get it's previous length. + * \return The length of the vector before the normalization. */ + T normalize(); + + /*! \brief Access operator to the values of a vector. + * \param i The index of the value to access. + * \return A reference to the value at position \a i in this vector. + * \remarks The index \a i has to be less than the size of the vector, given by the template + * argument \a n. + * \note The behavior is undefined if \ i is greater or equal to \a n. */ + T & operator[]( size_t i ); + + /*! \brief Constant access operator to the values of a vector. + * \param i The index of the value to access. + * \return A constant reference to the value at position \a i in this vector. + * \remarks The index \a i has to be less than the size of the vector, given by the template + * argument \a n. + * \note The behavior is undefined if \ i is greater or equal to \a n. */ + const T & operator[]( size_t i ) const; + + /*! \brief Vector assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to assign to \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise assigned to the values of \c this. */ + template + Vecnt & operator=( const Vecnt & rhs ); + + /*! \brief Vector addition and assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to add to \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise added to the values of \c this. */ + template + Vecnt & operator+=( const Vecnt & rhs ); + + /*! \brief Vector subtraction and assignment operator with a vector of possibly different type. + * \param rhs A constant reference to the vector to subtract from \c this. + * \return A reference to \c this. + * \remarks The values of \a rhs are component-wise subtracted from the values of \c this. */ + template + Vecnt & operator-=( const Vecnt & rhs ); + + /*! \brief Scalar multiplication and assignment operator with a scalar of possibly different type. + * \param s A scalar to multiply \c this with. + * \return A reference to \c this. + * \remarks The values of \c this are component-wise multiplied with \a s. */ + template + Vecnt & operator*=( S s ); + + /*! \brief Scalar division and assignment operator with a scalar of possibly different type. + * \param s A scalar to divide \c this by. + * \return A reference to \c this. + * \remarks The values of \c this are component-wise divided by \a s. + * \note The behavior is undefined if \a s is less than the type-dependent epsilon. */ + template + Vecnt & operator/=( S s ); + + /*! \brief Orthonormalize \c this with respect to the vector \a v. + * \param v A constant reference to the vector to orthonormalize \c this to. + * \remarks Subtracts the orthogonal projection of \c this on \a v from \c this and + * normalizes the it, resulting in a normalized vector that is orthogonal to \a v. */ + void orthonormalize( const Vecnt & v ); + + private: + T m_vec[n]; + }; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine the bounding box of a number of points. + * \param points A vector to the points. + * \param numberOfPoints The number of points in \a points. + * \param min Reference to the calculated lower left front vertex of the bounding box. + * \param max Reference to the calculated upper right back vertex of the bounding box. */ + template + void boundingBox( const Vecnt * points, size_t numberOfPoints, Vecnt & min, Vecnt & max ); + + /*! \brief Determine the bounding box of a number of points. + * \param points A random access iterator to the points. + * \param numberOfPoints The number of points in \a points. + * \param min Reference to the calculated lower left front vertex of the bounding box. + * \param max Reference to the calculated upper right back vertex of the bounding box. */ + template + inline void boundingBox( RandomAccessIterator points, size_t numberOfPoints, Vecnt & min, Vecnt & max ); + + /*! \brief Determine if two vectors point in opposite directions. + * \param v0 A constant reference to the first normalized vector to use. + * \param v1 A constant reference to the second normalized vector to use. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v0 and \a v1 are anti-parallel, otherwise \c false. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \sa areCollinear, isNormalized */ + template + bool areOpposite( const Vecnt & v0, const Vecnt & v1 + , T eps = std::numeric_limits::epsilon() ) + { + return( ( 1 + v0 * v1 ) <= eps ); + } + + /*! \brief Determine if two vectors are orthogonal. + * \param v0 A constant reference to the first vector to use. + * \param v1 A constant reference to the second vector to use. + * \param eps An optional deviation from orthonormality. The default is the type dependent + * epsilon. + * \return \c true, if \a v0 and \a v1 are orthogonal, otherwise \c false. + * \sa areOrthonormal */ + template + bool areOrthogonal( const Vecnt & v0, const Vecnt & v1 + , T eps = 2 * std::numeric_limits::epsilon() ) + { + return( std::abs(v0*v1) <= std::max(T(1),length(v0)) * std::max(T(1),length(v1)) * eps ); + } + + /*! \brief Determine if two vectors are orthonormal. + * \param v0 A constant reference to the first normalized vector to use. + * \param v1 A constant reference to the second normalized vector to use. + * \param eps An optional deviation from orthonormality. The default is the type dependent + * epsilon. + * \return \c true, if \a v0 and \a v1 are orthonormal, otherwise \c false. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \sa areOrthogonal */ + template + bool areOrthonormal( const Vecnt & v0, const Vecnt & v1 + , T eps = 2 * std::numeric_limits::epsilon() ) + { + return( isNormalized(v0) && isNormalized(v1) && ( std::abs(v0*v1) <= eps ) ); + } + + /*! \brief Determine if two vectors differ less than a given epsilon in each component. + * \param v0 A constant reference to the first vector to use. + * \param v1 A constant reference to the second vector to use. + * \param eps The acceptable deviation for each component. + * \return \c true, if \ v0 and \a v1 differ less than or equal to \a eps in each component, otherwise \c + * false. + * \sa distance, operator==() */ + template + bool areSimilar( const Vecnt & v0, const Vecnt & v1, T eps ); + + /*! \brief Determine the distance between vectors. + * \param v0 A constant reference to the first vector. + * \param v1 A constant reference to the second vector. + * \return The euclidean distance between \a v0 and \a v1. + * \sa length, lengthSquared */ + template + T distance( const Vecnt & v0, const Vecnt & v1 ); + + template + T intensity( const Vecnt & v ); + + /*! \brief Determine if a vector is normalized. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v is normalized, otherwise \c false. + * \sa isNull, length, lengthSquared, normalize */ + template + bool isNormalized( const Vecnt & v, T eps = 2 * std::numeric_limits::epsilon() ) + { + return( std::abs( length( v ) - 1 ) <= eps ); + } + + /*! \brief Determine if a vector is the null vector. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v is the null vector, otherwise \c false. + * \sa isNormalized, length, lengthSquared */ + template + bool isNull( const Vecnt & v, T eps = std::numeric_limits::epsilon() ) + { + return( length( v ) <= eps ); + } + + /*! \brief Determine if a vector is uniform, that is, all its components are equal. + * \param v A constant reference to the vector to test. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if all components of \a v are equal, otherwise \c false. */ + template + bool isUniform( const Vecnt & v, T eps = std::numeric_limits::epsilon() ) + { + bool uniform = true; + for ( unsigned int i=1 ; i + T length( const Vecnt & v ); + + /*! \brief Determine the squared length of a vector. + * \param v A constant reference to the vector to use. + * \return The squared length of the vector \a v. + * \sa length */ + template + T lengthSquared( const Vecnt & v ); + + /*! \brief Determine the maximal element of a vector. + * \param v A constant reference to the vector to use. + * \return The largest absolute value of \a v. + * \sa minElement */ + template + T maxElement( const Vecnt & v ); + + /*! \brief Determine the minimal element of a vector. + * \param v A constant reference to the vector to use. + * \return The smallest absolute value of \a v. + * \sa maxElement */ + template + T minElement( const Vecnt & v ); + + /*! \brief Normalize a vector. + * \param v A reference to the vector to normalize. + * \return The length of the unnormalized vector. + * \sa isNormalized, length */ + template + T normalize( Vecnt & v ); + + /*! \brief Test for equality of two vectors. + * \param v0 A constant reference to the first vector to test. + * \param v1 A constant reference to the second vector to test. + * \return \c true, if the two vectors component-wise differ less than the type dependent + * epsilon, otherwise \c false. + * \sa operator!=() */ + template + bool operator==( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Test for inequality of two vectors. + * \param v0 A constant reference to the first vector to test. + * \param v1 A constant reference to the second vector to test. + * \return \c true, if the two vectors component-wise differ more than the type dependent + * epsilon in at least one component, otherwise \c false. + * \sa operator==() */ + template + bool operator!=( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Vector addition operator + * \param v0 A constant reference to the left operand. + * \param v1 A second reference to the right operand. + * \return A vector holding the component-wise sum of \a v0 and \a v1. + * \sa operator-() */ + template + Vecnt operator+( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Unary vector negation operator. + * \param v A constant reference to a vector. + * \return A vector holding the component-wise negation of \a v. + * \sa operator-() */ + template + Vecnt operator-( const Vecnt & v ); + + /*! \brief Vector subtraction operator. + * \param v0 A constant reference to the left operand. + * \param v1 A second reference to the right operand. + * \return A vector holding the component-wise difference of \a v0 and \a v1. + * \sa operator+() */ + template + Vecnt operator-( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Scalar multiplication of a vector. + * \param v A constant reference to the left operand. + * \param s A scalar as the right operand. + * \return A vector holding the component-wise product with s. + * \sa operator/() */ + template + Vecnt operator*( const Vecnt & v, S s ); + + /*! \brief Scalar multiplication of a vector. + * \param s A scalar as the left operand. + * \param v A constant reference to the right operand. + * \return A vector holding the component-wise product with \a s. + * \sa operator/() */ + template + Vecnt operator*( S s, const Vecnt & v ); + + /*! \brief Vector multiplication. + * \param v0 A constant reference to the left operand. + * \param v1 A constant reference to the right operand. + * \return The dot product of \a v0 and \a v1. + * \sa operator^() */ + template + T operator*( const Vecnt & v0, const Vecnt & v1 ); + + /*! \brief Scalar division of a vector. + * \param v A constant reference to the left operand. + * \param s A scalar as the right operand. + * \return A vector holding the component-wise division by \a s. + * \sa operator*() */ + template + Vecnt operator/( const Vecnt & v, S s ); + + /*! \brief Determine a vector that's orthonormal to \a v. + * \param v A vector to determine an orthonormal vector to. + * \return A vector that's orthonormal to \a v. + * \note The result of this function is not uniquely defined. In two dimensions, the orthonormal + * to a vector can be one of two anti-parallel vectors. In higher dimensions, there are infinitely + * many possible results. This function just select one of them. + * \sa orthonormalize */ + template + Vecnt orthonormal( const Vecnt & v ); + + /*! \brief Determine the orthonormal vector of \a v1 with respect to \a v0. + * \param v0 A constant reference to the normalized vector to orthonormalize against. + * \param v1 A constant reference to the normalized vector to orthonormalize. + * \return A normalized vector representing the orthonormalized version of \a v1 with respect to + * \a v0. + * \note The behavior is undefined if \a v0 or \a v1 are not normalized. + * \par Example: + * \code + * Vec3f newYAxis = orthonormalize( newZAxis, oldYAxis ); + * \endcode + * \sa normalize */ + template + Vecnt orthonormalize( const Vecnt & v0, const Vecnt & v1 ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 1 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a two-component vector. + * \param v A reference to the vector to set with \a x. + * \param x The first component to set. */ + template + Vecnt<1,T> & setVec( Vecnt<1,T> & v, T x ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 2 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a two-component vector. + * \param v A reference to the vector to set with \a x and \a y. + * \param x The first component to set. + * \param y The second component to set. */ + template + Vecnt<2,T> & setVec( Vecnt<2,T> & v, T x, T y ); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Determine if two vectors are collinear. + * \param v0 A constant reference to the first vector. + * \param v1 A constant reference to the second vector. + * \param eps An optional type dependent epsilon, defining the acceptable deviation. + * \return \c true, if \a v0 and \a v1 are collinear, otherwise \c false. + * \sa areOpposite */ + template + bool areCollinear( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 + , T eps = std::numeric_limits::epsilon() ) + { + return( length( v0 ^ v1 ) < eps ); + } + + /*! \brief Cross product operator. + * \param v0 A constant reference to the left operand. + * \param v1 A constant reference to the right operand. + * \return A vector that is the cross product of v0 and v1. + * \sa operator*() */ + template + Vecnt<3,T> operator^( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 ); + + /*! \brief Set the values of a three-component vector. + * \param v A reference to the vector to set with \a x, \a y and \a z. + * \param x The first component to set. + * \param y The second component to set. + * \param z The third component to set. */ + template + Vecnt<3,T> & setVec( Vecnt<3,T> & v, T x, T y, T z ); + + /*! \brief Determine smoothed normals for a set of vertices. + * \param vertices A constant reference to a vector of vertices. + * \param sphere A constant reference to the bounding sphere of the vertices. + * \param creaseAngle The angle in radians to crease at. + * \param normals A reference to a vector of normals. + * \remarks Given \a vertices with \a normals and their bounding \a sphere, this function + * calculates smoothed normals in \a normals for the vertices. For all vertices, that are similar + * within a tolerance that depends on the radius of \a sphere, and whose normals differ less than + * \a creaseAngle (in radians), their normals are set to the average of their normals. */ + template + void smoothNormals( const std::vector > &vertices, const Spherent<3,T> &sphere + , T creaseAngle, std::vector > &normals ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // non-member functions, specialized for n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*! \brief Set the values of a four-component vector. + * \param v A reference to the vector to set with \a x, \a y, \a z, and \a w. + * \param x The first component to set. + * \param y The second component to set. + * \param z The third component to set. + * \param w The fourth component to set. */ + template + Vecnt<4,T> & setVec( Vecnt<4,T> & v, T x, T y, T z, T w ); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Convenience type definitions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + typedef Vecnt<1,float> Vec1f; + typedef Vecnt<1,double> Vec1d; + typedef Vecnt<2,float> Vec2f; + typedef Vecnt<2,double> Vec2d; + typedef Vecnt<3,float> Vec3f; + typedef Vecnt<3,double> Vec3d; + typedef Vecnt<4,float> Vec4f; + typedef Vecnt<4,double> Vec4d; + typedef Vecnt<1,int> Vec1i; + typedef Vecnt<2,int> Vec2i; + typedef Vecnt<3,int> Vec3i; + typedef Vecnt<4,int> Vec4i; + typedef Vecnt<1,unsigned int> Vec1ui; + typedef Vecnt<2,unsigned int> Vec2ui; + typedef Vecnt<3,unsigned int> Vec3ui; + typedef Vecnt<4,unsigned int> Vec4ui; + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt::Vecnt() + { + } + + template + template + inline Vecnt::Vecnt( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt::Vecnt( const Vecnt & rhs, R last ) + { + for ( unsigned int i=0 ; i + inline Vecnt::Vecnt( T x ) + { + setVec( *this, x ); + } + + template + inline Vecnt::Vecnt( T x, T y ) + { + setVec( *this, x, y ); + } + + template + inline Vecnt::Vecnt( T x, T y, T z ) + { + setVec( *this, x, y, z ); + } + + template + inline Vecnt::Vecnt( T x, T y, T z, T w ) + { + setVec( *this, x, y, z, w ); + } + + template + inline const T * Vecnt::getPtr() const + { + return( &m_vec[0] ); + } + + template + inline T Vecnt::normalize() + { + return( dp::math::normalize( *this ) ); + } + + template + inline T & Vecnt::operator[]( size_t i ) + { + MY_ASSERT( i < n ); + return( m_vec[i] ); + } + + template + inline const T & Vecnt::operator[]( size_t i ) const + { + MY_ASSERT( i < n ); + return( m_vec[i] ); + } + + template + template + inline Vecnt & Vecnt::operator=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator+=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator-=( const Vecnt & rhs ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator*=( S s ) + { + for ( unsigned int i=0 ; i + template + inline Vecnt & Vecnt::operator/=( S s ) + { + for ( unsigned int i=0 ; i + inline void Vecnt::orthonormalize( const Vecnt & v ) + { + *this = dp::math::orthonormalize( v, *this ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline void boundingBox( const Vecnt * points, size_t numberOfPoints, Vecnt & min, Vecnt & max ) + { + MY_ASSERT( 0 < n ); + min = max = points[0]; + for ( size_t i=1 ; i + inline void boundingBox( RandomAccessIterator points, size_t numberOfPoints, Vecnt & min, Vecnt & max ) + { + MY_ASSERT( 0 < n ); + min = max = points[0]; + for ( size_t i=1 ; i + inline bool areSimilar( const Vecnt & v0, const Vecnt & v1, T eps ) + { + for ( unsigned int i=0 ; i + inline T distance( const Vecnt & v0, const Vecnt & v1 ) + { + return( length( v0 - v1 ) ); + } + + template + inline T intensity( const Vecnt & v ) + { + T intens(0); + for ( unsigned int i=0 ; i + inline T length( const Vecnt & v ) + { + return( sqrt( lengthSquared( v ) ) ); + } + + template + inline T lengthSquared( const Vecnt & v ) + { + return( v * v ); + } + + template + inline T maxElement( const Vecnt & v ) + { + T me = std::abs( v[0] ); + for ( unsigned int i=1 ; i + inline T minElement( const Vecnt & v ) + { + T me = std::abs( v[0] ); + for ( unsigned int i=1 ; i + inline T normalize( Vecnt & v ) + { + T norm = length( v ); + if ( std::numeric_limits::epsilon() < norm ) + { + v /= norm; + } + return( norm ); + } + + template + inline float normalize( Vecnt & v ) + { + Vecnt vd(v); + double norm = normalize( vd ); + v = vd; + return( (float)norm ); + } + + template + inline bool operator==( const Vecnt & v0, const Vecnt & v1 ) + { + for (unsigned int i = 0; i < n; i++) + { + if (v0[i] != v1[i]) + { + return(false); + } + } + return(true); + } + + template + inline bool operator==(const Vecnt<1, T> & v0, const Vecnt<1, T> & v1) + { + return (v0[0] == v1[0]); + } + + template + inline bool operator==(const Vecnt<2, T> & v0, const Vecnt<2, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]); + } + + + template + inline bool operator==(const Vecnt<3, T> & v0, const Vecnt<3, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]) && (v0[2] == v1[2]); + } + + template + inline bool operator==(const Vecnt<4, T> & v0, const Vecnt<3, T> & v1) + { + return (v0[0] == v1[0]) && (v0[1] == v1[1]) && (v0[2] == v1[2]) && (v0[3] == v1[3]); + } + + template + inline bool operator!=( const Vecnt & v0, const Vecnt & v1 ) + { + return( ! ( v0 == v1 ) ); + } + + template + inline Vecnt operator+( const Vecnt & v0, const Vecnt & v1 ) + { + Vecnt ret(v0); + ret += v1; + return( ret ); + } + + template + inline Vecnt operator-( const Vecnt & v ) + { + Vecnt ret; + for ( unsigned int i=0 ; i + inline Vecnt operator-( const Vecnt & v0, const Vecnt & v1 ) + { + Vecnt ret(v0); + ret -= v1; + return( ret ); + } + + template + inline Vecnt operator*( const Vecnt & v, S s ) + { + Vecnt ret(v); + ret *= s; + return( ret ); + } + + template + inline Vecnt operator*( S s, const Vecnt & v ) + { + return( v * s ); + } + + template + inline T operator*( const Vecnt & v0, const Vecnt & v1 ) + { + T ret(0); + for ( unsigned int i=0 ; i + inline Vecnt operator/( const Vecnt & v, S s ) + { + Vecnt ret(v); + ret /= s; + return( ret ); + } + + template + inline bool operator<(const Vecnt& lhs, const Vecnt& rhs) + { + for ( unsigned int i=0 ; i + inline bool operator>(const Vecnt& lhs, const Vecnt& rhs) + { + for ( unsigned int i=0 ; i + inline Vecnt orthonormal( const Vecnt & v ) + { + MY_STATIC_ASSERT( 1 < n ); + MY_STATIC_ASSERT( ! std::numeric_limits::is_integer ); + + T firstV(-1), secondV(-1); + unsigned int firstI(0), secondI(0); + Vecnt result; + for ( unsigned int i=0 ; i + inline Vecnt orthonormalize( const Vecnt & v0, const Vecnt & v1 ) + { + // determine the orthogonal projection of v1 on v0 : ( v0 * v1 ) * v0 + // and subtract it from v1 resulting in the orthogonalized version of v1 + Vecnt vr = v1 - ( v0 * v1 ) * v0; + vr.normalize(); + // don't assert the general case, because this orthonormalization is far from exact + return( vr ); + } + + template + inline Vecnt<3,T> orthonormalize( const Vecnt<3,T> & v0, const Vecnt<3,T> & v1 ) + { + Vecnt<3,T> vr = v0 ^ ( v1 ^ v0 ); + vr.normalize(); + return( vr ); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 1 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<1,T> & setVec( Vecnt<1,T> & v, T x ) + { + v[0] = x; + return( v ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 2 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<2,T> & setVec( Vecnt<2,T> & v, T x, T y ) + { + v[0] = x; + v[1] = y; + return( v ); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 3 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<3,T> operator^( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1 ) + { + Vecnt<3,T> ret; + ret[0] = v0[1] * v1[2] - v0[2] * v1[1]; + ret[1] = v0[2] * v1[0] - v0[0] * v1[2]; + ret[2] = v0[0] * v1[1] - v0[1] * v1[0]; + return( ret ); + } + + template + inline Vecnt<3,T> & setVec( Vecnt<3,T> & v, T x, T y, T z ) + { + v[0] = x; + v[1] = y; + v[2] = z; + return( v ); + } + + template + size_t _hash( const Vecnt<3,T> &vertex, const Vecnt<3,T> &base, const Vecnt<3,T> &scale + , size_t numVertices ) + { + #if 1 + size_t value = static_cast(floor( ( vertex - base ) * scale )); + MY_ASSERT( value < numVertices ); + return( value ); + #else + int value = (int) floor( ( vertex - base ) * scale ); + if ( value < 0 ) + { + value = 0; + } + else if ( numVertices <= value ) + { + value = (int) numVertices - 1; + } + return( value ); + #endif + } + + template + inline void smoothNormals( const std::vector > &vertices, const Spherent<3,T> &sphere + , T creaseAngle, std::vector > &normals ) + { + if ( creaseAngle < T(0.01) ) + { + for_each( normals.begin(), normals.end(), std::mem_fun_ref(&Vecnt<3,T>::normalize) ); + } + else + { + // the face normals are un-normalized (for weighting them in the smoothing process) + // and we need the normalized too + std::vector > normalizedNormals(normals); + for_each( normalizedNormals.begin(), normalizedNormals.end() + , std::mem_fun_ref(&Vecnt<3,T>::normalize) ); + + // the tolerance to distinguish different points is a function of the given radius + T tolerance = sphere.getRadius() / 10000; + + // the toleranceVector is used to determine all slices of the bounding box (_hash entries) + // within the given tolerance from the current point + Vecnt<3,T> toleranceVector( tolerance, tolerance, tolerance ); + + // we create a _hash by using diagonal slices through the bounding box (or some approximation + // to it) + T halfWidth = sphere.getRadius() + tolerance; + Vecnt<3,T> hashBase = sphere.getCenter() - Vecnt<3,T>( halfWidth, halfWidth, halfWidth ); + + // The _hash function is a linear function of x, y, and z such that one corner of the + // bounding box (hashBase) maps to the key "0" and the opposite corner maps to the key + // "numVertices()". + T scale = vertices.size() / ( 3 * 2 * halfWidth ) ; + Vecnt<3,T> hashScale( scale, scale, scale ); + + // The "indirect" table is a circularly linked list of indices that are within tolerance of + // each other. + std::vector indirect( vertices.size() ); + + // create a _hash table as a vector of lists of indices into the vertex array + std::vector > hashTable( vertices.size() ); + for ( size_t i=0 ; i::const_iterator hlit=hashTable[hv].begin() + ; ! found && hlit!=hashTable[hv].end() + ; ++hlit ) + { + if ( areSimilar( vertices[i], vertices[*hlit], tolerance ) ) + { + indirect[i] = indirect[*hlit]; + indirect[*hlit] = i; + found = true; + } + } + } + + if ( ! found ) + { + // there is no similar (previous) point, so add it to the hashTable + size_t hashValue = _hash( vertices[i], hashBase, hashScale, vertices.size() ); + hashTable[hashValue].push_back( i ); + indirect[i] = i; + } + } + + // now the indirect vector maps all vertices i to the (similar) vertex j + std::vector > vertexNormals( vertices.size() ); + T cosCreaseAngle = cos( creaseAngle ); + for ( size_t i=0 ; i sum = normals[i]; + for ( size_t j=indirect[i] ; j!=i ; j=indirect[j] ) + { + if ( normalizedNormals[i] * normalizedNormals[j] > cosCreaseAngle ) + { + sum += normals[j]; + } + } + sum.normalize(); + vertexNormals[i] = sum; + } + + // finally, set the vertex normals + normals.swap( vertexNormals ); + } + } + + /*! \brief Compute the scalar triple product (v0 ^ v1) * v2. + **/ + template + inline T scalarTripleProduct( const Vecnt<3,T> &v0, const Vecnt<3,T> &v1, const Vecnt<3,T> &v2 ) + { + return (v0 ^ v1) * v2; + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // inlined non-member functions, specialized for n == 4 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + inline Vecnt<4,T> & setVec( Vecnt<4,T> & v, T x, T y, T z, T w ) + { + v[0] = x; + v[1] = y; + v[2] = z; + v[3] = w; + return( v ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/math.h b/apps/bench_shared_offscreen/dp/math/math.h new file mode 100644 index 00000000..4177e822 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/math.h @@ -0,0 +1,401 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + +#pragma once +/** @file */ + +#include +#include +#include +#include +#include + +//#include +#include "inc/MyAssert.h" +#include + + +namespace dp +{ + namespace math + { + + // define the stand trig values + #ifdef PI + # undef PI + # undef PI_HALF + # undef PI_QUARTER + #endif + //! constant PI + const float PI = 4 * atan( 1.0f ); + //! constant PI half + const float PI_HALF = 0.5f * PI; + //! constant PI quarter + const float PI_QUARTER = 0.25f * PI; + //! constant square root two + const float SQRT_TWO = sqrt( 2.0f ); + //! constant square root two half + const float SQRT_TWO_HALF = 0.5f * SQRT_TWO; + //! constant square root three + const float SQRT_THREE = sqrt( 3.0f ); + //! constant square root three + const float SQRT_THREE_HALF = 0.5f * SQRT_THREE; + + const double LOG_TWO = log( 2.0f ); + const double ONE_OVER_LOG_TWO = 1.0f / LOG_TWO; + + //! Template to clamp an object of type T to a lower and an upper limit. + /** \returns clamped value of \a v between \a l and \a u */ + template T clamp( T v //!< value to clamp + , T l //!< lower limit + , T u //!< upper limit + ) + { + return std::min(u, std::max(l,v)); + } + + //! Template to cube an object of Type T. + /** \param t value to cube + * \returns triple product of \a t with itself */ + template + inline T cube( const T& t ) + { + return( t * t * t ); + } + + //! Transform an angle in degrees to radians. + /** \returns angle in radians */ + template + inline T degToRad( T angle ) + { + return( angle*(PI/180) ); + } + + template + inline T exp2( T x ) + { + return( (T)pow( T(2), x ) ); + } + + /*! \brief Returns the highest bit set for the specified positive input value */ + template + inline int highestBit( T i ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i ); + int hb = -1; + for ( T h = i ; h ; h>>=1, hb++ ) + ; + return hb; + } + + /*! \brief Returns the highest bit value for the specified positive input value */ + template + inline T highestBitValue(T i) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + MY_ASSERT( 0 <= i ); + T h = 0; + while (i) + { + h = (i & (~i+1)); // grab lowest bit + i &= ~h; // clear lowest bit + } + return h; + } + + /*! \brief Calculate the vertical field of view out of the horizontal field of view */ + inline float horizontalToVerticalFieldOfView( float hFoV, float aspectRatio ) + { + return( 2.0f * atanf( tanf( 0.5f * hFoV ) / aspectRatio ) ); + } + + //! Determine if an integer is a power of two. + /** \returns \c true if \a n is a power of two, otherwise \c false */ + template + inline bool isPowerOfTwo( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + return (n && !(n & (n-1))); + } + + //! Linear interpolation between two values \a v0 and \a v1. + /** v = ( 1 - alpha ) * v0 + alpha * v1 */ + template + inline T lerp( float alpha //!< interpolation parameter + , const T &v0 //!< starting value + , const T &v1 //!< ending value + ) + { + return( (T)( ( 1.0f - alpha ) * v0 + alpha * v1 ) ); + } + + template + inline void lerp( float alpha, const T & v0, const T & v1, T & vr ) + { + vr = (T)( ( 1.0f - alpha ) * v0 + alpha * v1 ); + } + + template + inline double log2( T x ) + { + return( ONE_OVER_LOG_TWO * log( x ) ); + } + + /*! \brief Determine the maximal value out of three. + * \param a A constant reference of the first value to consider. + * \param b A constant reference of the second value to consider. + * \param c A constant reference of the third value to consider. + * \return A constant reference to the maximal value out of \a a, \a b, and \a c. + * \sa min */ + template + inline const T & max( const T &a, const T &b, const T &c ) + { + return( std::max( a, std::max( b, c ) ) ); + } + + /*! \brief Determine the minimal value out of three. + * \param a A constant reference of the first value to consider. + * \param b A constant reference of the second value to consider. + * \param c A constant reference of the third value to consider. + * \return A constant reference to the minimal value out of \a a, \a b, and \a c. + * \sa max */ + template + inline const T & min( const T &a, const T &b, const T &c ) + { + return( std::min( a, std::min( b, c ) ) ); + } + + /*! \brief Determine the smallest power of two equal to or larger than \a n. + * \param n The value to determine the nearest power of two above. + * \return \a n, if \a n is a power of two, otherwise the smallest power of two above \a n. + * \sa powerOfTowBelow */ + template + inline T powerOfTwoAbove( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return( n ); + } + else + { + return highestBitValue(n) << 1; + } + } + + /*! \brief Determine the largest power of two equal to or smaller than \a n. + * \param n The value to determine the nearest power of two below. + * \return \a n, if \a n is a power of two, otherwise the largest power of two below \a n. + * \sa powerOfTowAbove */ + template + inline T powerOfTwoBelow( T n ) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return( n ); + } + else + { + return highestBitValue(n); + } + } + + /*! \brief Calculates the nearest power of two for the specified integer. + * \param n Integer for which to calculate the nearest power of two. + * \returns nearest power of two for integer \a n. */ + template + inline T powerOfTwoNearest( T n) + { + MY_STATIC_ASSERT( std::numeric_limits::is_integer ); + if ( isPowerOfTwo( n ) ) + { + return n; + } + + T prev = highestBitValue(n); // POT below + T next = prev<<1; // POT above + return (next-n) < (n-prev) ? next : prev; // return nearest + } + + //! Transform an angle in radian to degree. + /** \param angle angle in radians + * \returns angle in degrees */ + inline float radToDeg( float angle ) + { + return( angle*180.0f/PI ); + } + + //! Determine the sign of a scalar. + /** \param t scalar value + * \returns sign of \a t */ + template + inline int sign( const T &t ) + { + return( ( t < 0 ) ? -1 : ( ( t > 0 ) ? 1 : 0 ) ); + } + + //! Solve the quadratic equation a*x^2 + b*x + c = 0 + /** \param a Coefficient of the quadratic term. + * \param b Coefficient of the linear term. + * \param c Coefficient of the constant term. + * \param x0 Reference to hold the first of up to two solutions. + * \param x1 Reference to hold the second of up to two solutions. + * \returns The number of real solutions (0, 1, or 2) + * \remarks If 2 is returned, x0 and x1 hold those two solutions. If 1 is returned, it is a double solution + * (turning point), returned in x0. If 0 is returned, there are only two complex conjugated solution, that + * are not calculated here. */ + template + inline unsigned int solveQuadraticEquation( T a, T b, T c, T & x0, T & x1 ) + { + if ( std::numeric_limits::epsilon() < fabs( a ) ) + { + T D = b * b - 4 * a * c; + if ( 0 < D ) + { + D = sqrt( D ); + x0 = 0.5f * ( - b + D ) / a; + x1 = 0.5f * ( - b - D ) / a; + return( 2 ); + } + if ( D < 0 ) + { + return( 0 ); + } + x0 = - 0.5f * b / a; + return( 1 ); + } + if ( std::numeric_limits::epsilon() < fabs( b ) ) + { + x0 = - c / b; + return( 1 ); + } + return( 0 ); + } + + //! Solve the cubic equation a*x^3 + b*x^2 + c*x + d = 0 + /** \param a Coefficient of the cubic term. + * \param b Coefficient of the quadratic term. + * \param c Coefficient of the linear term. + * \param d Coefficient of the constant term. + * \param x0 Reference to hold the first of up to three solutions. + * \param x1 Reference to hold the second of up to three solutions. + * \param x2 Reference to hold the second of up to three solutions. + * \returns The number of real solutions (0, 1, 2, or 3) + * \remarks If the absolute value of \a a is less than the type specific epsilon, the cubic equation is handled + * as a quadratic only. + * If 3 is returned, x0, x1, and x2 hold those three solutions. If 2 is returned, the cubic equation + * in fact was a quadratic one, and x0 and x1 hold those two solutions. If 1 is returned, the cubic equation + * has two conjugate complex, and one real solution; as complex numbers are not treated by this function, it is + * just the real solution returned in x0. If 0 is returned, the cubic equationn again war in fact a quadratic one, + * and there are only two complex conjugated solution, that are not calculated here. */ + template + inline unsigned int solveCubicEquation( T a, T b, T c, T d, T & x0, T & x1, T & x2 ) + { + if ( std::numeric_limits::epsilon() < abs( a ) ) + { + T bOverThreeA = b / ( 3 * a ); + T p = - square( bOverThreeA ) + c / ( 3 * a ); + T q = cube( bOverThreeA ) - T(0.5) * bOverThreeA * c / a + T(0.5) * d / a; + T discriminant = square( q ) + cube( p ); + if ( discriminant < - std::numeric_limits::epsilon() ) // < 0 ? + { + MY_ASSERT( p <= 0 ); + T f = 2 * sqrt( -p ); + T phi = acos( -q / sqrt( - cube( p ) ) ); + x0 = f * cos( phi / 3 ) - bOverThreeA; + x1 = - f * cos( ( phi + PI ) / 3 ) - bOverThreeA; + x2 = - f * cos( ( phi - PI ) / 3 ) - bOverThreeA; + return( 3 ); + } + else if ( std::numeric_limits::epsilon() < discriminant ) // > 0 ? + { + T t = sqrt( discriminant ); + T u = sign( - q + t ) * pow( abs( - q + t ), 1 / T(3) ); + T v = sign( - q - t ) * pow( abs( - q - t ), 1 / T(3) ); + x0 = u + v - bOverThreeA; + return( 1 ); // plus two complex solutions + } + else + { + T t = sign( - q ) * pow( abs( - q ), 1 / T(3) ); + x0 = 2 * t - bOverThreeA; + x1 = -t - bOverThreeA; + x2 = -t - bOverThreeA; + return( 3 ); + } + } + else + { + return( solveQuadraticEquation( b, c, d, x0, x1 ) ); + } + } + + //! Template to square an object of Type T. + /** \param t value to square + * \returns product of \a t with itself */ + template + inline T square( const T& t ) + { + return( t * t ); + } + + /*! \brief Calculate the horizontal field of view out of the vertical field of view */ + inline float verticalToHorizontalFieldOfView( float vFoV, float aspectRatio ) + { + return( 2.0f * atanf( tanf( 0.5f * vFoV ) * aspectRatio ) ); + } + + //! Compares two numerical values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template + inline int compare(T lhs, T rhs) + { + return (lhs == rhs) ? 0 : (lhs < rhs) ? -1 : 1; + } + + // specializations for float and double below + + //! Compares two float values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template<> + inline int compare(float lhs, float rhs) + { + return ((fabs(lhs-rhs) < FLT_EPSILON) ? 0 : (lhs < rhs) ? -1 : 1); + } + + //! Compares two double values + /** \returns -1 if \a lhs < \a rhs, 1 if \a lhs > \a rhs, and 0 if \a lhs == \a rhs. */ + template<> + inline int compare(double lhs, double rhs) + { + return ((fabs(lhs-rhs) < DBL_EPSILON) ? 0 : (lhs < rhs) ? -1 : 1); + } + + DP_MATH_API float _atof( const std::string &str ); + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/src/Math.cpp b/apps/bench_shared_offscreen/dp/math/src/Math.cpp new file mode 100644 index 00000000..9c216ee5 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/src/Math.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +#include + + +namespace dp +{ + namespace math + { + + DP_MATH_API float _atof( const std::string &str ) + { + int pre = 0; + int post = 0; + float divisor = 1.0f; + bool negative = false; + size_t i = 0; + while ( ( i < str.length() ) && ( ( str[i] == ' ' ) || ( str[i] == '\t' ) ) ) + { + i++; + } + if ( ( i < str.length() ) && ( ( str[i] == '+' ) || ( str[i] == '-' ) ) ) + { + negative = ( str[i] == '-' ); + i++; + } + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + pre = pre * 10 + ( str[i] - 0x30 ); + i++; + } + if ( ( i < str.length() ) && ( str[i] == '.' ) ) + { + i++; + while ( ( i < str.length() ) && isdigit( str[i] ) && ( post <= INT_MAX/10-9 ) ) + { + post = post * 10 + ( str[i] - 0x30 ); + divisor *= 10; + i++; + } + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + i++; + } + } + if ( negative ) + { + pre = - pre; + post = - post; + } + float f = post ? pre + post / divisor : (float)pre; + if ( ( i < str.length() ) && ( ( str[i] == 'd' ) || ( str[i] == 'D' ) || ( str[i] == 'e' ) || ( str[i] == 'E' ) ) ) + { + i++; + negative = false; + if ( ( i < str.length() ) && ( ( str[i] == '+' ) || ( str[i] == '-' ) ) ) + { + negative = ( str[i] == '-' ); + i++; + } + int exponent = 0; + while ( ( i < str.length() ) && isdigit( str[i] ) ) + { + exponent = exponent * 10 + ( str[i] - 0x30 ); + i++; + } + if ( negative ) + { + exponent = - exponent; + } + f *= pow( 10.0f, float(exponent) ); + } + #if !defined( NDEBUG ) + float z = (float)atof( str.c_str() ); + MY_ASSERT( fabsf( f - z ) < FLT_EPSILON * std::max( 1.0f, fabsf( z ) ) ); + #endif + return( f ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/src/Matmnt.cpp b/apps/bench_shared_offscreen/dp/math/src/Matmnt.cpp new file mode 100644 index 00000000..920c6ba0 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/src/Matmnt.cpp @@ -0,0 +1,411 @@ +// Copyright (c) 2012-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +using std::abs; + +namespace dp +{ + namespace math + { + const Mat44f cIdentity44f( { 1.0f, 0.0f, 0.0f, 0.0f + , 0.0f, 1.0f, 0.0f, 0.0f + , 0.0f, 0.0f, 1.0f, 0.0f + , 0.0f, 0.0f, 0.0f, 1.0f } ); + + template + static T _colNorm( const Matmnt &mat ) + { + T max(0); + for ( unsigned int i=0 ; i + static T _rowNorm( const Matmnt &mat ) + { + T max(0); + for ( unsigned int i=0 ; i + static int _maxColumn( const Matmnt &mat ) + { + T max(0); + int col(-1); + for ( unsigned int i=0 ; i + static void _makeReflector( Vecnt<3,T> &v ) + { + T s = sqrt( v * v ); + v[2] += ( v[2] < 0 ) ? -s : s; + s = sqrt( T(2) / ( v * v ) ); + v *= s; + } + + // Apply Householder reflection represented by u to column vectors of M + template + static void _reflectCols( Matmnt &M, const Vecnt &u ) + { + Vecnt v = u * M; + for ( unsigned int i=0 ; i + static void _reflectRows( Matmnt &M, const Vecnt &u ) + { + Vecnt v = M * u; + for ( unsigned int i=0 ; i write to mk + template + static void _decomposeRank1( Matmnt<3,3,T> & mk ) + { + // if rank(mk) is 1 we should find a non-zero column in M + int col = _maxColumn( mk ); + if ( col < 0 ) + { + // rank 0 + setIdentity( mk ); + } + else + { + Vecnt<3,T> v1( mk[0][col], mk[1][col], mk[2][col] ); + _makeReflector( v1 ); + _reflectCols( mk, v1 ); + Vecnt<3,T> v2( mk[2][0], mk[2][1], mk[2][2] ); + _makeReflector( v2 ); + _reflectRows( mk, v2 ); + T s = mk[2][2]; + setIdentity( mk ); + if ( s < 0 ) + { + mk[2][2] = T(-1); + } + _reflectCols( mk, v1 ); + _reflectRows( mk, v2 ); + } + } + + // Find orthogonal factor Q or rank 2 (or less) of mk using adjoint transpose -> write to mk + template + static void _decomposeRank2( Matmnt<3,3,T> & mk, const Matmnt<3,3,T> mAdjTk ) + { + // if rank(mk) is 2, we should find a non-zero column in mAdjTk + int col = _maxColumn( mAdjTk ); + if ( col < 0 ) + { + // rank 1 + _decomposeRank1( mk ); + } + else + { + Vecnt<3,T> v1( mAdjTk[0][col], mAdjTk[1][col], mAdjTk[2][col] ); + _makeReflector( v1 ); + _reflectCols( mk, v1 ); + Vecnt<3,T> v2 = mk[0] ^ mk[1]; + _makeReflector( v2 ); + _reflectRows( mk, v2 ); + if ( mk[0][1] * mk[1][0] < mk[0][0] * mk[1][1] ) + { + T c = mk[1][1] + mk[0][0]; + T s = mk[1][0] - mk[0][1]; + T d = sqrt( c * c + s * s ); + c /= d; + s /= d; + mk[0][0] = c; + mk[0][1] = -s; + mk[1][0] = s; + mk[1][1] = c; + } + else + { + T c = mk[1][1] - mk[0][0]; + T s = mk[1][0] + mk[0][1]; + T d = sqrt( c * c + s * s ); + c /= d; + s /= d; + mk[0][0] = -c; + mk[0][1] = s; + mk[1][0] = s; + mk[1][1] = c; + } + mk[0][2] = T(0); + mk[1][2] = T(0); + mk[2][0] = T(0); + mk[2][1] = T(0); + mk[2][2] = T(1); + _reflectCols( mk, v1 ); + _reflectRows( mk, v2 ); + } + } + + template + static T _polarDecomposition( const Matmnt<3,3,T> &mat, Matmnt<3,3,T> &rot, Matmnt<3,3,T> &sca ) + { + Matmnt<3,3,T> mk = ~mat; + T det; + T eCol; + T mCol = _colNorm( mk ); + T mRow = _rowNorm( mk ); + do + { + Matmnt<3, 3, T> mAdjTk{ mk[1] ^ mk[2], mk[2] ^ mk[0], mk[0] ^ mk[1] }; + det = mk[0] * mAdjTk[0]; + T absDet = abs( det ); + if ( std::numeric_limits::epsilon() < absDet ) + { + T mAdjTCol = _colNorm( mAdjTk ); + T mAdjTRow = _rowNorm( mAdjTk ); + T gamma = sqrt( sqrt( ( mAdjTCol * mAdjTRow ) / ( mCol * mRow ) ) / absDet ); + Matmnt<3,3,T> ek = mk; + mk = T(0.5) * ( gamma * mk + mAdjTk / ( gamma * det ) ); + ek -= mk; + eCol = _colNorm( ek ); + mCol = _colNorm( mk ); + mRow = _rowNorm( mk ); + } + else + { + // rank 2 + _decomposeRank2( mk, mAdjTk ); + break; + } + } while( ( 2 * mCol * std::numeric_limits::epsilon() ) < eCol ); + if ( det < T(0) ) + { + mk = - mk; + } + rot = ~mk; + + sca = mat * mk; + for ( unsigned int i=0 ; i<3 ; ++i ) + { + for ( unsigned int j=i+1 ; j<3 ; ++j ) + { + sca[i][j] = sca[j][i] = T(0.5) * ( sca[i][j] + sca[j][i] ); + } + } + return( det ); + } + + template + static Vecnt<3,T> _spectralDecomposition( const Matmnt<3,3,T> &mat, Matmnt<3,3,T> &u ) + { + setIdentity( u ); + Vecnt<3,T> diag( mat[0][0], mat[1][1], mat[2][2] ); + Vecnt<3,T> offd( mat[2][1], mat[0][2], mat[1][0] ); + bool done = false; + for ( int sweep=20 ; 0::epsilon() ); + if ( !done ) + { + done = true; + for ( int i=2 ; 0<=i ; i-- ) + { + int p = (i+1)%3; + int q = (p+1)%3; + T absOffDi = abs(offd[i]); + if ( std::numeric_limits::epsilon() < absOffDi ) + { + done = false; + T g = 100 * absOffDi; + T h = diag[q] - diag[p]; + T absh = abs( h ); + T t; + if ( absh + g == absh ) + { + t = offd[i] / h; + } + else + { + T theta = T(0.5) * h / offd[i]; + t = 1 / ( abs(theta) + sqrt(theta*theta+1) ); + if ( theta < 0 ) + { + t = -t; + } + } + T c = 1 / sqrt( t*t+1); + T s = t * c; + T tau = s / (c + 1); + T ta = t * offd[i]; + offd[i] = 0; + diag[p] -= ta; + diag[q] += ta; + T offdq = offd[q]; + offd[q] -= s * ( offd[p] + tau * offd[q] ); + offd[p] += s * ( offdq - tau * offd[p] ); + for ( int j=2 ; 0<=j ; j-- ) + { + T a = u[p][j]; + T b = u[q][j]; + u[p][j] -= s * ( b + tau * a ); + u[q][j] += s * ( a - tau * b ); + } + } + } + } + } + return( diag ); + } + + void decompose( const Matmnt<3,3,float> &mat, Quatt &orientation, Vecnt<3,float> &scaling + , Quatt &scaleOrientation ) + { +#if 0 + + Matmnt<3,3,float> rot, sca; + float det = _polarDecomposition( mat, rot, sca ); +#if !defined(NDEBUG) + { + Matmnt<3,3,float> diff = mat - (float)sign(det) * sca * rot; + float eps = std::max( 1.0f, maxElement( sca ) ) * std::numeric_limits::epsilon(); + } +#endif + + orientation = Quatt(rot); + + // get the scaling out of sca + Matmnt<3,3,float> so; + scaling = (float)sign(det) * _spectralDecomposition( sca, so ); +#if !defined( NDEBUG ) + { + Matmnt<3,3,float> k( scaling[0], 0, 0, 0, scaling[1], 0, 0, 0, scaling[2] ); + Matmnt<3,3,float> diff = sca - ~so * k * so; + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + } +#endif + + scaleOrientation = Quatt( so ); + +#if !defined( NDEBUG ) + { + Matmnt<3,3,float> ms( scaling[0], 0, 0, 0, scaling[1], 0, 0, 0, scaling[2] ); + Matmnt<3,3,float> mso( so ); + Matmnt<3,3,float> diff = mat - ~mso * ms * mso * rot; + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + } +#endif + +#else + + Matmnt<3,3,double> rot, sca; + double det = _polarDecomposition( Matmnt<3,3,double>(mat), rot, sca ); +#if !defined(NDEBUG) + { + Matmnt<3,3,double> diff = Matmnt<3,3,double>(mat) - sca * rot; + double eps = std::max( 1.0, maxElement( sca ) ) * std::numeric_limits::epsilon(); + } +#endif + + orientation = Quatt(Quatt(rot)); + + // get the scaling out of sca + Matmnt<3,3,double> so; + scaling = _spectralDecomposition( sca, so ); +#if !defined( NDEBUG ) + { + Matmnt<3, 3, double> k( { (double)scaling[0], 0.0, 0.0, 0.0, (double)scaling[1], 0.0, 0.0, 0.0, (double)scaling[2] } ); + Matmnt<3,3,double> diff = sca - ~so * k * so; + double eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); + int a = 0; + } +#endif + + scaleOrientation = Quatt(Quatt( so )); + +#if !defined( NDEBUG ) + Matmnt<3, 3, double> ms( { (double)scaling[0], 0.0, 0.0, 0.0, (double)scaling[1], 0.0, 0.0, 0.0, (double)scaling[2] } ); + Matmnt<3,3,double> mso( so ); + Matmnt<3,3,float> diff = mat - Matmnt<3,3,float>(~mso * ms * mso * rot); + float eps = std::max( 1.0f, maxElement( scaling ) ) * std::numeric_limits::epsilon(); +#endif + +#endif + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/src/Quatt.cpp b/apps/bench_shared_offscreen/dp/math/src/Quatt.cpp new file mode 100644 index 00000000..6b79158a --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/src/Quatt.cpp @@ -0,0 +1,83 @@ +// Copyright (c) 2012, NVIDIA Corporation. 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 NVIDIA CORPORATION 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 ``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. + + +#include + +namespace dp +{ + namespace math + { + template + Quatt _lerp( T alpha, const Quatt & q0, const Quatt & q1 ) + { + // cosine theta = dot product of q0 and q1 + T cosTheta = q0[0] * q1[0] + q0[1] * q1[1] + q0[2] * q1[2] + q0[3] * q1[3]; + + // if q1 is on opposite hemisphere from q0, use -q1 instead + bool flip = ( cosTheta < 0 ); + if ( flip ) + { + cosTheta = - cosTheta; + } + + T beta; + if ( 1 - cosTheta < std::numeric_limits::epsilon() ) + { + // if q1 is (within precision limits) the same as q0, just linear interpolate between q0 and q1. + beta = 1 - alpha; + } + else + { + // normal case + T theta = acos( cosTheta ); + T oneOverSinTheta = 1 / sin( theta ); + beta = sin( theta * ( 1 - alpha ) ) * oneOverSinTheta; + alpha = sin( theta * alpha ) * oneOverSinTheta; + } + + if ( flip ) + { + alpha = - alpha; + } + + return( Quatt( beta * q0[0] + alpha * q1[0] + , beta * q0[1] + alpha * q1[1] + , beta * q0[2] + alpha * q1[2] + , beta * q0[3] + alpha * q1[3] ) ); + } + + Quatt lerp( float alpha, const Quatt & q0, const Quatt & q1 ) + { + return( _lerp( alpha, q0, q1 ) ); + } + + void lerp( float alpha, const Quatt & q0, const Quatt & q1, Quatt &qr ) + { + qr = _lerp( alpha, q0, q1 ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/dp/math/src/Trafo.cpp b/apps/bench_shared_offscreen/dp/math/src/Trafo.cpp new file mode 100644 index 00000000..844f1200 --- /dev/null +++ b/apps/bench_shared_offscreen/dp/math/src/Trafo.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2002-2015, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + + +#include + + +namespace dp +{ + namespace math + { + + Trafo::Trafo( void ) + : m_center( Vec3f( 0.0f, 0.0f, 0.0f ) ) + , m_orientation( Quatf( 0.0f, 0.0f, 0.0f, 1.0f ) ) + , m_scaleOrientation( Quatf( 0.0f, 0.0f, 0.0f, 1.0f ) ) + , m_scaling( Vec3f( 1.0f, 1.0f, 1.0f ) ) + , m_translation( Vec3f( 0.0f, 0.0f, 0.0f ) ) + , m_matrixValid( false ) + , m_decompositionValid( true ) + { + }; + + Trafo::Trafo( const Trafo &rhs ) + : m_matrixValid( rhs.m_matrixValid ) + , m_decompositionValid( rhs.m_decompositionValid ) + { + if ( m_matrixValid ) + { + m_matrix = rhs.m_matrix; + } + if ( m_decompositionValid ) + { + m_center = rhs.m_center; + m_orientation = rhs.m_orientation; + m_scaleOrientation = rhs.m_scaleOrientation; + m_scaling = rhs.m_scaling; + m_translation = rhs.m_translation; + } + } + + Trafo & Trafo::operator=( const Trafo & rhs ) + { + MY_ASSERT( rhs.m_matrixValid || rhs.m_decompositionValid ); + if (&rhs != this) + { + if ( rhs.m_decompositionValid ) + { + m_center = rhs.m_center; + m_orientation = rhs.m_orientation; + m_scaleOrientation = rhs.m_scaleOrientation; + m_scaling = rhs.m_scaling; + m_translation = rhs.m_translation; + } + if ( rhs.m_matrixValid ) + { + m_matrix = rhs.m_matrix; + } + + m_matrixValid = rhs.m_matrixValid; + m_decompositionValid = rhs.m_decompositionValid; + } + return *this; + } + + const Mat44f& Trafo::getMatrix( void ) const + { + MY_ASSERT( m_matrixValid || m_decompositionValid ); + + if ( !m_matrixValid ) + { + // Calculates -C * SO^-1 * S * SO * R * C * T, with + // C being the center translation + // SO being the scale orientation + // S being the scale + // R being the rotation + // T being the translation + Mat33f soInv( -m_scaleOrientation ); + Mat44f m0( Vec4f( soInv[0], 0.0f ) + , Vec4f( soInv[1], 0.0f ) + , Vec4f( soInv[2], 0.0f ) + , Vec4f( -m_center*soInv, 1.0f ) ); + Mat33f rot( m_scaleOrientation * m_orientation ); + Mat44f m1( Vec4f( m_scaling[0] * rot[0], 0.0f ) + , Vec4f( m_scaling[1] * rot[1], 0.0f ) + , Vec4f( m_scaling[2] * rot[2], 0.0f ) + , Vec4f( m_center + m_translation, 1.0f ) ); + m_matrix = m0 * m1; + m_matrixValid = true; + } + return( m_matrix ); + } + + Mat44f Trafo::getInverse( void ) const + { + dp::math::Mat44f inverse = getMatrix(); // Automatically makes the matrix valid. + if ( !inverse.invert() ) + { + // Inverting the matrix directly failed. + // Try the more robust but also more expensive decomposition. + // (Note that this still barely handles a uniform scaling of 2.2723e-6, + // while the direct matrix inversion already dropped below the + // std::numeric_limits::epsilon() of 2.2204460492503131e-016 for the determinant.) + if ( !m_decompositionValid ) + { + decompose(); + } + // Calculates T^-1 * C^-1 * R^-1 * SO^-1 * S^-1 * SO * -C^-1, with + // C being the center translation + // SO being the scale orientation + // S being the scale + // R being the rotation + // T being the translation + Mat33f rot( -m_orientation * -m_scaleOrientation ); + Mat44f m0; + m0[0] = Vec4f( rot[0], 0.0f ); + m0[1] = Vec4f( rot[1], 0.0f ); + m0[2] = Vec4f( rot[2], 0.0f ); + m0[3] = Vec4f( ( -m_center - m_translation ) * rot, 1.0f ); + + Mat33f so( m_scaleOrientation ); + + Mat44f m1; + m1[0] = Vec4f( so[0], 0.0f ) / m_scaling[0]; + m1[1] = Vec4f( so[1], 0.0f ) / m_scaling[1]; + m1[2] = Vec4f( so[2], 0.0f ) / m_scaling[2]; + m1[3] = Vec4f( m_center, 1.0f ); + + inverse = m0 * m1; + } + return inverse; + } + + void Trafo::setIdentity( void ) + { + m_center = Vec3f( 0.0f, 0.0f, 0.0f ); + m_orientation = Quatf( 0.0f, 0.0f, 0.0f, 1.0f ); + m_scaling = Vec3f( 1.0f, 1.0f, 1.0f ); + m_scaleOrientation = Quatf( 0.0f, 0.0f, 0.0f, 1.0f ); + m_translation = Vec3f( 0.0f, 0.0f, 0.0f ); + m_matrixValid = false; + m_decompositionValid = true; + } + + void Trafo::setMatrix( const Mat44f &matrix ) + { + m_matrix = matrix; + m_matrixValid = true; + m_decompositionValid = false; + } + + void Trafo::decompose() const + { + MY_ASSERT( m_matrixValid ); + MY_ASSERT( m_decompositionValid == false ); + + dp::math::decompose( m_matrix, m_translation, m_orientation, m_scaling, m_scaleOrientation); + + m_decompositionValid = true; + } + + + bool Trafo::operator==( const Trafo &t ) const + { + if ( m_decompositionValid && t.m_decompositionValid ) + { + return( + ( getCenter() == t.getCenter() ) + && ( getOrientation() == t.getOrientation() ) + && ( getScaling() == t.getScaling() ) + && ( getScaleOrientation() == t.getScaleOrientation() ) + && ( getTranslation() == t.getTranslation() ) ); + } + else + { + return getMatrix() == t.getMatrix(); + } + + } + + Trafo lerp( float alpha, const Trafo &t0, const Trafo &t1 ) + { + Trafo t; + t.setCenter( lerp( alpha, t0.getCenter(), t1.getCenter() ) ); + t.setOrientation( lerp( alpha, t0.getOrientation(), t1.getOrientation() ) ); + t.setScaling( lerp( alpha, t0.getScaling(), t1.getScaling() ) ); + t.setScaleOrientation( lerp( alpha, t0.getScaleOrientation(), t1.getScaleOrientation() ) ); + t.setTranslation( lerp( alpha, t0.getTranslation(), t1.getTranslation() ) ); + return( t ); + } + + } // namespace math +} // namespace dp diff --git a/apps/bench_shared_offscreen/imgui/LICENSE b/apps/bench_shared_offscreen/imgui/LICENSE new file mode 100644 index 00000000..b28ef225 --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 Omar Cornut and ImGui contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/bench_shared_offscreen/imgui/imconfig.h b/apps/bench_shared_offscreen/imgui/imconfig.h new file mode 100644 index 00000000..8abf26f3 --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imconfig.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// Most options (memory allocation, clipboard callbacks, etc.) can be set at runtime via the ImGuiIO structure - ImGui::GetIO(). +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h) +// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" +// Note that options such as IMGUI_API, IM_VEC2_CLASS_EXTRA or ImDrawIdx needs to be defined consistently everywhere you include imgui.h, not only for the imgui*.cpp compilation units. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. +// DAR HACK Compiling directly into the application, not using DLLs. Not doing this will lead to "incosistent DLL definitions". +//#ifndef IMGUI_API +//# if imgui_EXPORTS /* Set by CMake */ +//# if defined( _WIN32 ) || defined( _WIN64 ) +//# define IMGUI_API __declspec( dllexport ) +//# endif +//# else /* imgui_EXPORTS */ +//# if defined( _WIN32 ) || defined( _WIN64 ) +//# define IMGUI_API __declspec( dllimport ) +//# endif +//# endif +//#endif + +//---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce likelihood of using already obsolete function/names +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Don't implement default handlers for Windows (so as not to link with certain functions) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // Don't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // Don't use and link with ImmGetContext/ImmSetCompositionWindow. + +//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) +//---- It is very strongly recommended to NOT disable the demo windows. Please read the comment at the top of imgui_demo.cpp. +//#define IMGUI_DISABLE_DEMO_WINDOWS + +//---- Don't implement ImFormatString(), ImFormatStringV() so you can reimplement them yourself. +//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +//---- Include imgui_user.h at the end of imgui.h as a convenience +//#define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Pack colors to BGRA8 instead of RGBA8 (if you needed to convert from one to another anyway) +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Implement STB libraries in a namespace to avoid linkage conflicts (defaults to global namespace) +//#define IMGUI_STB_NAMESPACE ImGuiStb + +//---- Define constructor and implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ + +//---- Use 32-bit vertex indices (instead of default 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it. +//#define ImDrawIdx unsigned int + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +/* +namespace ImGui +{ + void MyFunction(const char* name, const MyMatrix44& v); +} +*/ diff --git a/apps/bench_shared_offscreen/imgui/imgui.cpp b/apps/bench_shared_offscreen/imgui/imgui.cpp new file mode 100644 index 00000000..d6d9f40f --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui.cpp @@ -0,0 +1,13277 @@ +// dear imgui, v1.60 WIP +// (main code and documentation) + +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui +// Releases change-log at https://github.com/ocornut/imgui/releases +// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// This library is free but I need your support to sustain development and maintenance. +// If you work for a company, please consider financial support, see Readme. For individuals: https://www.patreon.com/imgui + +/* + + Index + - MISSION STATEMENT + - END-USER GUIDE + - PROGRAMMER GUIDE (read me!) + - Read first + - How to update to a newer version of Dear ImGui + - Getting started with integrating Dear ImGui in your code/engine + - Using gamepad/keyboard navigation [BETA] + - API BREAKING CHANGES (read me when you update!) + - ISSUES & TODO LIST + - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + - How can I help? + - How can I display an image? What is ImTextureID, how does it works? + - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels and the ID stack. + - How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + - How can I load a different font than the default? + - How can I easily use icons in my application? + - How can I load multiple fonts? + - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? + - How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + - I integrated Dear ImGui in my engine and the text or lines are blurry.. + - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + - ISSUES & TODO-LIST + - CODE + + + MISSION STATEMENT + ================= + + - Easy to use to create code-driven and data-driven tools + - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools + - Easy to hack and improve + - Minimize screen real-estate usage + - Minimize setup and maintenance + - Minimize state storage on user side + - Portable, minimize dependencies, run on target (consoles, phones, etc.) + - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, opening a tree node + for the first time, etc. but a typical frame won't allocate anything) + + Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: + - Doesn't look fancy, doesn't animate + - Limited layout features, intricate layouts are typically crafted in code + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) + - Controls are automatically adjusted for OSX to match standard OSX text editing operations. + - Gamepad navigation: see suggested mappings in imgui.h ImGuiNavInput_ + + + PROGRAMMER GUIDE + ================ + + READ FIRST + + - Read the FAQ below this section! + - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention + on your side, no state duplication, less sync, less bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + + - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed from the public API. + If you have a problem with a missing function/symbols, search for its name in the code, there will likely be a comment about it. + Please report any issue to the GitHub page! + - Try to keep your copy of dear imgui reasonably up to date. + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + + - Add the Dear ImGui source files to your projects, using your preferred build system. + It is recommended you build the .cpp files as part of your project and not as a library. + - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types. + - See examples/ folder for standalone sample applications. + - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + + - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize + (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic + integration you don't need to worry about it all. + - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory. + - Every frame: + - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.) + - Call ImGui::NewFrame() to begin the frame + - You can use any ImGui function you want between NewFrame() and Render() + - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler. + (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break) + - All rendering information are stored into command-lists until ImGui::Render() is called. + - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. + - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases + of your own application. + - Refer to the examples applications in the examples/ folder for instruction on how to setup your code. + - A minimal application skeleton may be: + + // Application init + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = 1920.0f; + io.DisplaySize.y = 1280.0f; + // TODO: Fill others settings of the io structure later. + + // Load texture atlas (there is a default font so you don't need to care about choosing a font yet) + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height); + // TODO: At this points you've got the texture data and you need to upload that your your graphic system: + MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA) + // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer. + io.Fonts->TexID = (void*)texture; + + // Application main loop + while (true) + { + // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.) + ImGuiIO& io = ImGui::GetIO(); + io.DeltaTime = 1.0f/60.0f; + io.MousePos = mouse_pos; + io.MouseDown[0] = mouse_button_0; + io.MouseDown[1] = mouse_button_1; + + // Call NewFrame(), after this point you can use ImGui::* functions anytime + ImGui::NewFrame(); + + // Most of your application code here + MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); + MyGameRender(); // may use any ImGui functions as well! + + // Render & swap video buffers + ImGui::Render(); + MyImGuiRenderFunction(ImGui::GetDrawData()); + SwapBuffers(); + } + + // Shutdown + ImGui::DestroyContext(); + + + - A minimal render function skeleton may be: + + void void MyRenderFunction(ImDrawData* draw_data) + { + // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + // TODO: Setup viewport, orthographic projection matrix + // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // The texture for the draw call is specified by pcmd->TextureId. + // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization. + MyEngineBindTexture(pcmd->TextureId); + + // We are using scissoring to clip some objects. All low-level graphics API supports it. + // If your engine doesn't support scissoring yet, you will get some small glitches (some elements outside their bounds) which you can fix later. + MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices. + MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + } + + - The examples/ folders contains many functional implementation of the pseudo-code above. + - When calling NewFrame(), the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'io.WantTextInput' flags are updated. + They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide + mouse inputs from the rest of your application. Read the FAQ below for more information about those flags. + + USING GAMEPAD/KEYBOARD NAVIGATION [BETA] + + - Ask questions and report issues at https://github.com/ocornut/imgui/issues/787 + - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable. + - Keyboard: + - Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays. + - When keyboard navigation is active (io.NavActive + NavFlags_EnableKeyboard), the io.WantCaptureKeyboard flag will be set. + For more advanced uses, you may want to read from: + - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. + - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used). + - or query focus information with e.g. IsWindowFocused(), IsItemFocused() etc. functions. + Please reach out if you think the game vs navigation input sharing could be improved. + - Gamepad: + - Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). + - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: + 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. + - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. + Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, maybe a power curve, etc.). + - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. + Please reach out if you think the game vs navigation input sharing could be improved. + - Mouse: + - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback. + - Consoles/Tablet/Phone users: Consider using Synergy host (on your computer) + uSynergy.c (in your console/tablet/phone app) to use your PC mouse/keyboard. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiNavFlags_MoveMouse flag in io.NavFlags. + Enabling ImGuiNavFlags_MoveMouse instructs dear imgui to move your mouse cursor along with navigation movements. + When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantMoveMouse' to notify you that it wants the mouse cursor to be moved. + When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that. + (If you set the ImGuiNavFlags_MoveMouse flag but don't honor 'io.WantMoveMouse' properly, imgui will misbehave as it will see your mouse as moving back and forth.) + (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want + to set a boolean to ignore your other external mouse positions until the external source is moved again.) + + + API BREAKING CHANGES + ==================== + + Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. + Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. + Also read releases logs https://github.com/ocornut/imgui/releases for more details. + + - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display. + - 2018/02/07 (1.60) - reorganized context handling to be more explicit, + - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. + - removed Shutdown() function, as DestroyContext() serve this purpose. + - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwhise CreateContext() will create its own font atlas instance. + - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. + - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. + - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. + - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. + - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side. + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. + Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). + - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). + - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame. + - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. + - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. + However if your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color. + ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) + { + float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; + return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); + } + If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDraw::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. + you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. + this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + became: + ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + argument 'cmd_lists' -> 'draw_data->CmdLists' + argument 'cmd_lists_count' -> 'draw_data->CmdListsCount' + ImDrawList 'commands' -> 'CmdBuffer' + ImDrawList 'vtx_buffer' -> 'VtxBuffer' + ImDrawList n/a -> 'IdxBuffer' (new) + ImDrawCmd 'vtx_count' -> 'ElemCount' + ImDrawCmd 'clip_rect' -> 'ClipRect' + ImDrawCmd 'user_callback' -> 'UserCallback' + ImDrawCmd 'texture_id' -> 'TextureId' + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + this sequence: + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + // + became: + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + // + io.Fonts->TexID = (your_texture_identifier); + you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + it is now recommended that you sample the font texture with bilinear interpolation. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. + (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + + ISSUES & TODO-LIST + ================== + See TODO.txt + + + FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + ====================================== + + Q: How can I help? + A: - If you are experienced with Dear ImGui and C++, look at the github issues, or TODO.txt and see how you want/can help! + - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README. + - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. + You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers. + But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. + - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately). + + Q: How can I display an image? What is ImTextureID, how does it works? + A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function. + Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry! + It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc. + At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render. + Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing. + (c++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!) + To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions. + Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. + You may call ImGui::ShowMetricsWindow() to explore active draw lists and visualize/understand how the draw data is generated. + It is your responsibility to get textures uploaded to your GPU. + + Q: Can I have multiple widgets with the same label? Can I have widget without a label? + A: Yes. A primer on labels and the ID stack... + + - Elements that are typically not clickable, such as Text() items don't need an ID. + + - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui often needs to remember what is + the "active" widget). to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. + + Button("OK"); // Label = "OK", ID = hash of "OK" + Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" + + - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" + in two different windows or in two different locations of a tree. + + - If you have a same ID twice in the same location, you'll have a conflict: + + Button("OK"); + Button("OK"); // ID collision! Both buttons will be treated as the same. + + Fear not! this is easy to solve and there are many ways to solve it! + + - When passing a label you can optionally specify extra unique ID information within string itself. + Use "##" to pass a complement to the ID that won't be visible to the end-user. + This helps solving the simple collision cases when you know which items are going to be created. + + Button("Play"); // Label = "Play", ID = hash of "Play" + Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above) + Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above) + + - If you want to completely hide the label, but still need an ID: + + Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!) + + - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels. + For example you may want to include varying information in a window title bar, but windows are uniquely identified by their ID.. + Use "###" to pass a label that isn't part of ID: + + Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" + Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) + + sprintf(buf, "My game (%f FPS)###MyGame", fps); + Begin(buf); // Variable label, ID = hash of "MyGame" + + - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window. + This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements. + You can push a pointer, a string or an integer value. Remember that ID are formed from the concatenation of _everything_ in the ID stack! + + for (int i = 0; i < 100; i++) + { + PushID(i); + Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj); + Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj->Name); + Button("Click"); // Label = "Click", ID = hash of string + "label" (unique) + PopID(); + } + + - More example showing that you can stack multiple prefixes into the ID stack: + + Button("Click"); // Label = "Click", ID = hash of "Click" + PushID("node"); + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + PushID(my_ptr); + Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click" + PopID(); + PopID(); + + - Tree nodes implicitly creates a scope for you by calling PushID(). + + Button("Click"); // Label = "Click", ID = hash of "Click" + if (TreeNode("node")) + { + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + TreePop(); + } + + - When working with trees, ID are used to preserve the open/close state of each tree node. + Depending on your use cases you may want to use strings, indices or pointers as ID. + e.g. when displaying a single object that may change over time (dynamic 1-1 relationship), using a static string as ID will preserve your + node open/closed state when the targeted object change. + e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. + experiment and see what makes more sense! + + Q: How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure. + - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application. + - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is + also generally ok, as the bool toggles fairly rarely and you don't generally expect to interact with either Dear ImGui or your application during + the same frame when that transition occurs. Dear ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, + so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered. + (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically + have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs + were for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) + + Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13) + A: Use the font atlas to load the TTF/OTF file you want: + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + + New programmers: remember that in C/C++ and most programming languages if you want to use a backslash \ in a string literal you need to write a double backslash "\\": + io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG + io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT + io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT + + Q: How can I easily use icons in my application? + A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your + strings. Read 'How can I load multiple fonts?' and the file 'misc/fonts/README.txt' for instructions and useful header files. + + Q: How can I load multiple fonts? + A: Use the font atlas to pack them into a single texture: + (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.) + + ImGuiIO& io = ImGui::GetIO(); + ImFont* font0 = io.Fonts->AddFontDefault(); + ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + // the first loaded font gets used by default + // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime + + // Options + ImFontConfig config; + config.OversampleH = 3; + config.OversampleV = 1; + config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up + config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); + + // Combine multiple fonts into one (e.g. for icon fonts) + ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontDefault(); + io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs + + Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? + A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. + + // Add default Japanese ranges + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); + + // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need) + ImVector ranges; + ImFontAtlas::GlyphRangesBuilder builder; + builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters) + builder.AddChar(0x7262); // Add a specific character + builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges + builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); + + All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax. + Specifying literal in your source code using a local code page (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work! + Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. + + Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that. + For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. + The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly. + + Q: How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' + so you don't rely on the default globals. + + Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + A: - You can create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, + push a ImGuiCol_WindowBg with zero alpha, then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. + - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows. + - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData. + + Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. + A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). + Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. + + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + A: You are probably mishandling the clipping rectangles in your render function. + Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). + + + - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. + this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". + - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle + of a deep nested inner loop in your code. + - tip: you can call Render() multiple times (e.g for VR renders). + - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui! + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // toupper, isprint +#include // NULL, malloc, free, qsort, atoi +#include // vsnprintf, sscanf, printf +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#define IMGUI_DEBUG_NAV_SCORING 0 +#define IMGUI_DEBUG_NAV_RECTS 0 + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great! +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' // +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#endif + +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif + +//------------------------------------------------------------------------- +// Forward Declarations +//------------------------------------------------------------------------- + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); + +static ImFont* GetDefaultFont(); +static void SetCurrentWindow(ImGuiWindow* window); +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); +static ImGuiWindow* FindHoveredWindow(); +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); +static void CheckStacksSize(ImGuiWindow* window, bool write); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); +static void AddWindowToDrawData(ImVector* out_list, ImGuiWindow* window); +static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); + +static ImGuiWindowSettings* AddWindowSettings(const char* name); + +static void LoadIniSettingsFromDisk(const char* ini_filename); +static void LoadIniSettingsFromMemory(const char* buf); +static void SaveIniSettingsToDisk(const char* ini_filename); +static void SaveIniSettingsToMemory(ImVector& out_buf); +static void MarkIniSettingsDirty(ImGuiWindow* window); + +static ImRect GetViewportRect(); + +static void ClosePopupToLevel(int remaining); +static ImGuiWindow* GetFrontMostModalRootWindow(); + +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size); +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size); +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2); +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format); + +namespace ImGui +{ +static void NavUpdate(); +static void NavUpdateWindowing(); +static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id); + +static void UpdateMovingWindow(); +static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]); +static void FocusFrontMostActiveWindow(ImGuiWindow* ignore_window); +} + +//----------------------------------------------------------------------------- +// Platform dependent default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(void* user_data); +static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +// Current context pointer. Implicitely used by all ImGui functions. Always assumed to be != NULL. +// CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext(). +// If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file. +// ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can: +// - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) +#ifndef GImGui +ImGuiContext* GImGui = NULL; +#endif + +// Memory Allocator functions. Use SetAllocatorFunctions() to change them. +// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file. +// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); } +#else +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); } +#endif + +static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper; +static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper; +static void* GImAllocatorUserData = NULL; +static size_t GImAllocatorActiveAllocationsCount = 0; + +//----------------------------------------------------------------------------- +// User facing structures +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + Alpha = 1.0f; // Global alpha applies to everything in ImGui + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns + ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + DisplaySafeAreaPadding = ImVec2(4,4); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. + AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + ImGui::StyleColorsClassic(this); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); + MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); +} + +ImGuiIO::ImGuiIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + + // Settings + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f/60.0f; + NavFlags = 0x00; + IniSavingRate = 5.0f; + IniFilename = "imgui.ini"; + LogFilename = "imgui_log.txt"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; + KeyRepeatDelay = 0.250f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); + + // Advanced/subtle behaviors +#ifdef __APPLE__ + OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + OptMacOSXBehaviors = false; +#endif + OptCursorBlink = true; + + // Settings (User Functions) + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; + ImeWindowHandle = NULL; + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + RenderDrawListsFn = NULL; +#endif + + // Input (NB: we already have memset zero the entire structure) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; +} + +// Pass in translated ASCII characters for text input. +// - with glfw you can get those from the callback set in glfwSetCharCallback() +// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message +void ImGuiIO::AddInputCharacter(ImWchar c) +{ + const int n = ImStrlenW(InputCharacters); + if (n + 1 < IM_ARRAYSIZE(InputCharacters)) + { + InputCharacters[n] = c; + InputCharacters[n+1] = '\0'; + } +} + +void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +{ + // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more + const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); + ImWchar wchars[wchars_buf_len]; + ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); + for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) + AddInputCharacter(wchars[i]); +} + +//----------------------------------------------------------------------------- +// HELPERS +//----------------------------------------------------------------------------- + +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 + +// Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) +{ + ImVec2 ap = p - a; + ImVec2 ab_dir = b - a; + float ab_len = sqrtf(ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y); + ab_dir *= 1.0f / ab_len; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + if (dot > ab_len) + return b; + return a + ab_dir * dot; +} + +bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) +{ + ImVec2 v0 = b - a; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + out_w = (v0.x * v2.y - v2.x * v0.y) / denom; + out_u = 1.0f - out_v - out_w; +} + +ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + ImVec2 proj_ab = ImLineClosestPoint(a, b, p); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + return proj_bc; + return proj_ca; +} + +int ImStricmp(const char* str1, const char* str2) +{ + int d; + while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + return d; +} + +int ImStrnicmp(const char* str1, const char* str2, size_t count) +{ + int d = 0; + while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + return d; +} + +void ImStrncpy(char* dst, const char* src, size_t count) +{ + if (count < 1) return; + strncpy(dst, src, count); + dst[count-1] = 0; +} + +char* ImStrdup(const char *str) +{ + size_t len = strlen(str) + 1; + void* buf = ImGui::MemAlloc(len); + return (char*)memcpy(buf, (const void*)str, len); +} + +char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + for ( ; str < str_end; str++) + if (*str == c) + return (char*)str; + return NULL; +} + +int ImStrlenW(const ImWchar* str) +{ + int n = 0; + while (*str++) n++; + return n; +} + +const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) +{ + if (!needle_end) + needle_end = needle + strlen(needle); + + const char un0 = (char)toupper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) + { + if (toupper(*haystack) == un0) + { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + return NULL; +} + +static const char* ImAtoi(const char* src, int* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + int v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS +int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + int w = vsnprintf(buf, buf_size, fmt, args); + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) +{ + int w = vsnprintf(buf, buf_size, fmt, args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +// Pass data_size==0 for zero-terminated strings +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImU32 ImHash(const void* data, int data_size, ImU32 seed) +{ + static ImU32 crc32_lut[256] = { 0 }; + if (!crc32_lut[1]) + { + const ImU32 polynomial = 0xEDB88320; + for (ImU32 i = 0; i < 256; i++) + { + ImU32 crc = i; + for (ImU32 j = 0; j < 8; j++) + crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); + crc32_lut[i] = crc; + } + } + + seed = ~seed; + ImU32 crc = seed; + const unsigned char* current = (const unsigned char*)data; + + if (data_size > 0) + { + // Known size + while (data_size--) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; + } + else + { + // Zero-terminated string + while (unsigned char c = *current++) + { + // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. + // Because this syntax is rarely used we are optimizing for the common case. + // - If we reach ### in the string we discard the hash so far and reset to the seed. + // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. + if (c == '#' && current[0] == '#' && current[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + return ~crc; +} + +//----------------------------------------------------------------------------- +// ImText* helpers +//----------------------------------------------------------------------------- + +// Convert UTF-8 to 32-bits character, process single character input. +// Based on stb_from_utf8() from github.com/nothings/stb/ +// We handle UTF-8 decoding error by skipping forward. +int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) +{ + unsigned int c = (unsigned int)-1; + const unsigned char* str = (const unsigned char*)in_text; + if (!(*str & 0x80)) + { + c = (unsigned int)(*str++); + *out_char = c; + return 1; + } + if ((*str & 0xe0) == 0xc0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 2) return 1; + if (*str < 0xc2) return 2; + c = (unsigned int)((*str++ & 0x1f) << 6); + if ((*str & 0xc0) != 0x80) return 2; + c += (*str++ & 0x3f); + *out_char = c; + return 2; + } + if ((*str & 0xf0) == 0xe0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 3) return 1; + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; + if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x0f) << 12); + if ((*str & 0xc0) != 0x80) return 3; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 3; + c += (*str++ & 0x3f); + *out_char = c; + return 3; + } + if ((*str & 0xf8) == 0xf0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 4) return 1; + if (*str > 0xf4) return 4; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; + if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x07) << 18); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 12); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 4; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return 4; + *out_char = c; + return 4; + } + *out_char = 0; + return 0; +} + +int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + ImWchar* buf_out = buf; + ImWchar* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) + char_count++; + } + return char_count; +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c >= 0xdc00 && c < 0xe000) return 0; + if (c >= 0xd800 && c < 0xdc00) return 4; + return 3; +} + +int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +{ + char* buf_out = buf; + const char* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); + } + *buf_out = 0; + return (int)(buf_out - buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_count; +} + +ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) +{ + float s = 1.0f/255.0f; + return ImVec4( + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; + return out; +} + +ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4& col) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = col; + c.w *= style.Alpha; + return ColorConvertFloat4ToU32(c); +} + +const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) +{ + ImGuiStyle& style = GImGui->Style; + return style.Colors[idx]; +} + +ImU32 ImGui::GetColorU32(ImU32 col) +{ + float style_alpha = GImGui->Style.Alpha; + if (style_alpha >= 1.0f) + return col; + int a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (int)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 +// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) +{ + float K = 0.f; + if (g < b) + { + ImSwap(g, b); + K = -1.f; + } + if (r < g) + { + ImSwap(r, g); + K = -2.f / 6.f - K; + } + + const float chroma = r - (g < b ? g : b); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); + out_s = chroma / (r + 1e-20f); + out_v = r; +} + +// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 +// also http://en.wikipedia.org/wiki/HSL_and_HSV +void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) +{ + if (s == 0.0f) + { + // gray + out_r = out_g = out_b = v; + return; + } + + h = fmodf(h, 1.0f) / (60.0f/360.0f); + int i = (int)h; + float f = h - (float)i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (i) + { + case 0: out_r = v; out_g = t; out_b = p; break; + case 1: out_r = q; out_g = v; out_b = p; break; + case 2: out_r = p; out_g = v; out_b = t; break; + case 3: out_r = p; out_g = q; out_b = v; break; + case 4: out_r = t; out_g = p; out_b = v; break; + case 5: default: out_r = v; out_g = p; out_b = q; break; + } +} + +FILE* ImFileOpen(const char* filename, const char* mode) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can) + const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; + const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); + ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); + return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); +#else + return fopen(filename, mode); +#endif +} + +// Load file content into memory +// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() +void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) +{ + IM_ASSERT(filename && file_open_mode); + if (out_file_size) + *out_file_size = 0; + + FILE* f; + if ((f = ImFileOpen(filename, file_open_mode)) == NULL) + return NULL; + + long file_size_signed; + if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) + { + fclose(f); + return NULL; + } + + int file_size = (int)file_size_signed; + void* file_data = ImGui::MemAlloc(file_size + padding_bytes); + if (file_data == NULL) + { + fclose(f); + return NULL; + } + if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) + { + fclose(f); + ImGui::MemFree(file_data); + return NULL; + } + if (padding_bytes > 0) + memset((void *)(((char*)file_data) + file_size), 0, padding_bytes); + + fclose(f); + if (out_file_size) + *out_file_size = file_size; + + return file_data; +} + +//----------------------------------------------------------------------------- +// ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +// std::lower_bound but without the bullshit +static ImVector::iterator LowerBound(ImVector& data, ImGuiID key) +{ + ImVector::iterator first = data.begin(); + ImVector::iterator last = data.end(); + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + ImVector::iterator mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. +void ImGuiStorage::BuildSortByKey() +{ + struct StaticFunc + { + static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1; + if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. +int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_i; +} + +bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_f; +} + +void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) +void ImGuiStorage::SetInt(ImGuiID key, int val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_i = val; +} + +void ImGuiStorage::SetBool(ImGuiID key, bool val) +{ + SetInt(key, val ? 1 : 0); +} + +void ImGuiStorage::SetFloat(ImGuiID key, float val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) +{ + if (default_filter) + { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } + else + { + InputBuf[0] = 0; + CountGrep = 0; + } +} + +bool ImGuiTextFilter::Draw(const char* label, float width) +{ + if (width != 0.0f) + ImGui::PushItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (width != 0.0f) + ImGui::PopItemWidth(); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::TextRange::split(char separator, ImVector& out) +{ + out.resize(0); + const char* wb = b; + const char* we = wb; + while (we < e) + { + if (*we == separator) + { + out.push_back(TextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out.push_back(TextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); + input_range.split(',', Filters); + + CountGrep = 0; + for (int i = 0; i != Filters.Size; i++) + { + Filters[i].trim_blanks(); + if (Filters[i].empty()) + continue; + if (Filters[i].front() != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (int i = 0; i != Filters.Size; i++) + { + const TextRange& f = Filters[i]; + if (f.empty()) + continue; + if (f.front() == '-') + { + // Subtract + if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) + return false; + } + else + { + // Grep + if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) + return true; + } + } + + // Implicit * grep + if (CountGrep == 0) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiTextBuffer +//----------------------------------------------------------------------------- + +// On some platform vsnprintf() takes va_list by reference and modifies it. +// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. +#ifndef va_copy +#define va_copy(dest, src) (dest = src) +#endif + +// Helper: Text buffer for logging/accumulating text +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + if (len <= 0) + return; + + const int write_off = Buf.Size; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int double_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); + } + + Buf.resize(needed_sz); + ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy); +} + +void ImGuiTextBuffer::appendf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + appendfv(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// ImGuiSimpleColumns (internal use only) +//----------------------------------------------------------------------------- + +ImGuiMenuColumns::ImGuiMenuColumns() +{ + Count = 0; + Spacing = Width = NextWidth = 0.0f; + memset(Pos, 0, sizeof(Pos)); + memset(NextWidths, 0, sizeof(NextWidths)); +} + +void ImGuiMenuColumns::Update(int count, float spacing, bool clear) +{ + IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); + Count = count; + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < Count; i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = (float)(int)Width; + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } +} + +float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +{ + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < 3; i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); +} + +float ImGuiMenuColumns::CalcExtraSpace(float avail_w) +{ + return ImMax(0.0f, avail_w - Width); +} + +//----------------------------------------------------------------------------- +// ImGuiListClipper +//----------------------------------------------------------------------------- + +static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) +{ + // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. Consider moving within SetCursorXXX functions? + ImGui::SetCursorPosY(pos_y); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage. + window->DC.PrevLineHeight = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. + if (window->DC.ColumnsSet) + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly +} + +// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 +// Use case B: Begin() called from constructor with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int count, float items_height) +{ + StartPosY = ImGui::GetCursorPosY(); + ItemsHeight = items_height; + ItemsCount = count; + StepNo = 0; + DisplayEnd = DisplayStart = -1; + if (ItemsHeight > 0.0f) + { + ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display + if (DisplayStart > 0) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor + StepNo = 2; + } +} + +void ImGuiListClipper::End() +{ + if (ItemsCount < 0) + return; + // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; + StepNo = 3; +} + +bool ImGuiListClipper::Step() +{ + if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) + { + ItemsCount = -1; + return false; + } + if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height. + { + DisplayStart = 0; + DisplayEnd = 1; + StartPosY = ImGui::GetCursorPosY(); + StepNo = 1; + return true; + } + if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. + { + if (ItemsCount == 1) { ItemsCount = -1; return false; } + float items_height = ImGui::GetCursorPosY() - StartPosY; + IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically + Begin(ItemsCount-1, items_height); + DisplayStart++; + DisplayEnd++; + StepNo = 3; + return true; + } + if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3. + { + IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); + StepNo = 3; + return true; + } + if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. + End(); + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiWindow +//----------------------------------------------------------------------------- + +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) +{ + Name = ImStrdup(name); + ID = ImHash(name, 0); + IDStack.push_back(ID); + Flags = 0; + PosFloat = Pos = ImVec2(0.0f, 0.0f); + Size = SizeFull = ImVec2(0.0f, 0.0f); + SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); + WindowPadding = ImVec2(0.0f, 0.0f); + WindowRounding = 0.0f; + WindowBorderSize = 0.0f; + MoveId = GetID("#MOVE"); + ChildId = 0; + Scroll = ImVec2(0.0f, 0.0f); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + ScrollbarX = ScrollbarY = false; + ScrollbarSizes = ImVec2(0.0f, 0.0f); + Active = WasActive = false; + WriteAccessed = false; + Collapsed = false; + CollapseToggleWanted = false; + SkipItems = false; + Appearing = false; + CloseButton = false; + BeginOrderWithinParent = -1; + BeginOrderWithinContext = -1; + BeginCount = 0; + PopupId = 0; + AutoFitFramesX = AutoFitFramesY = -1; + AutoFitOnlyGrows = false; + AutoFitChildAxises = 0x00; + AutoPosLastDirection = ImGuiDir_None; + HiddenFrames = 0; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + + LastFrameActive = -1; + ItemWidthDefault = 0.0f; + FontWindowScale = 1.0f; + + DrawList = IM_NEW(ImDrawList)(&context->DrawListSharedData); + DrawList->_OwnerName = Name; + ParentWindow = NULL; + RootWindow = NULL; + RootWindowForTitleBarHighlight = NULL; + RootWindowForTabbing = NULL; + RootWindowForNav = NULL; + + NavLastIds[0] = NavLastIds[1] = 0; + NavRectRel[0] = NavRectRel[1] = ImRect(); + NavLastChildNavWindow = NULL; + + FocusIdxAllCounter = FocusIdxTabCounter = -1; + FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; + FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_DELETE(DrawList); + IM_DELETE(Name); + for (int i = 0; i != ColumnsStorage.Size; i++) + ColumnsStorage[i].~ImGuiColumnsSet(); +} + +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(&ptr, sizeof(void*), seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; + ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed); + ImGui::KeepAliveID(id); + return id; +} + +//----------------------------------------------------------------------------- +// Internal API exposed in imgui_internal.h +//----------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow = window; + if (window) + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +static void SetNavID(ImGuiID id, int nav_layer) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindow); + IM_ASSERT(nav_layer == 0 || nav_layer == 1); + g.NavId = id; + g.NavWindow->NavLastIds[nav_layer] = id; +} + +static void SetNavIDAndMoveMouse(ImGuiID id, int nav_layer, const ImRect& rect_rel) +{ + ImGuiContext& g = *GImGui; + SetNavID(id, nav_layer); + g.NavWindow->NavRectRel[nav_layer] = rect_rel; + g.NavMousePosDirty = true; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; +} + +void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) + g.ActiveIdTimer = 0.0f; + g.ActiveId = id; + g.ActiveIdAllowNavDirFlags = 0; + g.ActiveIdAllowOverlap = false; + g.ActiveIdWindow = window; + if (id) + { + g.ActiveIdIsAlive = true; + g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; + } +} + +ImGuiID ImGui::GetActiveID() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId; +} + +void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0); + + // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it. + const int nav_layer = window->DC.NavLayerCurrent; + if (g.NavWindow != window) + g.NavInitRequest = false; + g.NavId = id; + g.NavWindow = window; + g.NavLayer = nav_layer; + window->NavLastIds[nav_layer] = id; + if (window->DC.LastItemId == id) + window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + + if (g.ActiveIdSource == ImGuiInputSource_Nav) + g.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f; +} + +ImGuiID ImGui::GetHoveredID() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; +} + +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = true; +} + +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + + return true; +} + +// Advance cursor given item size for layout. +void ImGui::ItemSize(const ImVec2& size, float text_offset_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Always align ourselves on pixel boundaries + const float line_height = ImMax(window->DC.CurrentLineHeight, size.y); + const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); + window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineHeight = line_height; + window->DC.PrevLineTextBaseOffset = text_base_offset; + window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + +void ImGui::ItemSize(const ImRect& bb, float text_offset_y) +{ + ItemSize(bb.GetSize(), text_offset_y); +} + +static ImGuiDir NavScoreItemGetQuadrant(float dx, float dy) +{ + if (fabsf(dx) > fabsf(dy)) + return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; + return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; +} + +static float NavScoreItemDistInterval(float a0, float a1, float b0, float b1) +{ + if (a1 < b0) + return a1 - b0; + if (b1 < a0) + return a0 - b1; + return 0.0f; +} + +// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavLayer != window->DC.NavLayerCurrent) + return false; + + const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringCount++; + + // We perform scoring on items bounding box clipped by their parent window on the other axis (clipping on our movement axis would give us equal scores for all clipped items) + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + { + cand.Min.y = ImClamp(cand.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y); + cand.Max.y = ImClamp(cand.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y); + } + else + { + cand.Min.x = ImClamp(cand.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + cand.Max.x = ImClamp(cand.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + } + + // Compute distance between boxes + // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. + float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + if (dby != 0.0f && dbx != 0.0f) + dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); + float dist_box = fabsf(dbx) + fabsf(dby); + + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) + float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); + float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); + float dist_center = fabsf(dcx) + fabsf(dcy); // L1 metric (need this for our connectedness guarantee) + + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance + ImGuiDir quadrant; + float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; + if (dbx != 0.0f || dby != 0.0f) + { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = NavScoreItemGetQuadrant(dbx, dby); + } + else if (dcx != 0.0f || dcy != 0.0f) + { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = NavScoreItemGetQuadrant(dcx, dcy); + } + else + { + // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) + quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + g.OverlayDrawList.AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100)); + g.OverlayDrawList.AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); + g.OverlayDrawList.AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); + g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + } + else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + { + if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } + if (quadrant == g.NavMoveDir) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + g.OverlayDrawList.AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); + g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } + #endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + if (quadrant == g.NavMoveDir) + { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) + { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) + { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) + { + result->DistCenter = dist_center; + new_best = true; + } + else if (dist_center == result->DistCenter) + { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items + // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), + // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. + if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches + // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) + // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. + // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. + // Disabling it may however lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +static void NavSaveLastChildNavWindow(ImGuiWindow* child_window) +{ + ImGuiWindow* parent_window = child_window; + while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent_window = parent_window->ParentWindow; + if (parent_window && parent_window != child_window) + parent_window->NavLastChildNavWindow = child_window; +} + +// Call when we are expected to land on Layer 0 after FocusWindow() +static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window) +{ + return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; +} + +static void NavRestoreLayer(int layer) +{ + ImGuiContext& g = *GImGui; + g.NavLayer = layer; + if (layer == 0) + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); + if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) + SetNavIDAndMoveMouse(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); + else + ImGui::NavInitWindow(g.NavWindow, true); +} + +static inline void NavUpdateAnyRequestFlag() +{ + ImGuiContext& g = *GImGui; + g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || IMGUI_DEBUG_NAV_SCORING; +} + +static bool NavMoveRequestButNoResultYet() +{ + ImGuiContext& g = *GImGui; + return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +static void NavMoveRequestCancel() +{ + ImGuiContext& g = *GImGui; + g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); +} + +// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) +static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +{ + ImGuiContext& g = *GImGui; + //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. + // return; + + const ImGuiItemFlags item_flags = window->DC.ItemFlags; + const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + { + g.NavInitResultId = id; + g.NavInitResultRectRel = nav_bb_rel; + } + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + { + g.NavInitRequest = false; // Found a match, clear request + NavUpdateAnyRequestFlag(); + } + } + + // Scoring for navigation + if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav)) + { + ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; +#if IMGUI_DEBUG_NAV_SCORING + // [DEBUG] Score all items in NavWindow at all times + if (!g.NavMoveRequest) + g.NavMoveDir = g.NavMoveDirLast; + bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; +#else + bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); +#endif + if (new_best) + { + result->ID = id; + result->ParentID = window->IDStack.back(); + result->Window = window; + result->RectRel = nav_bb_rel; + } + } + + // Update window-relative bounding box of navigated item + if (g.NavId == id) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavIdIsAlive = true; + g.NavIdTabCounter = window->FocusIdxTabCounter; + window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + } +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (id != 0) + { + // Navigation processing runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window. + // it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick) + window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask; + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); + } + + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemStatusFlags = 0; + + // Clipping test + const bool is_clipped = IsClippedEx(bb, id, false); + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (IsMouseHoveringRect(bb.Min, bb.Max)) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavDisableMouseHover && !g.NavDisableHighlight) + return IsItemFocused(); + + // Test for bounding box overlap, as updated as ItemAdd() + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function + + // Test if we are hovering the right window (our window could be behind another window) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. + // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. + //if (g.HoveredWindow != window) + // return false; + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) + return false; + + // Test if another item is active (e.g. being dragged) + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + + // Test if interactions on this window are blocked by an active popup or modal + if (!IsWindowContentHoverable(window, flags)) + return false; + + // Test if the item is disabled + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. + if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_Default)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + SetHoveredID(id); + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || id != g.ActiveId) + if (clip_even_when_logged || !g.LogEnabled) + return true; + return false; +} + +bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) +{ + ImGuiContext& g = *GImGui; + + const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus; + window->FocusIdxAllCounter++; + if (allow_keyboard_focus) + window->FocusIdxTabCounter++; + + // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. + // Note that we can always TAB out of a widget that doesn't allow tabbing in. + if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) + window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + + if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) + return true; + if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) + { + g.NavJustTabbedId = id; + return true; + } + + return false; +} + +void ImGui::FocusableItemUnregister(ImGuiWindow* window) +{ + window->FocusIdxAllCounter--; + window->FocusIdxTabCounter--; +} + +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) +{ + ImGuiContext& g = *GImGui; + ImVec2 content_max; + if (size.x < 0.0f || size.y < 0.0f) + content_max = g.CurrentWindow->Pos + GetContentRegionMax(); + if (size.x <= 0.0f) + size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; + if (size.y <= 0.0f) + size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; + return size; +} + +float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) +{ + if (wrap_pos_x < 0.0f) + return 0.0f; + + ImGuiWindow* window = GetCurrentWindowRead(); + if (wrap_pos_x == 0.0f) + wrap_pos_x = GetContentRegionMax().x + window->Pos.x; + else if (wrap_pos_x > 0.0f) + wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space + + return ImMax(wrap_pos_x - pos.x, 1.0f); +} + +//----------------------------------------------------------------------------- + +void* ImGui::MemAlloc(size_t sz) +{ + GImAllocatorActiveAllocationsCount++; + return GImAllocatorAllocFunc(sz, GImAllocatorUserData); +} + +void ImGui::MemFree(void* ptr) +{ + if (ptr) GImAllocatorActiveAllocationsCount--; + return GImAllocatorFreeFunc(ptr, GImAllocatorUserData); +} + +const char* ImGui::GetClipboardText() +{ + return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char* text) +{ + if (GImGui->IO.SetClipboardTextFn) + GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); +} + +const char* ImGui::GetVersion() +{ + return IMGUI_VERSION; +} + +// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data) +{ + GImAllocatorAllocFunc = alloc_func; + GImAllocatorFreeFunc = free_func; + GImAllocatorUserData = user_data; +} + +ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) +{ + ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + if (GImGui == NULL) + SetCurrentContext(ctx); + Initialize(ctx); + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + if (ctx == NULL) + ctx = GImGui; + Shutdown(ctx); + if (GImGui == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +ImGuiIO& ImGui::GetIO() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->IO; +} + +ImGuiStyle& ImGui::GetStyle() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->Style; +} + +// Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame() +ImDrawData* ImGui::GetDrawData() +{ + ImGuiContext& g = *GImGui; + return g.DrawData.Valid ? &g.DrawData : NULL; +} + +float ImGui::GetTime() +{ + return GImGui->Time; +} + +int ImGui::GetFrameCount() +{ + return GImGui->FrameCount; +} + +ImDrawList* ImGui::GetOverlayDrawList() +{ + return &GImGui->OverlayDrawList; +} + +ImDrawListSharedData* ImGui::GetDrawListSharedData() +{ + return &GImGui->DrawListSharedData; +} + +// This needs to be called before we submit any widget (aka in or before Begin) +void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == g.NavWindow); + bool init_for_nav = false; + if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; + if (init_for_nav) + { + SetNavID(0, g.NavLayer); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } + else + { + g.NavId = window->NavLastIds[0]; + } +} + +static ImVec2 NavCalcPreferredMousePos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.NavWindow; + if (!window) + return g.IO.MousePos; + const ImRect& rect_rel = window->NavRectRel[g.NavLayer]; + ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImRect visible_rect = GetViewportRect(); + return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. +} + +static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size-1; i >= 0; i--) + if (g.Windows[i] == window) + return i; + return -1; +} + +static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.Windows[i])) + return g.Windows[i]; + return NULL; +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + ImGuiContext& g = *GImGui; + if (mode == ImGuiInputReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) + return 0.0f; + if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. + return (t == 0.0f) ? 1.0f : 0.0f; + if (mode == ImGuiInputReadMode_Repeat) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f); + if (mode == ImGuiInputReadMode_RepeatSlow) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f); + if (mode == ImGuiInputReadMode_RepeatFast) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f); + return 0.0f; +} + +// Equivalent of IsKeyDown() for NavInputs[] +static bool IsNavInputDown(ImGuiNavInput n) +{ + return GImGui->IO.NavInputs[n] > 0.0f; +} + +// Equivalent of IsKeyPressed() for NavInputs[] +static bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + return ImGui::GetNavInputAmount(n, mode) > 0.0f; +} + +static bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) +{ + return (ImGui::GetNavInputAmount(n1, mode) + ImGui::GetNavInputAmount(n2, mode)) > 0.0f; +} + +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +{ + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = FindWindowIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); + if (!window_target) + window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir); + g.NavWindowingTarget = window_target; + g.NavWindowingToggleLayer = false; +} + +// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) +static void ImGui::NavUpdateWindowing() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; + + bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard); + if (start_windowing_with_gamepad || start_windowing_with_keyboard) + if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1)) + { + g.NavWindowingTarget = window->RootWindowForTabbing; + g.NavWindowingHighlightTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavWindowingInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + } + + // Gamepad update + g.NavWindowingHighlightTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + { + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.20f) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); + if (focus_change_dir != 0) + { + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } + + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) + if (!IsNavInputDown(ImGuiNavInput_Menu)) + { + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.15f) / 0.04f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and Release ALT to toggle menu layer + // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) + if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) + apply_toggle_layer = true; + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + ImVec2 move_delta; + if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingTarget->PosFloat += move_delta * move_speed; + g.NavDisableMouseHover = true; + MarkIniSettingsDirty(g.NavWindowingTarget); + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowForTabbing)) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window); + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window only has a menu layer, select it directly + if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) + g.NavLayer = 1; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) + { + ImGuiWindow* new_nav_window = g.NavWindow; + while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) + { + ImGuiWindow* old_nav_window = g.NavWindow; + FocusWindow(new_nav_window); + new_nav_window->NavLastChildNavWindow = old_nav_window; + } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0); + } +} + +// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. +static void NavScrollToBringItemIntoView(ImGuiWindow* window, ImRect& item_rect_rel) +{ + // Scroll to keep newly navigated item fully into view + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); + //g.OverlayDrawList.AddRect(window->Pos + window_rect_rel.Min, window->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG] + if (window_rect_rel.Contains(item_rect_rel)) + return; + + ImGuiContext& g = *GImGui; + if (window->ScrollbarX && item_rect_rel.Min.x < window_rect_rel.Min.x) + { + window->ScrollTarget.x = item_rect_rel.Min.x + window->Scroll.x - g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + else if (window->ScrollbarX && item_rect_rel.Max.x >= window_rect_rel.Max.x) + { + window->ScrollTarget.x = item_rect_rel.Max.x + window->Scroll.x + g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 1.0f; + } + if (item_rect_rel.Min.y < window_rect_rel.Min.y) + { + window->ScrollTarget.y = item_rect_rel.Min.y + window->Scroll.y - g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + else if (item_rect_rel.Max.y >= window_rect_rel.Max.y) + { + window->ScrollTarget.y = item_rect_rel.Max.y + window->Scroll.y + g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 1.0f; + } + + // Estimate upcoming scroll so we can offset our relative mouse position so mouse position can be applied immediately (under this block) + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); + item_rect_rel.Translate(window->Scroll - next_scroll); +} + +static void ImGui::NavUpdate() +{ + ImGuiContext& g = *GImGui; + g.IO.WantMoveMouse = false; + +#if 0 + if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); +#endif + + // Update Keyboard->Nav inputs mapping + memset(g.IO.NavInputs + ImGuiNavInput_InternalStart_, 0, (ImGuiNavInput_COUNT - ImGuiNavInput_InternalStart_) * sizeof(g.IO.NavInputs[0])); + if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) + { + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (g.IO.KeyMap[_KEY] != -1 && IsKeyDown(g.IO.KeyMap[_KEY])) g.IO.NavInputs[_NAV_INPUT] = 1.0f; + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); + NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); + if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; + if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; +#undef NAV_MAP_KEY + } + + memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) + g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) + { + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + IM_ASSERT(g.NavWindow); + if (g.NavInitRequestFromMove) + SetNavIDAndMoveMouse(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); + else + SetNavID(g.NavInitResultId, g.NavLayer); + g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + } + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 0; + + // Process navigation move request + if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) + { + // Select which result to use + ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + if (g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) // Maybe entering a flattened child? In this case solve the tie using the regular scoring rules + if ((g.NavMoveResultOther.DistBox < g.NavMoveResultLocal.DistBox) || (g.NavMoveResultOther.DistBox == g.NavMoveResultLocal.DistBox && g.NavMoveResultOther.DistCenter < g.NavMoveResultLocal.DistCenter)) + result = &g.NavMoveResultOther; + + IM_ASSERT(g.NavWindow && result->Window); + + // Scroll to keep newly navigated item fully into view + if (g.NavLayer == 0) + NavScrollToBringItemIntoView(result->Window, result->RectRel); + + // Apply result from previous frame navigation directional move request + ClearActiveID(); + g.NavWindow = result->Window; + SetNavIDAndMoveMouse(result->ID, g.NavLayer, result->RectRel); + g.NavJustMovedToId = result->ID; + g.NavMoveFromClampedRefRect = false; + } + + // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame + if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + { + IM_ASSERT(g.NavMoveRequest); + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + g.NavDisableHighlight = false; + g.NavMoveRequestForward = ImGuiNavForward_None; + } + + // Apply application mouse position movement, after we had a chance to process move request result. + if (g.NavMousePosDirty && g.NavIdIsAlive) + { + // Set mouse position given our knowledge of the nav widget position from last frame + if (g.IO.NavFlags & ImGuiNavFlags_MoveMouse) + { + g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredMousePos(); + g.IO.WantMoveMouse = true; + } + g.NavMousePosDirty = false; + } + g.NavIdIsAlive = false; + g.NavJustTabbedId = 0; + IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); + + // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 + if (g.NavWindow) + NavSaveLastChildNavWindow(g.NavWindow); + if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) + g.NavWindow->NavLastChildNavWindow = NULL; + + NavUpdateWindowing(); + + // Set output flags for user application + g.IO.NavActive = (g.IO.NavFlags & (ImGuiNavFlags_EnableGamepad | ImGuiNavFlags_EnableKeyboard)) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); + g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; + + // Process NavCancel input (to close a popup, get back to parent, clear focus) + if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) + { + if (g.ActiveId != 0) + { + ClearActiveID(); + } + else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + { + // Exit child window + ImGuiWindow* child_window = g.NavWindow; + ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, 0); + g.NavIdIsAlive = false; + if (g.NavDisableMouseHover) + g.NavMousePosDirty = true; + } + else if (g.OpenPopupStack.Size > 0) + { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + } + else if (g.NavLayer != 0) + { + // Leave the "menu" layer + NavRestoreLayer(0); + } + else + { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = 0; + } + } + + // Process manual activation request + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + if (g.ActiveId == 0 && activate_pressed) + g.NavActivateId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) + g.NavActivatePressedId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) + g.NavInputId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + g.NavMoveRequest = false; + + // Process programmatic activation request + if (g.NavNextActivateId != 0) + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + g.NavNextActivateId = 0; + + // Initiate directional inputs request + const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; + if (g.NavMoveRequestForward == ImGuiNavForward_None) + { + g.NavMoveDir = ImGuiDir_None; + if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + if ((allowed_dir_flags & (1<Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) + { + // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item + ImGuiWindow* window = g.NavWindow; + const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + { + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) + SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + } + + // *Normal* Manual scroll with NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + { + SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + if (scroll_dir.y != 0.0f) + { + SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + } + + // Reset search results + g.NavMoveResultLocal.Clear(); + g.NavMoveResultOther.Clear(); + + // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items + if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1,1), window->InnerRect.Max - window->Pos + ImVec2(1,1)); + if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) + { + float pad = window->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item + window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); + g.NavId = 0; + } + g.NavMoveFromClampedRefRect = false; + } + + // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) + ImRect nav_rect_rel = (g.NavWindow && g.NavWindow->NavRectRel[g.NavLayer].IsFinite()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); + g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect(); + g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); + g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; + IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous fabsf() calls in NavScoreItem(). + //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] + g.NavScoringCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) g.OverlayDrawList.AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames <= 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredMousePos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } +#endif +} + +static void ImGui::UpdateMovingWindow() +{ + ImGuiContext& g = *GImGui; + if (g.MovingWindow && g.MovingWindow->MoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse) + { + // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). + // We track it to preserve Focus and so that ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. + KeepAliveID(g.ActiveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + ImGuiWindow* moving_window = g.MovingWindow->RootWindow; + if (g.IO.MouseDown[0]) + { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + if (moving_window->PosFloat.x != pos.x || moving_window->PosFloat.y != pos.y) + { + MarkIniSettingsDirty(moving_window); + moving_window->PosFloat = pos; + } + FocusWindow(g.MovingWindow); + } + else + { + ClearActiveID(); + g.MovingWindow = NULL; + } + } + else + { + // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. + if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) + { + KeepAliveID(g.ActiveId); + if (!g.IO.MouseDown[0]) + ClearActiveID(); + } + g.MovingWindow = NULL; + } +} + +void ImGui::NewFrame() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + ImGuiContext& g = *GImGui; + + // Check user data + // (We pass an error message in the assert expression as a trick to get it visible to programmers who are not using a debugger, as most assert handlers display their argument) + IM_ASSERT(g.Initialized); + IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)"); + IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value"); + IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting"); + IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)"); + IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); + for (int n = 0; n < ImGuiKey_COUNT; n++) + IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)"); + + // Do a simple check for required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was super recently added in 1.60 WIP) + if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) + IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); + + // Load settings on first frame + if (!g.SettingsLoaded) + { + IM_ASSERT(g.SettingsWindows.empty()); + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.SettingsLoaded = true; + } + + g.Time += g.IO.DeltaTime; + g.FrameCount += 1; + g.TooltipOverrideCount = 0; + g.WindowsActiveCount = 0; + + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + + g.OverlayDrawList.Clear(); + g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); + g.OverlayDrawList.PushClipRectFullScreen(); + g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it + g.DrawData.Clear(); + + // Clear reference to active widget if the widget isn't alive anymore + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + if (g.ActiveId) + g.ActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) + g.ScalarAsInputTextId = 0; + + // Elapse drag & drop payload + if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) + { + ClearDragDrop(); + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + } + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + + // Update keyboard input state + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Update gamepad/keyboard directional navigation + NavUpdate(); + + // Update mouse input state + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) + g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) + g.NavDisableMouseHover = false; + + g.IO.MousePosPrev = g.IO.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseDoubleClicked[i] = false; + if (g.IO.MouseClicked[i]) + { + if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime) + { + if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) + g.IO.MouseDoubleClicked[i] = true; + g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click + } + else + { + g.IO.MouseClickedTime[i] = g.Time; + } + g.IO.MouseClickedPos[i] = g.IO.MousePos; + g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (g.IO.MouseDown[i]) + { + ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i]; + g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x); + g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y); + g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta)); + } + if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + g.NavDisableMouseHover = false; + } + + // Calculate frame-rate for the user, as a purely luxurious feature + g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; + g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); + g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); + + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) + UpdateMovingWindow(); + + // Delay saving settings so we don't spam disk too much + if (g.SettingsDirtyTimer > 0.0f) + { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) + SaveIniSettingsToDisk(g.IO.IniFilename); + } + + // Find the window we are hovering + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow(); + g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + + ImGuiWindow* modal_window = GetFrontMostModalRootWindow(); + if (modal_window != NULL) + { + g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f); + if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) + g.HoveredRootWindow = g.HoveredWindow = NULL; + } + else + { + g.ModalWindowDarkeningRatio = 0.0f; + } + + // Update the WantCaptureMouse/WantCaptureKeyboard flags, so user can capture/discard the inputs away from the rest of their application. + // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership. + int mouse_earliest_button_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + if (g.IO.MouseClicked[i]) + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); + mouse_any_down |= g.IO.MouseDown[i]; + if (g.IO.MouseDown[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) + mouse_earliest_button_down = i; + } + bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); + else + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + + if (g.WantCaptureKeyboardNextFrame != -1) + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + if (g.IO.NavActive && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) && !(g.IO.NavFlags & ImGuiNavFlags_NoCaptureKeyboard)) + g.IO.WantCaptureKeyboard = true; + + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0; + g.MouseCursor = ImGuiMouseCursor_Arrow; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) + bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; + if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) + g.HoveredWindow = g.HoveredRootWindow = NULL; + + // Mouse wheel scrolling, scale + if (g.HoveredWindow && !g.HoveredWindow->Collapsed && (g.IO.MouseWheel != 0.0f || g.IO.MouseWheelH != 0.0f)) + { + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set). + ImGuiWindow* window = g.HoveredWindow; + ImGuiWindow* scroll_window = window; + while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow) + scroll_window = scroll_window->ParentWindow; + const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs); + + if (g.IO.MouseWheel != 0.0f) + { + if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) + { + // Zoom / Scale window + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + window->Pos += offset; + window->PosFloat += offset; + window->Size *= scale; + window->SizeFull *= scale; + } + else if (!g.IO.KeyCtrl && scroll_allowed) + { + // Mouse wheel vertical scrolling + float scroll_amount = 5 * scroll_window->CalcFontSize(); + scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f); + SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount); + } + } + if (g.IO.MouseWheelH != 0.0f && scroll_allowed) + { + // Mouse wheel horizontal scrolling (for hardware that supports it) + float scroll_amount = scroll_window->CalcFontSize(); + if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) + SetWindowScrollX(window, window->Scroll.x - g.IO.MouseWheelH * scroll_amount); + } + } + + // Pressing TAB activate widget focus + if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false)) + { + if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) + g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1); + else + g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0; + } + g.NavIdTabCounter = INT_MAX; + + // Mark all windows as not visible + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->Active = false; + window->WriteAccessed = false; + } + + // Closing the focused window restore focus to the first active root window in descending z-order + if (g.NavWindow && !g.NavWindow->WasActive) + FocusFrontMostActiveWindow(NULL); + + // No window should be open at the beginning of the frame. + // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. + g.CurrentWindowStack.resize(0); + g.CurrentPopupStack.resize(0); + ClosePopupsOverWindow(g.NavWindow); + + // Create implicit window - we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. + SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); +} + +static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); + if (!settings) + settings = AddWindowSettings(name); + return (void*)settings; +} + +static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; + float x, y; + int i; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); + else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); +} + +static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + ImGuiContext& g = *imgui_ctx; + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID); + if (!settings) + settings = AddWindowSettings(window->Name); + settings->Pos = window->Pos; + settings->Size = window->SizeFull; + settings->Collapsed = window->Collapsed; + } + + // Write a buffer + // If a window wasn't opened in this session we preserve its settings + buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) + { + const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; + if (settings->Pos.x == FLT_MAX) + continue; + const char* name = settings->Name; + if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + name = p; + buf->appendf("[%s][%s]\n", handler->TypeName, name); + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->appendf("\n"); + } +} + +void ImGui::Initialize(ImGuiContext* context) +{ + ImGuiContext& g = *context; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + g.LogClipboard = IM_NEW(ImGuiTextBuffer)(); + + // Add .ini handle for ImGuiWindow type + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHash("Window", 0, 0); + ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; + ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; + ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; + g.SettingsHandlers.push_front(ini_handler); + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown(ImGuiContext* context) +{ + ImGuiContext& g = *context; + + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + if (g.IO.Fonts && g.FontAtlasOwnedByContext) + IM_DELETE(g.IO.Fonts); + + // Cleanup of other data are conditional on actually having initialize ImGui. + if (!g.Initialized) + return; + + SaveIniSettingsToDisk(g.IO.IniFilename); + + // Clear everything else + for (int i = 0; i < g.Windows.Size; i++) + IM_DELETE(g.Windows[i]); + g.Windows.clear(); + g.WindowsSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = NULL; + g.HoveredRootWindow = NULL; + g.ActiveIdWindow = NULL; + g.MovingWindow = NULL; + for (int i = 0; i < g.SettingsWindows.Size; i++) + IM_DELETE(g.SettingsWindows[i].Name); + g.ColorModifiers.clear(); + g.StyleModifiers.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.CurrentPopupStack.clear(); + g.DrawDataBuilder.ClearFreeMemory(); + g.OverlayDrawList.ClearFreeMemory(); + g.PrivateClipboard.clear(); + g.InputTextState.Text.clear(); + g.InputTextState.InitialText.clear(); + g.InputTextState.TempTextBuffer.clear(); + + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile && g.LogFile != stdout) + { + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard) + IM_DELETE(g.LogClipboard); + + g.Initialized = false; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (int i = 0; i != g.SettingsWindows.Size; i++) + if (g.SettingsWindows[i].Id == id) + return &g.SettingsWindows[i]; + return NULL; +} + +static ImGuiWindowSettings* AddWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + g.SettingsWindows.push_back(ImGuiWindowSettings()); + ImGuiWindowSettings* settings = &g.SettingsWindows.back(); + settings->Name = ImStrdup(name); + settings->Id = ImHash(name, 0); + return settings; +} + +static void LoadIniSettingsFromDisk(const char* ini_filename) +{ + if (!ini_filename) + return; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1); + if (!file_data) + return; + LoadIniSettingsFromMemory(file_data); + ImGui::MemFree(file_data); +} + +ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) +{ + ImGuiContext& g = *GImGui; + const ImGuiID type_hash = ImHash(type_name, 0, 0); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +// Zero-tolerance, no error reporting, cheap .ini parsing +static void LoadIniSettingsFromMemory(const char* buf_readonly) +{ + // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy. + char* buf = ImStrdup(buf_readonly); + char* buf_end = buf + strlen(buf); + + ImGuiContext& g = *GImGui; + void* entry_data = NULL; + ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) + { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + + if (line[0] == '[' && line_end > line && line_end[-1] == ']') + { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; + char* type_end = ImStrchrRange(type_start, name_end, ']'); + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + { + name_start = type_start; // Import legacy entries that have no type + type_start = "Window"; + } + else + { + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + } + entry_handler = ImGui::FindSettingsHandler(type_start); + entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; + } + else if (entry_handler != NULL && entry_data != NULL) + { + // Let type handler parse the line + entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); + } + } + ImGui::MemFree(buf); + g.SettingsLoaded = true; +} + +static void SaveIniSettingsToDisk(const char* ini_filename) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + if (!ini_filename) + return; + + ImVector buf; + SaveIniSettingsToMemory(buf); + + FILE* f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f); + fclose(f); +} + +static void SaveIniSettingsToMemory(ImVector& out_buf) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + + ImGuiTextBuffer buf; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + { + ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; + handler->WriteAllFn(&g, handler, &buf); + } + + buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer + out_buf.swap(buf.Buf); +} + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +static void MarkIniSettingsDirty(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +// FIXME: Add a more explicit sort order in the window structure. +static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) +{ + const ImGuiWindow* a = *(const ImGuiWindow**)lhs; + const ImGuiWindow* b = *(const ImGuiWindow**)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) +{ + out_sorted_windows->push_back(window); + if (window->Active) + { + int count = window->DC.ChildWindows.Size; + if (count > 1) + qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); + for (int i = 0; i < count; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortedBuffer(out_sorted_windows, child); + } + } +} + +static void AddDrawListToDrawData(ImVector* out_render_list, ImDrawList* draw_list) +{ + if (draw_list->CmdBuffer.empty()) + return; + + // Remove trailing command if unused + ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); + if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) + { + draw_list->CmdBuffer.pop_back(); + if (draw_list->CmdBuffer.empty()) + return; + } + + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly. + IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); + IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); + IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + + // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) + // If this assert triggers because you are drawing lots of stuff manually: + // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents. + // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes. + // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API. + // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists. + if (sizeof(ImDrawIdx) == 2) + IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + + out_render_list->push_back(draw_list); +} + +static void AddWindowToDrawData(ImVector* out_render_list, ImGuiWindow* window) +{ + AddDrawListToDrawData(out_render_list, window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active && child->HiddenFrames <= 0) // clipped children may have been marked not active + AddWindowToDrawData(out_render_list, child); + } +} + +static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.IO.MetricsActiveWindows++; + if (window->Flags & ImGuiWindowFlags_Tooltip) + AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window); + else + AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window); +} + +void ImDrawDataBuilder::FlattenIntoSingleLayer() +{ + int n = Layers[0].Size; + int size = n; + for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) + size += Layers[i].Size; + Layers[0].resize(size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) + { + ImVector& layer = Layers[layer_n]; + if (layer.empty()) + continue; + memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + layer.resize(0); + } +} + +static void SetupDrawData(ImVector* draw_lists, ImDrawData* out_draw_data) +{ + out_draw_data->Valid = true; + out_draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; + out_draw_data->CmdListsCount = draw_lists->Size; + out_draw_data->TotalVtxCount = out_draw_data->TotalIdxCount = 0; + for (int n = 0; n < draw_lists->Size; n++) + { + out_draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; + out_draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size; + } +} + +// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. +void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +void ImGui::PopClipRect() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PopClipRect(); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. +void ImGui::EndFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times. + return; + + // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) + if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f) + { + g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y); + g.OsImePosSet = g.OsImePosRequest; + } + + // Hide implicit "Debug" window if it hasn't been used + IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls + if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + g.CurrentWindow->Active = false; + End(); + + if (g.ActiveId == 0 && g.HoveredId == 0) + { + if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear + { + // Click to focus window and start moving (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) + { + if (g.HoveredRootWindow != NULL) + { + // Set ActiveId even if the _NoMove flag is set, without it dragging away from a window with _NoMove would activate hover on other windows. + FocusWindow(g.HoveredWindow); + SetActiveID(g.HoveredWindow->MoveId, g.HoveredWindow); + g.NavDisableHighlight = true; + g.ActiveIdClickOffset = g.IO.MousePos - g.HoveredRootWindow->Pos; + if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove)) + g.MovingWindow = g.HoveredWindow; + } + else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) + { + // Clicking on void disable focus + FocusWindow(NULL); + } + } + + // With right mouse button we close popups without changing focus + // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger) + if (g.IO.MouseClicked[1]) + { + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostModalRootWindow(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal); + } + } + } + + // Sort the window list so that all child windows are after their parent + // We cannot do that on FocusWindow() because childs may not exist yet + g.WindowsSortBuffer.resize(0); + g.WindowsSortBuffer.reserve(g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + continue; + AddWindowToSortedBuffer(&g.WindowsSortBuffer, window); + } + + IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong + g.Windows.swap(g.WindowsSortBuffer); + + // Clear Input data for next frame + g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); + + g.FrameCountEnded = g.FrameCount; +} + +void ImGui::Render() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + + if (g.FrameCountEnded != g.FrameCount) + ImGui::EndFrame(); + g.FrameCountRendered = g.FrameCount; + + // Skip render altogether if alpha is 0.0 + // Note that vertex buffers have been created and are wasted, so it is best practice that you don't create windows in the first place, or consistently respond to Begin() returning false. + if (g.Style.Alpha > 0.0f) + { + // Gather windows to render + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0; + g.DrawDataBuilder.Clear(); + ImGuiWindow* window_to_render_front_most = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget : NULL; + for (int n = 0; n != g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0 && window != window_to_render_front_most) + AddWindowToDrawDataSelectLayer(window); + } + if (window_to_render_front_most && window_to_render_front_most->Active && window_to_render_front_most->HiddenFrames <= 0) // NavWindowingTarget is always temporarily displayed as the front-most window + AddWindowToDrawDataSelectLayer(window_to_render_front_most); + g.DrawDataBuilder.FlattenIntoSingleLayer(); + + // Draw software mouse cursor if requested + ImVec2 offset, size, uv[4]; + if (g.IO.MouseDrawCursor && g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &offset, &size, &uv[0], &uv[2])) + { + const ImVec2 pos = g.IO.MousePos - offset; + const ImTextureID tex_id = g.IO.Fonts->TexID; + const float sc = g.Style.MouseCursorScale; + g.OverlayDrawList.PushTextureID(tex_id); + g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(1,0)*sc, pos+ImVec2(1,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(2,0)*sc, pos+ImVec2(2,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[2], uv[3], IM_COL32(0,0,0,255)); // Black border + g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[0], uv[1], IM_COL32(255,255,255,255)); // White fill + g.OverlayDrawList.PopTextureID(); + } + if (!g.OverlayDrawList.VtxBuffer.empty()) + AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList); + + // Setup ImDrawData structure for end-user + SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData); + g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount; + g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount; + + // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) + g.IO.RenderDrawListsFn(&g.DrawData); +#endif + } +} + +const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) +{ + const char* text_display_end = text; + if (!text_end) + text_end = (const char*)-1; + + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) + text_display_end++; + return text_display_end; +} + +// Pass text data straight to log (without being displayed) +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + if (g.LogFile) + { + vfprintf(g.LogFile, fmt, args); + } + else + { + g.LogClipboard->appendfv(fmt, args); + } + va_end(args); +} + +// Internal version that takes a position to decide on newline placement and pad items according to their depth. +// We split text into individual lines to add current tree level padding +static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = ImGui::FindRenderedTextEnd(text, text_end); + + const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); + if (ref_pos) + window->DC.LogLinePosY = ref_pos->y; + + const char* text_remaining = text; + if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth + g.LogStartDepth = window->DC.TreeDepth; + const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); + for (;;) + { + // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. + const char* line_end = text_remaining; + while (line_end < text_end) + if (*line_end == '\n') + break; + else + line_end++; + if (line_end >= text_end) + line_end = NULL; + + const bool is_first_line = (text == text_remaining); + bool is_last_line = false; + if (line_end == NULL) + { + is_last_line = true; + line_end = text_end; + } + if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0)) + { + const int char_count = (int)(line_end - text_remaining); + if (log_new_line || !is_first_line) + ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining); + else + ImGui::LogText(" %.*s", char_count, text_remaining); + } + + if (is_last_line) + break; + text_remaining = line_end + 1; + } +} + +// Internal ImGui functions to render text +// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() +void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) + { + text_display_end = FindRenderedTextEnd(text, text_end); + } + else + { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + const int text_len = (int)(text_display_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); + } +} + +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + + const int text_len = (int)(text_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_end); + } +} + +// Default clip_rect uses (pos_min,pos_max) +// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Perform CPU side clipping for single clipped element to avoid using scissor state + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; + const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min + need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. + if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); + if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + + // Render + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); +} + +// Render a rectangle shaped with optional rounding and borders +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +// Render a triangle to denote expanded/collapsed state +void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const float h = g.FontSize * 1.00f; + float r = h * 0.40f * scale; + ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); + + ImVec2 a, b, c; + switch (dir) + { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) r = -r; + center.y -= r * 0.25f; + a = ImVec2(0,1) * r; + b = ImVec2(-0.866f,-0.5f) * r; + c = ImVec2(+0.866f,-0.5f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) r = -r; + center.x -= r * 0.25f; + a = ImVec2(1,0) * r; + b = ImVec2(-0.500f,+0.866f) * r; + c = ImVec2(-0.500f,-0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_Count_: + IM_ASSERT(0); + break; + } + + window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); +} + +void ImGui::RenderBullet(ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); +} + +void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness*0.5f; + pos += ImVec2(thickness*0.25f, thickness*0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third*0.5f; + window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); + window->DrawList->PathLineTo(ImVec2(bx, by)); + window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); + window->DrawList->PathStroke(col, false, thickness); +} + +void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +{ + ImGuiContext& g = *GImGui; + if (id != g.NavId) + return; + if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + return; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->DC.NavHideHighlightOneFrame) + return; + + float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + ImRect display_rect = bb; + display_rect.ClipWith(window->ClipRect); + if (flags & ImGuiNavHighlightFlags_TypeDefault) + { + const float THICKNESS = 2.0f; + const float DISTANCE = 3.0f + THICKNESS * 0.5f; + display_rect.Expand(ImVec2(DISTANCE,DISTANCE)); + bool fully_visible = window->ClipRect.Contains(display_rect); + if (!fully_visible) + window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS); + if (!fully_visible) + window->DrawList->PopClipRect(); + } + if (flags & ImGuiNavHighlightFlags_TypeThin) + { + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f); + } +} + +// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. +// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) +ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) +{ + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + ImFont* font = g.Font; + const float font_size = g.FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) + const float font_scale = font_size / font->FontSize; + const float character_spacing_x = 1.0f * font_scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + text_size.x = (float)(int)(text_size.x + 0.95f); + + return text_size; +} + +// Helper to calculate coarse clipping of large list of evenly sized items. +// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. +// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.LogEnabled) + { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (window->SkipItems) + { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((window->ClipRect.Min.y - pos.y) / items_height); + int end = (int)((window->ClipRect.Max.y - pos.y) / items_height); + if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Up) // When performing a navigation request, ensure we have one item extra in the direction we are moving to + start--; + if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) + end++; + + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have a lag here because WindowRectClipped is updated in Begin() so windows moved by user via SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is called, aka before the next Begin(). Moving window thankfully isn't affected. +static ImGuiWindow* FindHoveredWindow() +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (!window->Active) + continue; + if (window->Flags & ImGuiWindowFlags_NoInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding); + if (bb.Contains(g.IO.MousePos)) + return window; + } + return NULL; +} + +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(window->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + return rect_for_touch.Contains(g.IO.MousePos); +} + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat) +{ + const int key_index = GImGui->IO.KeyMap[key]; + return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false; +} + +int ImGui::GetKeyIndex(ImGuiKey imgui_key) +{ + IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); + return GImGui->IO.KeyMap[imgui_key]; +} + +// Note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your back-end/engine stored them into KeyDown[]! +bool ImGui::IsKeyDown(int user_key_index) +{ + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); + return GImGui->IO.KeysDown[user_key_index]; +} + +int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) +{ + if (t == 0.0f) + return 1; + if (t <= repeat_delay || repeat_rate <= 0.0f) + return 0; + const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); + return (count > 0) ? count : 0; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) return false; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); +} + +bool ImGui::IsKeyPressed(int user_key_index, bool repeat) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[user_key_index]; + if (t == 0.0f) + return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsKeyReleased(int user_key_index) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]; +} + +bool ImGui::IsMouseDown(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button]; +} + +bool ImGui::IsAnyMouseDown() +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + if (g.IO.MouseDown[n]) + return true; + return false; +} + +bool ImGui::IsMouseClicked(int button, bool repeat) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) + return true; + + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) + return true; + } + + return false; +} + +bool ImGui::IsMouseReleased(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + +bool ImGui::IsMouseDragging(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; +} + +ImVec2 ImGui::GetMousePos() +{ + return GImGui->IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext& g = *GImGui; + if (g.CurrentPopupStack.Size > 0) + return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos; + return g.IO.MousePos; +} + +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + if (mouse_pos == NULL) + mouse_pos = &GImGui->IO.MousePos; + const float MOUSE_INVALID = -256000.0f; + return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; +} + +// NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window. +ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr + g.IO.MouseClickedPos[button] = g.IO.MousePos; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + return GImGui->MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) +{ + GImGui->MouseCursor = cursor_type; +} + +void ImGui::CaptureKeyboardFromApp(bool capture) +{ + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; +} + +void ImGui::CaptureMouseFromApp(bool capture) +{ + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + return g.ActiveId == window->DC.LastItemId; + } + return false; +} + +bool ImGui::IsItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId; +} + +bool ImGui::IsItemClicked(int mouse_button) +{ + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default); +} + +bool ImGui::IsAnyItemHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; +} + +bool ImGui::IsAnyItemActive() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId != 0; +} + +bool ImGui::IsAnyItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId != 0 && !g.NavDisableHighlight; +} + +bool ImGui::IsItemVisible() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(window->DC.LastItemRect); +} + +// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId == g.CurrentWindow->DC.LastItemId) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == g.CurrentWindow->DC.LastItemId) + g.ActiveIdAllowOverlap = true; +} + +ImVec2 ImGui::GetItemRectMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Min; +} + +ImVec2 ImGui::GetItemRectMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Max; +} + +ImVec2 ImGui::GetItemRectSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.GetSize(); +} + +static ImRect GetViewportRect() +{ + ImGuiContext& g = *GImGui; + if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) + return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); + return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); +} + +// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) +{ + ImGuiContext& g = *GImGui; + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (override_previous_tooltip) + if (ImGuiWindow* window = FindWindowByName(window_name)) + if (window->Active) + { + // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one. + window->HiddenFrames = 1; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav; + Begin(window_name, NULL, flags | extra_flags); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) +{ + BeginTooltipEx(0, true); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +void ImGui::BeginTooltip() +{ + BeginTooltipEx(0, false); +} + +void ImGui::EndTooltip() +{ + IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls + End(); +} + +// Mark popup as open (toggle toward open state). +// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopupEx(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + int current_stack_size = g.CurrentPopupStack.Size; + ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. + popup_ref.PopupId = id; + popup_ref.Window = NULL; + popup_ref.ParentWindow = parent_window; + popup_ref.OpenFrameCount = g.FrameCount; + popup_ref.OpenParentId = parent_window->IDStack.back(); + popup_ref.OpenMousePos = g.IO.MousePos; + popup_ref.OpenPopupPos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos; + + if (g.OpenPopupStack.Size < current_stack_size + 1) + { + g.OpenPopupStack.push_back(popup_ref); + } + else + { + // Close child popups if any + g.OpenPopupStack.resize(current_stack_size + 1); + + // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui + // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing + // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. + if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) + g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; + else + g.OpenPopupStack[current_stack_size] = popup_ref; + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). + // This is equivalent to what ClosePopupToLevel() does. + //if (g.OpenPopupStack[current_stack_size].PopupId == id) + // FocusWindow(parent_window); + } +} + +void ImGui::OpenPopup(const char* str_id) +{ + ImGuiContext& g = *GImGui; + OpenPopupEx(g.CurrentWindow->GetID(str_id)); +} + +void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.empty()) + return; + + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. + // Don't close our own child popup windows. + int n = 0; + if (ref_window) + { + for (n = 0; n < g.OpenPopupStack.Size; n++) + { + ImGuiPopupRef& popup = g.OpenPopupStack[n]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) + bool has_focus = false; + for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) + has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); + if (!has_focus) + break; + } + } + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); +} + +static ImGuiWindow* GetFrontMostModalRootWindow() +{ + ImGuiContext& g = *GImGui; + for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +static void ClosePopupToLevel(int remaining) +{ + IM_ASSERT(remaining >= 0); + ImGuiContext& g = *GImGui; + ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow; + if (g.NavLayer == 0) + focus_window = NavRestoreLastChildNavWindow(focus_window); + ImGui::FocusWindow(focus_window); + focus_window->DC.NavHideHighlightOneFrame = true; + g.OpenPopupStack.resize(remaining); +} + +void ImGui::ClosePopup(ImGuiID id) +{ + if (!IsPopupOpen(id)) + return; + ImGuiContext& g = *GImGui; + ClosePopupToLevel(g.OpenPopupStack.Size - 1); +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext& g = *GImGui; + int popup_idx = g.CurrentPopupStack.Size - 1; + if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) + return; + while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) + popup_idx--; + ClosePopupToLevel(popup_idx); +} + +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (extra_flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::IsPopupOpen(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; +} + +bool ImGui::IsPopupOpen(const char* str_id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); +} + +bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + // Center modal windows by default + // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. + if (g.NextWindowData.PosCond == 0) + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); + if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + { + EndPopup(); + if (is_open) + ClosePopup(id); + return false; + } + + return is_open; +} + +static void NavProcessMoveRequestWrapAround(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.NavWindow == window && NavMoveRequestButNoResultYet()) + if ((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == 0) + { + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + g.NavWindow->NavRectRel[0].Min.y = g.NavWindow->NavRectRel[0].Max.y = ((g.NavMoveDir == ImGuiDir_Up) ? ImMax(window->SizeFull.y, window->SizeContents.y) : 0.0f) - window->Scroll.y; + } +} + +void ImGui::EndPopup() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls + IM_ASSERT(g.CurrentPopupStack.Size > 0); + + // Make all menus and popups wrap around for now, may need to expose that policy. + NavProcessMoveRequestWrapAround(g.CurrentWindow); + + End(); +} + +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id); + return true; + } + return false; +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. +bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) +{ + if (!str_id) + str_id = "window_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (also_over_items || !IsAnyItemHovered()) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) +{ + if (!str_id) + str_id = "void_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + const ImVec2 content_avail = ImGui::GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + flags |= extra_flags; + + char title[256]; + if (name) + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); + else + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); + + ImGui::SetNextWindowSize(size); + bool ret = ImGui::Begin(title, NULL, flags); + ImGuiWindow* child_window = ImGui::GetCurrentWindow(); + child_window->ChildId = id; + child_window->AutoFitChildAxises = auto_fit_axises; + g.Style.ChildBorderSize = backup_border_size; + + // Process navigation-in immediately so NavInit can run on first frame + if (!(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && g.NavActivateId == id) + { + ImGui::FocusWindow(child_window); + ImGui::NavInitWindow(child_window, false); + ImGui::SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item + g.ActiveIdSource = ImGuiInputSource_Nav; + } + + return ret; +} + +bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + IM_ASSERT(id != 0); + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss + if (window->BeginCount > 1) + { + End(); + } + else + { + // When using auto-filling child window, we don't provide full width/height to ItemSize so that it doesn't feed back into automatic size-fitting. + ImVec2 sz = GetWindowSize(); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + End(); + + ImGuiWindow* parent_window = g.CurrentWindow; + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) + { + ItemAdd(bb, window->ChildId); + RenderNavHighlight(bb, window->ChildId); + + // When browsing a window that has no activable items (scroll only) we keep a highlight on the child + if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow) + RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + } + else + { + // Not navigable into + ItemAdd(bb, 0); + } + } +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + return BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); +} + +void ImGui::EndChildFrame() +{ + EndChild(); + PopStyleVar(3); + PopStyleColor(); +} + +// Save and compare stack sizes on Begin()/End() to detect usage errors +static void CheckStacksSize(ImGuiWindow* window, bool write) +{ + // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + ImGuiContext& g = *GImGui; + int* p_backup = &window->DC.StackSizesBackup[0]; + { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop() + { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup() + { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup() + { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor() + { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar() + { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont() + IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); +} + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox +}; + +static ImVec2 FindBestWindowPosForPopup(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default) +{ + const ImGuiStyle& style = GImGui->Style; + + // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) + // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. + ImVec2 safe_padding = style.DisplaySafeAreaPadding; + ImRect r_outer(GetViewportRect()); + r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x*2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y*2) ? -safe_padding.y : 0.0f)); + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) + { + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Default popup policy + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + if (avail_w < size.x || avail_h < size.y) + continue; + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + *last_dir = dir; + return pos; + } + + // Fallback, try to keep within display + *last_dir = ImGuiDir_None; + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); +} + +ImGuiWindow* ImGui::FindWindowByName(const char* name) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = ImHash(name, 0); + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + { + // Retrieve settings from .ini file + // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + window->Pos = window->PosFloat = ImVec2(60, 60); + + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) + { + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + window->PosFloat = settings->Pos; + window->Pos = ImFloor(window->PosFloat); + window->Collapsed = settings->Collapsed; + if (ImLengthSqr(settings->Size) > 0.00001f) + size = settings->Size; + } + } + window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; + + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } + + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + return window; +} + +static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) +{ + ImGuiContext& g = *GImGui; + if (g.NextWindowData.SizeConstraintCond != 0) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.NextWindowData.SizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.NextWindowData.SizeCallback) + { + ImGuiSizeCallbackData data; + data.UserData = g.NextWindowData.SizeCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.NextWindowData.SizeCallback(&data); + new_size = data.DesiredSize; + } + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + { + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + } + return new_size; +} + +static ImVec2 CalcSizeContents(ImGuiWindow* window) +{ + ImVec2 sz; + sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); + sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); + return sz + window->WindowPadding; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + ImVec2 size_auto_fit; + if ((flags & ImGuiWindowFlags_Tooltip) != 0) + { + // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. + size_auto_fit = size_contents; + } + else + { + // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); + ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) + size_auto_fit.y += style.ScrollbarSize; + if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) + size_auto_fit.x += style.ScrollbarSize; + } + return size_auto_fit; +} + +static float GetScrollMaxX(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x)); +} + +static float GetScrollMaxY(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y)); +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) +{ + ImVec2 scroll = window->Scroll; + float cr_x = window->ScrollTargetCenterRatio.x; + float cr_y = window->ScrollTargetCenterRatio.y; + if (window->ScrollTarget.x < FLT_MAX) + scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); + if (window->ScrollTarget.y < FLT_MAX) + scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); + scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, GetScrollMaxX(window)); + scroll.y = ImMin(scroll.y, GetScrollMaxY(window)); + } + return scroll; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildBg; + return ImGuiCol_WindowBg; +} + +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +struct ImGuiResizeGripDef +{ + ImVec2 CornerPos; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; + +const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right + { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left + { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left + { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right +}; + +static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) rect.Max -= ImVec2(1,1); + if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness); + if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding); + if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y); + if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + IM_ASSERT(0); + return ImRect(); +} + +// Handle resize for: Resize Grips, Borders, Gamepad +static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return; + + const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0; + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f); + + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); + + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size); + resize_rect.FixInverted(); + bool hovered, held; + ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcSizeAfterConstraint(window, size_auto_fit); + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip + CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); + } + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) + { + const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check. + const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise + bool hovered, held; + ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE); + ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held) + { + g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) *border_held = border_n; + } + if (held) + { + ImVec2 border_target = window->Pos; + ImVec2 border_posn; + if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); } + if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); } + if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); } + if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); } + CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + } + } + PopID(); + + // Navigation/gamepad resize + if (g.NavWindowingTarget == window) + { + ImVec2 nav_resize_delta; + if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); + if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) + { + const float NAV_RESIZE_SPEED = 600.0f; + nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck. + size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta); + } + } + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) + { + window->Pos = window->PosFloat = ImFloor(pos_target); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; +} + +// Push a new ImGui window to add widgets to. +// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. +// - Begin/End can be called multiple times during the frame with the same window name to append content. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + IM_ASSERT(name != NULL); // Window name required + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + + // Find or create + ImGuiWindow* window = FindWindowByName(name); + if (!window) + { + ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. + window = CreateNewWindow(name, size_on_first_use, flags); + } + + // Automatically disable manual moving/resizing when NoInputs is set + if (flags & ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + if (flags & ImGuiWindowFlags_NavFlattened) + IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); + + const int current_frame = g.FrameCount; + const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); + if (first_begin_of_the_frame) + window->Flags = (ImGuiWindowFlags)flags; + else + flags = window->Flags; + + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); + window->CloseButton = (p_open != NULL); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack + ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + + // Add to stack + g.CurrentWindowStack.push_back(window); + SetCurrentWindow(window); + CheckStacksSize(window, true); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + popup_ref.Window = window; + g.CurrentPopupStack.push_back(popup_ref); + window->PopupId = popup_ref.PopupId; + } + + if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) + window->NavLastIds[0] = 0; + + // Process SetNextWindow***() calls + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.NextWindowData.PosCond) + { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) + { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.NextWindowData.PosVal; + window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } + else + { + SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); + } + g.NextWindowData.PosCond = 0; + } + if (g.NextWindowData.SizeCond) + { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); + SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); + g.NextWindowData.SizeCond = 0; + } + if (g.NextWindowData.ContentSizeCond) + { + // Adjust passed "client size" to become a "window size" + window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal; + if (window->SizeContentsExplicit.y != 0.0f) + window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); + g.NextWindowData.ContentSizeCond = 0; + } + else if (first_begin_of_the_frame) + { + window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); + } + if (g.NextWindowData.CollapsedCond) + { + SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); + g.NextWindowData.CollapsedCond = 0; + } + if (g.NextWindowData.FocusCond) + { + SetWindowFocus(); + g.NextWindowData.FocusCond = 0; + } + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) + { + const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) + + // Initialize + window->ParentWindow = parent_window; + window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = window->RootWindowForNav = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !window_is_child_tooltip) + window->RootWindow = parent_window->RootWindow; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = parent_window->RootWindowForTitleBarHighlight; // Same value in master branch, will differ for docking + while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + window->RootWindowForNav = window->RootWindowForNav->ParentWindow; + + window->Active = true; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = g.WindowsActiveCount++; + window->BeginCount = 0; + window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); + window->LastFrameActive = current_frame; + window->IDStack.resize(1); + + // Lock window rounding, border size and rounding so that altering the border sizes for children doesn't have side-effects. + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + + // Collapse window by double-clicking on title bar + // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + { + ImRect title_bar_rect = window->TitleBarRect(); + if (window->CollapseToggleWanted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])) + { + window->Collapsed = !window->Collapsed; + MarkIniSettingsDirty(window); + FocusWindow(window); + } + } + else + { + window->Collapsed = false; + } + window->CollapseToggleWanted = false; + + // SIZE + + // Update contents size from last frame for auto-fitting (unless explicitly specified) + window->SizeContents = CalcSizeContents(window); + + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + if (window->HiddenFrames > 0) + window->HiddenFrames--; + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user) + { + window->HiddenFrames = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) + { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->SizeContents = ImVec2(0.f, 0.f); + } + } + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); + ImVec2 size_full_modified(FLT_MAX, FLT_MAX); + if (flags & ImGuiWindowFlags_AlwaysAutoResize && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + window->SizeFull.x = size_full_modified.x = size_auto_fit.x; + if (!window_size_y_set_by_api) + window->SizeFull.y = size_full_modified.y = size_auto_fit.y; + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit only grows during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (!window->Collapsed) + MarkIniSettingsDirty(window); + } + + // Apply minimum/maximum window size constraints and final size + window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); + window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; + + // SCROLLBAR STATUS + + // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) + { + // When reading the current size we need to read it after size constraints have been applied + float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; + float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + } + + // POSITION + + // Popup latch its initial position, will position itself when it appears next frame + if (window_just_activated_by_user) + { + window->AutoPosLastDirection = ImGuiDir_None; + if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) + window->Pos = window->PosFloat = g.CurrentPopupStack.back().OpenPopupPos; + } + + // Position child window + if (flags & ImGuiWindowFlags_ChildWindow) + { + window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = window->PosFloat = parent_window->DC.CursorPos; + } + + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0); + if (window_pos_with_pivot) + { + // Position given a pivot (e.g. for centering) + SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); + } + else if (flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then our FindBestPopupWindowPos() function will move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(window_pos_set_by_api); + float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value). + ImGuiWindow* parent_menu = parent_window_in_stack; + ImRect rect_to_avoid; + if (parent_menu->DC.MenuBarAppending) + rect_to_avoid = ImRect(-FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight(), FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight() + parent_menu->MenuBarHeight()); + else + rect_to_avoid = ImRect(parent_menu->Pos.x + horizontal_overlap, -FLT_MAX, parent_menu->Pos.x + parent_menu->Size.x - horizontal_overlap - parent_menu->ScrollbarSizes.x, FLT_MAX); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + { + ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + + // Position tooltip (always follows mouse) + if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) + { + float sc = g.Style.MouseCursorScale; + ImVec2 ref_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos; + ImRect rect_to_avoid; + if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.NavFlags & ImGuiNavFlags_MoveMouse)) + rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + else + rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. + window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + if (window->AutoPosLastDirection == ImGuiDir_None) + window->PosFloat = ref_pos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + } + + // Clamp position so it stays visible + if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + { + if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + { + ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size; + window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding); + } + } + window->Pos = ImFloor(window->PosFloat); + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); + else + window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); + + // Prepare for focus requests + window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); + window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); + window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; + window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; + + // Apply scrolling + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + + // Apply focus, new windows appears in front + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + want_focus = true; + + // Handle manual resize: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = { 0 }; + const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4 + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + if (!window->Collapsed) + UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]); + + // DRAWING + + // Setup draw list and outer clipping rectangle + window->DrawList->Clear(); + window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + ImRect viewport_rect(GetViewportRect()); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) + PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); + else + PushClipRect(viewport_rect.Min, viewport_rect.Max, true); + + // Draw modal window background (darkens what is behind them) + if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) + window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio)); + + // Draw navigation selection/windowing rectangle background + if (g.NavWindowingTarget == window) + { + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); + } + + // Draw window + handle manual resize + const float window_rounding = window->WindowRounding; + const float window_border_size = window->WindowBorderSize; + const bool title_bar_is_highlight = want_focus || (g.NavWindow && window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); + const ImRect title_bar_rect = window->TitleBarRect(); + if (window->Collapsed) + { + // Title bar only + float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } + else + { + // Window background + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + if (g.NextWindowData.BgAlphaCond != 0) + { + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT); + g.NextWindowData.BgAlphaCond = 0; + } + window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); + + // Title bar + ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Scrollbars + if (window->ScrollbarX) + Scrollbar(ImGuiLayoutType_Horizontal); + if (window->ScrollbarY) + Scrollbar(ImGuiLayoutType_Vertical); + + // Render resize grips (after their input handling so we don't have a frame of latency) + if (!(flags & ImGuiWindowFlags_NoResize)) + { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } + } + + // Borders + if (window_border_size > 0.0f) + window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); + if (border_held != -1) + { + ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f); + window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); + } + if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Draw navigation selection/windowing rectangle border + if (g.NavWindowingTarget == window) + { + float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward + { + bb.Expand(-g.FontSize - 1.0f); + rounding = window->WindowRounding; + } + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + } + + // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. + window->SizeFullAtLastBegin = window->SizeFull; + + // Update ContentsRegionMax. All the variable it depends on are set above in this function. + window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; + window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); + window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); + window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); + + // Setup drawing context + // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) + window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffsetX = 0.0f; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.NavHideHighlightOneFrame = false; + window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f); + window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; + window->DC.NavLayerActiveMaskNext = 0x00; + window->DC.MenuBarAppending = false; + window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x); + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + window->DC.ChildWindows.resize(0); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + window->DC.ItemFlags = ImGuiItemFlags_Default_; + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemFlagsStack.resize(0); + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + window->DC.ColumnsSet = NULL; + window->DC.TreeDepth = 0; + window->DC.TreeDepthMayCloseOnPop = 0x00; + window->DC.StateStorage = &window->StateStorage; + window->DC.GroupStack.resize(0); + window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + + if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) + { + window->DC.ItemFlags = parent_window->DC.ItemFlags; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + } + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + { + FocusWindow(window); + NavInitWindow(window, false); + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + { + // Close & collapse button are on layer 1 (same as menus) and don't default focus + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + + // Collapse button + if (!(flags & ImGuiWindowFlags_NoCollapse)) + { + ImGuiID id = window->GetID("#COLLAPSE"); + ImRect bb(window->Pos + style.FramePadding + ImVec2(1,1), window->Pos + style.FramePadding + ImVec2(g.FontSize,g.FontSize) - ImVec2(1,1)); + ItemAdd(bb, id); // To allow navigation + if (ButtonBehavior(bb, id, NULL, NULL)) + window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function + RenderNavHighlight(bb, id); + RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + } + + // Close button + if (p_open != NULL) + { + const float PAD = 2.0f; + const float rad = (window->TitleBarHeight() - PAD*2.0f) * 0.5f; + if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-PAD - rad, PAD + rad), rad)) + *p_open = false; + } + + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.ItemFlags = item_flags_backup; + + // Title text (FIXME: refactor text alignment facilities along with RenderText helpers) + ImVec2 text_size = CalcTextSize(name, NULL, true); + ImRect text_r = title_bar_rect; + float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); + text_r.Min.x += pad_left; + text_r.Max.x -= pad_right; + ImRect clip_rect = text_r; + clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton() + RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); + } + + // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() + window->WindowRectClipped = window->Rect(); + window->WindowRectClipped.ClipWith(window->ClipRect); + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. + // Maybe we can support CTRL+C on every element? + /* + if (g.ActiveId == move_id) + if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + ImGui::LogToClipboard(); + */ + + // Inner rectangle + // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame + // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. + window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; + window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; + //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); + + // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.). + window->DC.LastItemId = window->MoveId; + window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; + window->DC.LastItemRect = title_bar_rect; + } + + // Inner clipping rectangle + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + const float border_size = window->WindowBorderSize; + ImRect clip_rect; + clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y); + clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y); + PushClipRect(clip_rect.Min, clip_rect.Max, true); + + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) + if (first_begin_of_the_frame) + window->WriteAccessed = false; + + window->BeginCount++; + g.NextWindowData.SizeConstraintCond = 0; + + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar). + if (flags & ImGuiWindowFlags_ChildWindow) + { + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + window->Collapsed = parent_window && parent_window->Collapsed; + + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y); + + // We also hide the window from rendering because we've already added its border to the command list. + // (we could perform the check earlier in the function but it is simpler at this point) + if (window->Collapsed) + window->Active = false; + } + if (style.Alpha <= 0.0f) + window->Active = false; + + // Return false if we don't intend to display anything to allow user to perform an early out optimization + window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0; + return !window->SkipItems; +} + +// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags) +{ + // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file. + if (size_first_use.x != 0.0f || size_first_use.y != 0.0f) + ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver); + + // Old API feature: override the window background alpha with a parameter. + if (bg_alpha_override >= 0.0f) + ImGui::SetNextWindowBgAlpha(bg_alpha_override); + + return ImGui::Begin(name, p_open, flags); +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +void ImGui::End() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + PopClipRect(); // Inner window clip rectangle + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + // Pop from window stack + g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.CurrentPopupStack.pop_back(); + CheckStacksSize(window, false); + SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); +} + +// Vertical scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +void ImGui::Scrollbar(ImGuiLayoutType direction) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const bool horizontal = (direction == ImGuiLayoutType_Horizontal); + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); + + // Render background + bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); + float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; + const ImRect window_rect = window->Rect(); + const float border_size = window->WindowBorderSize; + ImRect bb = horizontal + ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) + : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); + if (!horizontal) + bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); + if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) + return; + + int window_rounding_corners; + if (horizontal) + window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + else + window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); + bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); + float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; + float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; + float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); + const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + const bool previously_held = (g.ActiveId == id); + ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + + float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); + float scroll_ratio = ImSaturate(scroll_v / scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + if (held && grab_h_norm < 1.0f) + { + float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; + float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + SetHoveredID(id); + + bool seek_absolute = false; + if (!previously_held) + { + // On initial click calculate the distance between mouse and the center of the grab + if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) + { + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + else + { + seek_absolute = true; + *click_delta_to_grab_center_v = 0.0f; + } + } + + // Apply scroll + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position + const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); + scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); + if (horizontal) + window->Scroll.x = scroll_v; + else + window->Scroll.y = scroll_v; + + // Update values for rendering + scroll_ratio = ImSaturate(scroll_v / scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seeked and saturated + if (seek_absolute) + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + + // Render + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); + ImRect grab_rect; + if (horizontal) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); +} + +void ImGui::BringWindowToFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window + if (g.Windows[i] == window) + { + g.Windows.erase(g.Windows.Data + i); + g.Windows.push_back(window); + break; + } +} + +void ImGui::BringWindowToBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + if (g.NavWindow != window) + { + g.NavWindow = window; + if (window && g.NavDisableMouseHover) + g.NavMousePosDirty = true; + g.NavInitRequest = false; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavIdIsAlive = false; + g.NavLayer = 0; + } + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Move the root window to the top of the pile + if (window->RootWindow) + window = window->RootWindow; + + // Steal focus on active widgets + if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) + ClearActiveID(); + + // Bring to front + if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) + BringWindowToFront(window); +} + +void ImGui::FocusFrontMostActiveWindow(ImGuiWindow* ignore_window) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) + { + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]); + FocusWindow(focus_window); + return; + } +} + +void ImGui::PushItemWidth(float item_width) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = GImGui->Style; + if (w_full <= 0.0f) + w_full = CalcItemWidth(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components-1; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidthStack.pop_back(); + window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); +} + +float ImGui::CalcItemWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + float w = window->DC.ItemWidth; + if (w < 0.0f) + { + // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. + float width_to_right_edge = GetContentRegionAvail().x; + w = ImMax(1.0f, width_to_right_edge + w); + } + w = (float)(int)w; + return w; +} + +static ImFont* GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; +} + +void ImGui::SetCurrentFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; + g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + + ImFontAtlas* atlas = g.Font->ContainerAtlas; + g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (enabled) + window->DC.ItemFlags |= option; + else + window->DC.ItemFlags &= ~option; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); +} + +void ImGui::PopItemFlag() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); +} + +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPosStack.push_back(wrap_pos_x); +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPosStack.pop_back(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); +} + +// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 +void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiColMod& backup = g.ColorModifiers.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorModifiers.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo +{ + ImGuiDataType Type; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImGuiStyleVarInfo GStyleVarInfo[] = +{ + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_); + IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_Count_); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float) + { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float2) + { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiStyleMod& backup = g.StyleModifiers.back(); + const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); + if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr(&g.Style)) = backup.BackupFloat[0]; + else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr(&g.Style)) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); + else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr(&g.Style)) = backup.BackupInt[0]; + g.StyleModifiers.pop_back(); + count--; + } +} + +const char* ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) + { + case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; + case ImGuiCol_WindowBg: return "WindowBg"; + case ImGuiCol_ChildBg: return "ChildBg"; + case ImGuiCol_PopupBg: return "PopupBg"; + case ImGuiCol_Border: return "Border"; + case ImGuiCol_BorderShadow: return "BorderShadow"; + case ImGuiCol_FrameBg: return "FrameBg"; + case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: return "FrameBgActive"; + case ImGuiCol_TitleBg: return "TitleBg"; + case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_SliderGrab: return "SliderGrab"; + case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; + case ImGuiCol_Button: return "Button"; + case ImGuiCol_ButtonHovered: return "ButtonHovered"; + case ImGuiCol_ButtonActive: return "ButtonActive"; + case ImGuiCol_Header: return "Header"; + case ImGuiCol_HeaderHovered: return "HeaderHovered"; + case ImGuiCol_HeaderActive: return "HeaderActive"; + case ImGuiCol_Separator: return "Separator"; + case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: return "SeparatorActive"; + case ImGuiCol_ResizeGrip: return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_CloseButton: return "CloseButton"; + case ImGuiCol_CloseButtonHovered: return "CloseButtonHovered"; + case ImGuiCol_CloseButtonActive: return "CloseButtonActive"; + case ImGuiCol_PlotLines: return "PlotLines"; + case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening"; + case ImGuiCol_DragDropTarget: return "DragDropTarget"; + case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; + } + IM_ASSERT(0); + return "Unknown"; +} + +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) + { + if (window == potential_parent) + return true; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function + ImGuiContext& g = *GImGui; + + if (flags & ImGuiHoveredFlags_AnyWindow) + { + if (g.HoveredWindow == NULL) + return false; + } + else + { + switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) + { + case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: + if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_RootWindow: + if (g.HoveredWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_ChildWindows: + if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) + return false; + break; + default: + if (g.HoveredWindow != g.CurrentWindow) + return false; + break; + } + } + + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) + return false; + return true; +} + +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() + + if (flags & ImGuiFocusedFlags_AnyWindow) + return g.NavWindow != NULL; + + switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) + { + case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_RootWindow: + return g.NavWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); + default: + return g.NavWindow == g.CurrentWindow; + } +} + +// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + return window->Active && window == window->RootWindowForTabbing && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow); +} + +float ImGui::GetWindowWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.x; +} + +float ImGui::GetWindowHeight() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.y; +} + +ImVec2 ImGui::GetWindowPos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return window->Pos; +} + +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x) +{ + window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.x = new_scroll_x; + window->DC.CursorMaxPos.x -= window->Scroll.x; +} + +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) +{ + window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.y = new_scroll_y; + window->DC.CursorMaxPos.y -= window->Scroll.y; +} + +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowPosAllowFlags & cond) == 0) + return; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + + // Set + const ImVec2 old_pos = window->Pos; + window->PosFloat = pos; + window->Pos = ImFloor(pos); + window->DC.CursorPos += (window->Pos - old_pos); // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. +} + +void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + SetWindowPos(window, pos, cond); +} + +void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowPos(window, pos, cond); +} + +ImVec2 ImGui::GetWindowSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Size; +} + +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) + return; + window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + if (size.x > 0.0f) + { + window->AutoFitFramesX = 0; + window->SizeFull.x = size.x; + } + else + { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) + { + window->AutoFitFramesY = 0; + window->SizeFull.y = size.y; + } + else + { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) +{ + SetWindowSize(GImGui->CurrentWindow, size, cond); +} + +void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowSize(window, size, cond); +} + +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) + return; + window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + window->Collapsed = collapsed; +} + +void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); +} + +bool ImGui::IsWindowCollapsed() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; +} + +void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowCollapsed(window, collapsed, cond); +} + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.PosVal = pos; + g.NextWindowData.PosPivotVal = pivot; + g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeVal = size; + g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeConstraintCond = ImGuiCond_Always; + g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); + g.NextWindowData.SizeCallback = custom_callback; + g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; +} + +void ImGui::SetNextWindowContentSize(const ImVec2& size) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.ContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. + g.NextWindowData.ContentSizeCond = ImGuiCond_Always; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.CollapsedVal = collapsed; + g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +void ImGui::SetNextWindowBgAlpha(float alpha) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.BgAlphaVal = alpha; + g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +// In window space (not screen space!) +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImVec2 mx = window->ContentsRegionRect.Max; + if (window->DC.ColumnsSet) + mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); +} + +float ImGui::GetContentRegionAvailWidth() +{ + return GetContentRegionAvail().x; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetWindowContentRegionMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Min; +} + +ImVec2 ImGui::GetWindowContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max; +} + +float ImGui::GetWindowContentRegionWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x; +} + +float ImGui::GetTextLineHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize; +} + +float ImGui::GetTextLineHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.ItemSpacing.y; +} + +float ImGui::GetFrameHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + +float ImGui::GetFrameHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; +} + +ImDrawList* ImGui::GetWindowDrawList() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont* ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +void ImGui::SetWindowFontScale(float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->FontWindowScale = scale; + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. +// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. +ImVec2 ImGui::GetCursorPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos - window->Pos + window->Scroll; +} + +float ImGui::GetCursorPosX() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; +} + +float ImGui::GetCursorPosY() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; +} + +void ImGui::SetCursorPos(const ImVec2& local_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); +} + +void ImGui::SetCursorPosY(float y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); +} + +ImVec2 ImGui::GetCursorStartPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorStartPos - window->Pos; +} + +ImVec2 ImGui::GetCursorScreenPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos; +} + +void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = screen_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +float ImGui::GetScrollX() +{ + return GImGui->CurrentWindow->Scroll.x; +} + +float ImGui::GetScrollY() +{ + return GImGui->CurrentWindow->Scroll.y; +} + +float ImGui::GetScrollMaxX() +{ + return GetScrollMaxX(GImGui->CurrentWindow); +} + +float ImGui::GetScrollMaxY() +{ + return GetScrollMaxY(GImGui->CurrentWindow); +} + +void ImGui::SetScrollX(float scroll_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.x = scroll_x; + window->ScrollTargetCenterRatio.x = 0.0f; +} + +void ImGui::SetScrollY(float scroll_y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY + window->ScrollTargetCenterRatio.y = 0.0f; +} + +void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) +{ + // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); + window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); + window->ScrollTargetCenterRatio.y = center_y_ratio; + + // Minor hack to to make scrolling to top/bottom of window take account of WindowPadding, it looks more right to the user this way + if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y) + window->ScrollTarget.y = 0.0f; + else if (center_y_ratio >= 1.0f && window->ScrollTarget.y >= window->SizeContents.y - window->WindowPadding.y + GImGui->Style.ItemSpacing.y) + window->ScrollTarget.y = window->SizeContents.y; +} + +// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. +void ImGui::SetScrollHere(float center_y_ratio) +{ + ImGuiWindow* window = GetCurrentWindow(); + float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space + target_y += (window->DC.PrevLineHeight * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line. + SetScrollFromPosY(target_y, center_y_ratio); +} + +void ImGui::ActivateItem(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavNextActivateId = id; +} + +void ImGui::SetKeyboardFocusHere(int offset) +{ + IM_ASSERT(offset >= -1); // -1 is allowed but not below + ImGuiWindow* window = GetCurrentWindow(); + window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; + window->FocusIdxTabRequestNext = INT_MAX; +} + +void ImGui::SetItemDefaultFocus() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!window->Appearing) + return; + if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent) + { + g.NavInitRequest = false; + g.NavInitResultId = g.NavWindow->DC.LastItemId; + g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); + NavUpdateAnyRequestFlag(); + if (!IsItemVisible()) + SetScrollHere(); + } +} + +void ImGui::SetStateStorage(ImGuiStorage* tree) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.StateStorage = tree ? tree : &window->StateStorage; +} + +ImGuiStorage* ImGui::GetStateStorage() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.StateStorage; +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + TextUnformatted(g.TempBuffer, text_end); +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set + if (need_wrap) PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_wrap) PopTextWrapPos(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(text != NULL); + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = wrap_pos_x >= 0.0f; + if (text_end - text > 2000 && !wrap_enabled) + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + const char* line = text; + const float line_height = GetTextLineHeight(); + const ImRect clip_rect = window->ClipRect; + ImVec2 text_size(0,0); + + if (text_pos.y <= clip_rect.Max.y) + { + ImVec2 pos = text_pos; + + // Lines to skip (can't skip when logging text) + if (!g.LogEnabled) + { + int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (IsClippedEx(line_rect, 0, false)) + break; + + const ImVec2 line_size = CalcTextSize(line, line_end, false); + text_size.x = ImMax(text_size.x, line_size.x); + RenderText(pos, line, line_end, false); + if (!line_end) + line_end = text_end; + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + + text_size.y += (pos - text_pos).y; + } + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(bb); + ItemAdd(bb, 0); + } + else + { + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + // Account of baseline offset + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); + const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) ClearActiveID(); + return false; + } + + // Default behavior requires click+release on same spot + if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) + flags |= ImGuiButtonFlags_PressedOnClickRelease; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = window; + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && g.DragDropActive && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy + { + pressed = true; + FocusWindow(window); + } + } + + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = backup_hovered_window; + + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + // Mouse + if (hovered) + { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + { + // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat + // PressedOnClickRelease | * | .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds + // PressedOnClick | | .. + // PressedOnRelease | | .. (NOT on release) + // PressedOnDoubleClick | | .. + // FIXME-NAV: We don't honor those different behaviors. + if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) + { + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + FocusWindow(window); + } + if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveID) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) + { + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + pressed = true; + ClearActiveID(); + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) + pressed = true; + } + + if (pressed) + g.NavDisableHighlight = true; + } + + // Gamepad/Keyboard navigation + // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + hovered = true; + + if (g.NavActivateDownId == id) + { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); + if (nav_activated_by_code || nav_activated_by_inputs) + pressed = true; + if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + g.NavActivateId = id; // This is so SetActiveId assign a Nav source + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } + } + + bool held = false; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + if (g.IO.MouseDown[0]) + { + held = true; + } + else + { + if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + if (!g.DragDropActive) + pressed = true; + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + g.NavDisableHighlight = true; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + if (g.NavActivateDownId != id) + ClearActiveID(); + } + } + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, 0); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + return pressed; +} + +// Button to close a window +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. + // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); + bool is_clipped = !ItemAdd(bb, id); + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (is_clipped) + return pressed; + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton); + ImVec2 center = bb.GetCenter(); + window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12); + + const float cross_extent = (radius * 0.7071f) - 1.0f; + if (hovered) + { + center -= ImVec2(0.5f, 0.5f); + window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), GetColorU32(ImGuiCol_Text)); + window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), GetColorU32(ImGuiCol_Text)); + } + return pressed; +} + +// [Internal] +bool ImGui::ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + padding.x * 2.0f, g.FontSize + padding.y * 2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTriangle(bb.Min + padding, dir, 1.0f); + + return pressed; +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2,2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) + { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col)); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +// The color used are the button colors. +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. + PushID((void *)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2); + const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +// Start logging ImGui output to TTY +void ImGui::LogToTTY(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = stdout; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to given file +void ImGui::LogToFile(int max_depth, const char* filename) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + if (!filename) + { + filename = g.IO.LogFilename; + if (!filename) + return; + } + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = ImFileOpen(filename, "ab"); + if (!g.LogFile) + { + IM_ASSERT(g.LogFile != NULL); // Consider this an error + return; + } + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to clipboard +void ImGui::LogToClipboard(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = NULL; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +void ImGui::LogFinish() +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + if (g.LogFile != NULL) + { + if (g.LogFile == stdout) + fflush(g.LogFile); + else + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard->size() > 1) + { + SetClipboardText(g.LogClipboard->begin()); + g.LogClipboard->clear(); + } + g.LogEnabled = false; +} + +// Helper to display logging buttons +void ImGui::LogButtons() +{ + ImGuiContext& g = *GImGui; + + PushID("LogButtons"); + const bool log_to_tty = Button("Log To TTY"); SameLine(); + const bool log_to_file = Button("Log To File"); SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); + PushItemWidth(80.0f); + PushAllowKeyboardFocus(false); + SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); + PopAllowKeyboardFocus(); + PopItemWidth(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(g.LogAutoExpandMaxDepth); + if (log_to_file) + LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); + if (log_to_clipboard) + LogToClipboard(g.LogAutoExpandMaxDepth); +} + +bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions) + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.NextTreeNodeOpenCond != 0) + { + if (g.NextTreeNodeOpenCond & ImGuiCond_Always) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + g.NextTreeNodeOpenCond = 0; + } + else + { + is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) + is_open = true; + + return is_open; +} + +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + // We vertically grow up to current line height up the typical widget height. + const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); + ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); + if (display_frame) + { + // Framed header expand a little outside the default padding + frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; + frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; + } + + const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing + const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser + ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) + const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y); + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + + // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child. + // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). + // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero. + if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavCloseFromChild) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + window->DC.TreeDepthMayCloseOnPop |= (1 << window->DC.TreeDepth); + + bool item_add = ItemAdd(interact_bb, id); + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + window->DC.LastItemDisplayRect = frame_bb; + + if (!item_add) + { + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; + } + + // Flags that affects opening behavior: + // - 0(default) ..................... single-click anywhere to open + // - OpenOnDoubleClick .............. double-click anywhere to open + // - OpenOnArrow .................... single-click on arrow to open + // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open + ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); + + bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + { + bool toggled = false; + if (pressed) + { + toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id); + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover); + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + toggled |= g.IO.MouseDoubleClicked[0]; + if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = false; + } + + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) + { + toggled = true; + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavMoveRequestCancel(); + } + + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y); + if (display_frame) + { + // Framed type + RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + RenderTriangle(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + if (g.LogEnabled) + { + // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. + const char log_prefix[] = "\n##"; + const char log_suffix[] = "##"; + LogRenderedText(&text_pos, log_prefix, log_prefix+3); + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); + } + else + { + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + } + } + else + { + // Unframed typed for tree nodes + if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) + { + RenderFrame(frame_bb.Min, frame_bb.Max, col, false); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + } + + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); + else if (!(flags & ImGuiTreeNodeFlags_Leaf)) + RenderTriangle(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogRenderedText(&text_pos, ">"); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label); +} + +bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_open && !*p_open) + return false; + + ImGuiID id = window->GetID(label); + bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label); + if (p_open) + { + // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + ImGuiContext& g = *GImGui; + float button_sz = g.FontSize * 0.5f; + ImGuiItemHoveredDataBackup last_item_backup; + if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_sz, window->DC.LastItemRect.Min.y + g.Style.FramePadding.y + button_sz), button_sz)) + *p_open = false; + last_item_backup.Restore(); + } + + return is_open; +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, label, NULL); +} + +void ImGui::TreeAdvanceToLabelPos() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextTreeNodeOpenVal = is_open; + g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::PushID(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id)); +} + +void ImGui::PushID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id_begin, str_id_end)); +} + +void ImGui::PushID(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PushID(int int_id) +{ + const void* ptr_id = (void*)(intptr_t)int_id; + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PopID() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.pop_back(); +} + +ImGuiID ImGui::GetID(const char* str_id) +{ + return GImGui->CurrentWindow->GetID(str_id); +} + +ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) +{ + return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); +} + +ImGuiID ImGui::GetID(const void* ptr_id) +{ + return GImGui->CurrentWindow->GetID(ptr_id); +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x*2); + return; + } + + // Render and stay on same line + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + SameLine(0, style.FramePadding.x*2); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin = g.TempBuffer; + const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + // Render + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + ImFormatString(buf, buf_size, display_format, *(int*)data_ptr); + else if (data_type == ImGuiDataType_Float) + ImFormatString(buf, buf_size, display_format, *(float*)data_ptr); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%d", *(int*)data_ptr); + else + ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr); + } + else if (data_type == ImGuiDataType_Float) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%f", *(float*)data_ptr); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + else + ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr); + } +} + +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2)// Store into value1 +{ + if (data_type == ImGuiDataType_Int) + { + if (op == '+') + *(int*)value1 = *(int*)value1 + *(const int*)value2; + else if (op == '-') + *(int*)value1 = *(int*)value1 - *(const int*)value2; + } + else if (data_type == ImGuiDataType_Float) + { + if (op == '+') + *(float*)value1 = *(float*)value1 + *(const float*)value2; + else if (op == '-') + *(float*)value1 = *(float*)value1 - *(const float*)value2; + } +} + +// User can input math operators (e.g. +100) to edit a numerical values. +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format) +{ + while (ImCharIsSpace(*buf)) + buf++; + + // We don't support '-' op because it would conflict with inputing negative value. + // Instead you can use +-100 to subtract from an existing value + char op = buf[0]; + if (op == '+' || op == '*' || op == '/') + { + buf++; + while (ImCharIsSpace(*buf)) + buf++; + } + else + { + op = 0; + } + if (!buf[0]) + return false; + + if (data_type == ImGuiDataType_Int) + { + if (!scalar_format) + scalar_format = "%d"; + int* v = (int*)data_ptr; + const int old_v = *v; + int arg0i = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1) + return false; + + // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision + float arg1f = 0.0f; + if (op == '+') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i + arg1f); } // Add (use "+-" to subtract) + else if (op == '*') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i * arg1f); } // Multiply + else if (op == '/') { if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }// Divide + else { if (sscanf(buf, scalar_format, &arg0i) == 1) *v = arg0i; } // Assign constant (read as integer so big values are not lossy) + return (old_v != *v); + } + else if (data_type == ImGuiDataType_Float) + { + // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in + scalar_format = "%f"; + float* v = (float*)data_ptr; + const float old_v = *v; + float arg0f = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1) + return false; + + float arg1f = 0.0f; + if (sscanf(buf, scalar_format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + return (old_v != *v); + } + + return false; +} + +// Create text input in place of a slider (when CTRL+Clicking on slider) +// FIXME: Logic is messy and confusing. +bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) + // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id + SetActiveID(g.ScalarAsInputTextId, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetHoveredID(0); + FocusableItemUnregister(window); + + char buf[32]; + DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf)); + bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); + if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget + { + IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID (else we'd need to store them both, which is also possible) + g.ScalarAsInputTextId = g.ActiveId; + SetHoveredID(id); + } + if (text_value_changed) + return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL); + return false; +} + +// Parse display precision back from the display format string +int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) +{ + int precision = default_precision; + while ((fmt = strchr(fmt, '%')) != NULL) + { + fmt++; + if (fmt[0] == '%') { fmt++; continue; } // Ignore "%%" + while (*fmt >= '0' && *fmt <= '9') + fmt++; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 10) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + break; + } + return precision; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision); +} + +float ImGui::RoundScalar(float value, int decimal_precision) +{ + // Round past decimal precision + // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0 + // FIXME: Investigate better rounding methods + if (decimal_precision < 0) + return value; + const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision); + bool negative = value < 0.0f; + value = fabsf(value); + float remainder = fmodf(value, min_step); + if (remainder <= min_step*0.5f) + value -= remainder; + else + value += (min_step - remainder); + return negative ? -value : value; +} + +static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) +{ + if (v_min == v_max) + return 0.0f; + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_non_linear) + { + if (v_clamped < 0.0f) + { + const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f,v_max) - v_min); + return (1.0f - powf(f, 1.0f/power)) * linear_zero_pos; + } + else + { + const float f = (v_clamped - ImMax(0.0f,v_min)) / (v_max - ImMax(0.0f,v_min)); + return linear_zero_pos + powf(f, 1.0f/power) * (1.0f - linear_zero_pos); + } + } + + // Linear slider + return (v_clamped - v_min) / (v_max - v_min); +} + +bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32((g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Nav) ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; + + const float grab_padding = 2.0f; + const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); + float grab_sz; + if (decimal_precision != 0) + grab_sz = ImMin(style.GrabMinSize, slider_sz); + else + grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); // Integer sliders, if possible have the grab size represent 1 unit + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz*0.5f; + const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz*0.5f; + + // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f + float linear_zero_pos = 0.0f; // 0.0->1.0f + if (v_min * v_max < 0.0f) + { + // Different sign + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); + linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); + } + else + { + // Same sign + linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; + } + + // Process interacting with the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (!is_horizontal) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); + float delta = is_horizontal ? delta2.x : -delta2.y; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + { + ClearActiveID(); + } + else if (delta != 0.0f) + { + clicked_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (decimal_precision == 0 && !is_non_linear) + { + if (fabsf(v_max - v_min) <= 100.0f || IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (v_max - v_min); // Gamepad/keyboard tweak speeds in integer steps + else + delta /= 100.0f; + } + else + { + delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + if (IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta /= 10.0f; + } + if (IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= 10.0f; + set_new_value = true; + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits + set_new_value = false; + else + clicked_t = ImSaturate(clicked_t + delta); + } + } + + if (set_new_value) + { + float new_value; + if (is_non_linear) + { + // Account for logarithmic scale on both sides of the zero + if (clicked_t < linear_zero_pos) + { + // Negative: rescale to the negative range before powering + float a = 1.0f - (clicked_t / linear_zero_pos); + a = powf(a, power); + new_value = ImLerp(ImMin(v_max,0.0f), v_min, a); + } + else + { + // Positive: rescale to the positive range before powering + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f) + a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = clicked_t; + a = powf(a, power); + new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); + } + } + else + { + // Linear slider + new_value = ImLerp(v_min, v_max, clicked_t); + } + + // Round past decimal precision + new_value = RoundScalar(new_value, decimal_precision); + if (*v != new_value) + { + *v = new_value; + value_changed = true; + } + } + } + + // Draw + float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (!is_horizontal) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + ImRect grab_bb; + if (is_horizontal) + grab_bb = ImRect(ImVec2(grab_pos - grab_sz*0.5f, frame_bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz*0.5f, frame_bb.Max.y - grab_padding)); + else + grab_bb = ImRect(ImVec2(frame_bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f), ImVec2(frame_bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f)); + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + return value_changed; +} + +// Use power!=1.0 for logarithmic sliders. +// Adjust display_format to decorate the value with a prefix or a suffix. +// "%.3f" 1.234 +// "%5.2f secs" 01.23 secs +// "Gold: %.0f" Gold: 1 +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual slider behavior + render grab + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + + // Actual slider behavior + render grab + bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) +{ + float v_deg = (*v_rad) * 360.0f / (2*IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); + *v_rad = v_deg * (2*IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 2, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 3, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 4, v_min, v_max, display_format, power); +} + +bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 2, v_min, v_max, display_format); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 3, v_min, v_max, display_format); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 4, v_min, v_max, display_format); +} + +bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + bool value_changed = false; + + // Process interacting with the drag + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId == id) + { + if (g.ActiveIdIsJustActivated) + { + // Lock current value on click + g.DragCurrentValue = *v; + g.DragLastMouseDelta = ImVec2(0.f, 0.f); + } + + if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX) + v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio; + + float v_cur = g.DragCurrentValue; + const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f); + float adjust_delta = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid()) + { + adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x; + if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f) + adjust_delta *= g.DragSpeedScaleFast; + if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f) + adjust_delta *= g.DragSpeedScaleSlow; + g.DragLastMouseDelta.x = mouse_drag_delta.x; + } + if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; + if (v_min < v_max && ((v_cur >= v_max && adjust_delta > 0.0f) || (v_cur <= v_min && adjust_delta < 0.0f))) // This is to avoid applying the saturation when already past the limits + adjust_delta = 0.0f; + v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); + } + adjust_delta *= v_speed; + + if (fabsf(adjust_delta) > 0.0f) + { + if (fabsf(power - 1.0f) > 0.001f) + { + // Logarithmic curve on both side of 0.0 + float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur; + float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f; + float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign); + float v1_abs = v1 >= 0.0f ? v1 : -v1; + float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line + v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign + } + else + { + v_cur += adjust_delta; + } + + // Clamp + if (v_min < v_max) + v_cur = ImClamp(v_cur, v_min, v_max); + g.DragCurrentValue = v_cur; + } + + // Round to user desired precision, then apply + v_cur = RoundScalar(v_cur, decimal_precision); + if (*v != v_cur) + { + *v = v_cur; + value_changed = true; + } + } + + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Drag turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual drag behavior + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + return value_changed; +} + +bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* display_format, const char* display_format_max, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, display_format_max ? display_format_max : display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* display_format, const char* display_format_max) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, display_format_max ? display_format_max : display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + if (graph_size.x == 0.0f) + graph_size.x = CalcItemWidth(); + if (graph_size.y == 0.0f) + graph_size.y = label_size.y + (style.FramePadding.y * 2); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0, &frame_bb)) + return; + const bool hovered = ItemHoverable(inner_bb, 0); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + if (values_count > 0) + { + int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + int v_hovered = -1; + if (hovered) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + v_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); +} + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); + } + + if (!ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + *v = !(*v); + + RenderNavHighlight(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + if (*v) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + bool v = ((*flags & flags_value) == flags_value); + bool pressed = Checkbox(label, &v); + if (pressed) + { + if (v) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + + return pressed; +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb.Add(text_bb); + } + + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = (float)(int)center.x + 0.5f; + center.y = (float)(int)center.y + 0.5f; + const float radius = check_bb.GetHeight() * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + + RenderNavHighlight(total_bb, id); + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); + if (active) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + { + *v = v_button; + } + return pressed; +} + +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImFont* font = GImGui->Font; + const float line_height = GImGui->FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((unsigned short)c) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +namespace ImGuiStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) +{ + const ImWchar* text = obj->Text.Data; + const ImWchar* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool is_separator(unsigned int c) { return ImCharIsSpace(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->Text[idx-1] ) && !is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +#ifdef __APPLE__ // FIXME: Move setting to IO structure +static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +#else +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +#endif +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) +{ + ImWchar* dst = obj->Text.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text + const ImWchar* src = obj->Text.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) +{ + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + if (new_text_len + text_len + 1 > obj->Text.Size) + return false; + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA) + return false; + + ImWchar* text = obj->Text.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->Text[obj->CurLenW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_SHIFT 0x20000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + +} + +void ImGuiTextEditState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &StbState, key); + CursorFollow = true; + CursorAnimReset(); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos + bytes_count >= pos) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen + 1 >= BufSize) + return; + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + unsigned int c = *p_char; + + if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } + + if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. + return false; + + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank)) + { + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + *p_char = (c += (unsigned int)('A'-'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsSpace(c)) + return false; + } + + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Edit a string of text +// NB: when active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while active has no effect. +// FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188 +bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + + ImGuiContext& g = *GImGui; + const ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + + if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); + + ImGuiWindow* draw_window = window; + if (is_multiline) + { + ItemAdd(total_bb, id, &frame_bb); + if (!BeginChildFrame(id, frame_bb.GetSize())) + { + EndChildFrame(); + EndGroup(); + return false; + } + draw_window = GetCurrentWindow(); + size.x -= draw_window->ScrollbarSizes.x; + } + else + { + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // Password pushes a temporary font with only a fallback glyph + if (is_password) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->DisplayOffset = g.Font->DisplayOffset; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // NB: we are only allowed to access 'edit_state' if we are the active widget. + ImGuiTextEditState& edit_state = g.InputTextState; + + const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); + const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + + bool clear_active_id = false; + + bool select_all = (g.ActiveId != id) && (((flags & ImGuiInputTextFlags_AutoSelectAll) != 0) || (g.NavInputId == id)) && (!is_multiline); + if (focus_requested || user_clicked || user_scrolled || g.NavInputId == id) + { + if (g.ActiveId != id) + { + // Start edition + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int prev_len_w = edit_state.CurLenW; + edit_state.Text.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + edit_state.InitialText.resize(buf_size+1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + edit_state.CursorAnimReset(); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). + const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW); + if (recycle_state) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + edit_state.CursorClamp(); + } + else + { + edit_state.Id = id; + edit_state.ScrollX = 0.0f; + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); + if (!is_multiline && focus_requested_by_code) + select_all = true; + } + if (flags & ImGuiInputTextFlags_AlwaysInsertMode) + edit_state.StbState.insert_mode = true; + if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + select_all = true; + } + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)); + } + else if (io.MouseClicked[0]) + { + // Release focus when we click outside + clear_active_id = true; + } + + bool value_changed = false; + bool enter_pressed = false; + + if (g.ActiveId == id) + { + if (!is_editable && !g.ActiveIdIsJustActivated) + { + // When read-only we always use the live data passed to the function + edit_state.Text.resize(buf_size+1); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); + edit_state.CursorClamp(); + } + + edit_state.BufSizeA = buf_size; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); + + const bool osx_double_click_selects_words = io.OptMacOSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text + if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0])) + { + edit_state.SelectAll(); + edit_state.SelectedAllMouseLock = true; + } + else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0]) + { + // Select a word only, OS X style (by simulating keystrokes) + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) + { + if (hovered) + { + stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + } + } + else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + edit_state.CursorFollow = true; + } + if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) + edit_state.SelectedAllMouseLock = false; + + if (io.InputCharacters[0]) + { + // Process text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow CTRL+ALT as some keyboards (e.g. German) use AltGR - which is Alt+Ctrl - to input certain characters. + if (!(io.KeyCtrl && !io.KeyAlt) && is_editable) + { + for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) + if (unsigned int c = (unsigned int)io.InputCharacters[n]) + { + // Insert character if they pass filtering + if (!InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + edit_state.OnKeyPressed((int)c); + } + } + + // Consume characters + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + } + } + + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + // Handle key-presses + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_shortcut_key_only = (io.OptMacOSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl + const bool is_wordmove_key_down = io.OptMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = io.OptMacOSXBehaviors && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; + const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; + + const bool is_cut = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_copy = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_paste = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; + + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) + { + if (!edit_state.HasSelection()) + { + if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); + else if (io.OptMacOSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); + } + edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (IsKeyPressedMap(ImGuiKey_Enter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + enter_pressed = clear_active_id = true; + } + else if (is_editable) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } + else if (is_cut || is_copy) + { + // Cut, Copy + if (io.SetClipboardTextFn) + { + const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; + const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; + edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie); + SetClipboardText(edit_state.TempTextBuffer.Data); + } + + if (is_cut) + { + if (!edit_state.HasSelection()) + edit_state.SelectAll(); + edit_state.CursorFollow = true; + stb_textedit_cut(&edit_state, &edit_state.StbState); + } + } + else if (is_paste) + { + // Paste + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s; ) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) + break; + if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + edit_state.CursorFollow = true; + } + ImGui::MemFree(clipboard_filtered); + } + } + } + + if (g.ActiveId == id) + { + if (cancel_edit) + { + // Restore initial value + if (is_editable) + { + ImStrncpy(buf, edit_state.InitialText.Data, buf_size); + value_changed = true; + } + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + if (is_editable) + { + edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL); + } + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_COUNT; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + event_flag = ImGuiInputTextFlags_CallbackAlways; + + if (event_flag) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = user_data; + callback_data.ReadOnly = !is_editable; + + callback_data.EventKey = event_key; + callback_data.Buf = edit_state.TempTextBuffer.Data; + callback_data.BufTextLen = edit_state.CurLenA; + callback_data.BufSize = edit_state.BufSizeA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) + ImWchar* text = edit_state.Text.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA); + IM_ASSERT(callback_data.Flags == flags); + if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); + if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); + if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); + if (callback_data.BufDirty) + { + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL); + edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + edit_state.CursorAnimReset(); + } + } + } + + // Copy back to user buffer + if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0) + { + ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size); + value_changed = true; + } + } + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + + // Render + // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL; + + RenderNavHighlight(frame_bb, id); + if (!is_multiline) + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.f, 0.f); + const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); + if (g.ActiveId == id || is_currently_scrolling) + { + edit_state.CursorAnim += io.DeltaTime; + + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar* text_begin = edit_state.Text.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. + const ImWchar* searches_input_ptr[2]; + searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; + searches_input_ptr[1] = NULL; + int searches_remaining = 1; + int searches_result_line_number[2] = { -1, -999 }; + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + searches_result_line_number[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + for (const ImWchar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; + if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_number[0] * g.FontSize; + if (searches_result_line_number[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_number[1] * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(size.x, line_count * g.FontSize); + } + + // Scroll + if (edit_state.CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = size.x * 0.25f; + if (cursor_offset.x < edit_state.ScrollX) + edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= edit_state.ScrollX) + edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); + } + else + { + edit_state.ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + float scroll_y = draw_window->Scroll.y; + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - size.y >= scroll_y) + scroll_y = cursor_offset.y - size.y; + draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag + draw_window->Scroll.y = scroll_y; + render_pos.y = draw_window->DC.CursorPos.y; + } + } + edit_state.CursorFollow = false; + const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); + + // Draw selection + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); + + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); + ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = render_pos.x - render_scroll.x; + rect_pos.y += g.FontSize; + } + } + + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect); + + // Draw blinking cursor + bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (is_editable) + g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + } + else + { + // Render text only + const char* buf_end = NULL; + if (is_multiline) + text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + if (is_multiline) + { + Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line + EndChildFrame(); + EndGroup(); + } + + if (is_password) + PopFont(); + + // Log as text + if (g.LogEnabled && !is_password) + LogRenderedText(&render_pos, buf_display, NULL); + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +// NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument) +bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + BeginGroup(); + PushID(label); + const ImVec2 button_sz = ImVec2(GetFrameHeight(), GetFrameHeight()); + if (step_ptr) + PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2)); + + char buf[64]; + DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf)); + + bool value_changed = false; + if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal)) + extra_flags |= ImGuiInputTextFlags_CharsDecimal; + extra_flags |= ImGuiInputTextFlags_AutoSelectAll; + if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format); + + // Step buttons + if (step_ptr) + { + PopItemWidth(); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + } + PopID(); + + if (label_size.x > 0) + { + SameLine(0, style.ItemInnerSpacing.x); + RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); + ItemSize(label_size, style.FramePadding.y); + } + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char display_format[16]; + if (decimal_precision < 0) + strcpy(display_format, "%f"); // Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1 + else + ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision); + return InputScalarEx(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), display_format, extra_flags); +} + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), scalar_format, extra_flags); +} + +bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 2, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 3, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 4, decimal_precision, extra_flags); +} + +bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 2, extra_flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 3, extra_flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 4, extra_flags); +} + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + // Always consume the SetNextWindowSizeConstraint() call in our early return paths + ImGuiContext& g = *GImGui; + ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond; + g.NextWindowData.SizeConstraintCond = 0; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + bool popup_open = IsPopupOpen(id); + + const float arrow_size = GetFrameHeight(); + const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING + RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); + if (preview_value != NULL) + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((pressed || g.NavActivateId == id) && !popup_open) + { + if (window->DC.NavLayerCurrent == 0) + window->NavLastIds[0] = id; + OpenPopupEx(id); + popup_open = true; + } + + if (!popup_open) + return false; + + if (backup_next_window_size_constraint) + { + g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint; + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + } + + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + + // Peak into expected window size so we can position it + if (ImGuiWindow* popup_window = FindWindowByName(name)) + if (popup_window->WasActive) + { + ImVec2 size_contents = CalcSizeContents(popup_window); + ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents)); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; + ImVec2 pos = FindBestWindowPosForPopup(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + if (!Begin(name, NULL, window_flags)) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + + // Horizontally align ourselves with the framed text + if (style.FramePadding.x != style.WindowPadding.x) + Indent(style.FramePadding.x - style.WindowPadding.x); + + return true; +} + +void ImGui::EndCombo() +{ + const ImGuiStyle& style = GImGui->Style; + if (style.FramePadding.x != style.WindowPadding.x) + Unindent(style.FramePadding.x - style.WindowPadding.x); + EndPopup(); +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + const char* preview_text = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_text); + + // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here. + if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond) + { + float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, popup_max_height)); + } + + if (!BeginCombo(label, preview_text, 0)) + return false; + + // Display items + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + return value_changed; +} + +static bool Items_ArrayGetter(void* data, int idx, const char** out_text) +{ + const char* const* items = (const char* const*)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string. +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped. + PopClipRect(); + + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrentLineTextBaseOffset; + ImRect bb(pos, pos + size); + ItemSize(bb); + + // Fill horizontal space. + ImVec2 window_padding = window->WindowPadding; + float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; + float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); + ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y); + ImRect bb_with_spacing(pos, pos + size_draw); + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) + bb_with_spacing.Max.x += window_padding.x; + + // Selectables are tightly packed together, we extend the box to cover spacing between selectable. + float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); + float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); + float spacing_R = style.ItemSpacing.x - spacing_L; + float spacing_D = style.ItemSpacing.y - spacing_U; + bb_with_spacing.Min.x -= spacing_L; + bb_with_spacing.Min.y -= spacing_U; + bb_with_spacing.Max.x += spacing_R; + bb_with_spacing.Max.y += spacing_D; + if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id)) + { + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + PushColumnClipRect(); + return false; + } + + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID; + if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease; + if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; + if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + bool hovered, held; + bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags); + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; + + // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets) + if (pressed || hovered)// && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)) + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerActiveMask) + { + g.NavDisableHighlight = true; + SetNavID(id, window->DC.NavLayerCurrent); + } + + // Render + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f); + RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + { + PushColumnClipRect(); + bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x); + } + + if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); + if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + return pressed; +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + +// Helper to calculate the size of a listbox and display a label on the right. +// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" +bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = GetStyle(); + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. + + BeginGroup(); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) +{ + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + // However we don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. + // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); + + // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; + return ListBoxHeader(label, size); +} + +void ImGui::ListBoxFooter() +{ + ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; + const ImRect bb = parent_window->DC.LastItemRect; + const ImGuiStyle& style = GetStyle(); + + EndChildFrame(); + + // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) + // We call SameLine() to restore DC.CurrentLine* data + SameLine(); + parent_window->DC.CursorPos = bb.Min; + ItemSize(bb, style.FramePadding.y); + EndGroup(); +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + if (!ListBoxHeader(label, items_count, height_in_items)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. + bool value_changed = false; + ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + ListBoxFooter(); + return value_changed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation we render neither the shortcut neither the selected tick mark + float w = label_size.x; + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); + if (shortcut_size.x > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + SetNextWindowPos(ImVec2(0.0f, 0.0f)); + SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f)); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); + if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar) + || !BeginMenuBar()) + { + End(); + PopStyleVar(2); + return false; + } + g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x; + return true; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + ImGuiContext& g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) + FocusFrontMostActiveWindow(g.NavWindow); + + End(); + PopStyleVar(2); +} + +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Save position + PushID("##menubar"); + + // We don't clip with regular window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); + clip_rect.ClipWith(window->WindowRectClipped); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y);// + g.Style.FramePadding.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. + if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + { + ImGuiWindow* nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) + { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) + IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check + FocusWindow(window); + SetNavIDAndMoveMouse(window->NavLastIds[1], 1, window->NavRectRel[1]); + g.NavLayer = 1; + g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + } + } + + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x; + window->DC.GroupStack.back().AdvanceCursor = false; + EndGroup(); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.MenuBarAppending = false; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + + bool pressed; + bool menu_is_open = IsPopupOpen(id); + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) + g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos). + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestPopupWindowPos() in Begin() + popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + float w = label_size.x; + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a menu + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); + if (!enabled) PopStyleColor(); + } + + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) + g.NavWindow = backed_nav_window; + + bool want_open = false, want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_within_opened_triangle = false; + if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar)) + { + if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) + { + ImRect next_window_rect = next_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug + } + } + + want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); + want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); + + if (g.NavActivateId == id) + { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id)) + ClosePopupToLevel(g.CurrentPopupStack.Size); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) + OpenPopup(label); + + if (menu_is_open) + { + SetNextWindowPos(popup_pos, ImGuiCond_Always); + ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + + return menu_is_open; +} + +void ImGui::EndMenu() +{ + // Nav: When a left move request _within our child menu_ failed, close the menu. + // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. + // However it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + NavMoveRequestCancel(); + } + + EndPopup(); +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + BeginTooltipEx(0, true); + + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextUnformatted(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + EndTooltip(); +} + +static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) +{ + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); + return IM_COL32(r, g, b, 0xFF); +} + +// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. +// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether. +void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) + { + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col)); + window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); + + int yi = 0; + for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) + { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + int rounding_corners_flags_cell = 0; + if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } + rounding_corners_flags_cell &= rounding_corners_flags; + window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); + } + } + } + else + { + window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); + } +} + +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags__InputsMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask; + if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; + if ((flags & ImGuiColorEditFlags__PickerMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// A little colored square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + float default_size = GetFrameHeight(); + if (size.x == 0.0f) + size.x = default_size; + if (size.y == 0.0f) + size.y = default_size; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) + { + float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); + RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); + } + RenderNavHighlight(bb, id); + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + + // Drag and Drop Source + if (g.ActiveId == id && BeginDragDropSource()) // NB: The ActiveId test is merely an optional micro-optimization + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextUnformatted("Color"); + EndDragDropSource(); + hovered = false; + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; + if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1,0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + if (flags & ImGuiColorEditFlags_NoAlpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); +} + +static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + ImGui::PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) ImGui::Separator(); + ImGui::PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); + if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + ImGui::SetCursorScreenPos(backup_pos); + ImVec4 dummy_ref_col; + memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4)); + ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) ImGui::Separator(); + ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + ImGui::EndPopup(); +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_items_all = CalcItemWidth() - w_extra; + const char* label_display_end = FindRenderedTextEnd(label); + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + const ImGuiColorEditFlags flags_untouched = flags; + + BeginGroup(); + PushID(label); + + // If we're not showing any slider there's no point in doing any HSV conversions + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__InputsMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask); + if (!(flags & ImGuiColorEditFlags__DataTypeMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask)); + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + + const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + const char* fmt_table_int[3][4] = + { + { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, // Short display + { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, // Long display for RGBA + { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } // Long display for HSVA + }; + const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1; + + PushItemWidth(w_item_one); + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + if (n + 1 == components) + PushItemWidth(w_item_last); + if (flags & ImGuiColorEditFlags_Float) + value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + else + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + PopItemWidth(); + PopItemWidth(); + } + else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255)); + PushItemWidth(w_items_all); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsSpace(*p)) + p++; + i[0] = i[1] = i[2] = i[3] = 0; + if (alpha) + sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + PopItemWidth(); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + if (!(flags & ImGuiColorEditFlags_NoInputs)) + SameLine(0, style.ItemInnerSpacing.x); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + if (BeginPopup("picker")) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextUnformatted(label, label_display_end); + Separator(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemWidth(); + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + + // Convert back + if (picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + if (value_changed) + { + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + } + + PopID(); + EndGroup(); + + // Drag and Drop Target + if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && BeginDragDropTarget()) // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); + value_changed = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = true; + } + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +// 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. +static void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) +{ + switch (direction) + { + case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_None: case ImGuiDir_Count_: break; // Fix warnings + } +} + +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) +{ + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); +} + +// ColorPicker +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImDrawList* draw_list = window->DrawList; + + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + PushID(label); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(flags, col); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H,S,V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V); + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + PushItemFlag(ImGuiItemFlags_NoNav, true); + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1)) + { + // Interactive with Hue wheel + H = atan2f(current_off.y, current_off.x) / IM_PI*0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = cosf(-H * 2.0f * IM_PI); + float sin_hue_angle = sinf(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); + + // R,G,B and H,S,V slider color editor + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB); + if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV); + if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit), if any + if (value_changed) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f)); + + const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) }; + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list->VtxBuffer.Data + vert_start_idx, draw_list->VtxBuffer.Data + vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = cosf(H * 2.0f * IM_PI); + float sin_hue_angle = sinf(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE); + draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS); + draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK); + draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS); + draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]); + float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + EndGroup(); + PopID(); + + return value_changed && memcmp(backup_initial_col, col, components * sizeof(float)); +} + +// Horizontal separating line. +void ImGui::Separator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + ImGuiWindowFlags flags = 0; + if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0) + flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected + if (flags & ImGuiSeparatorFlags_Vertical) + { + VerticalSeparator(); + return; + } + + // Horizontal Separator + if (window->DC.ColumnsSet) + PopClipRect(); + + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + if (!window->DC.GroupStack.empty()) + x1 += window->DC.IndentX; + + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); + ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. + if (!ItemAdd(bb, 0)) + { + if (window->DC.ColumnsSet) + PushColumnClipRect(); + return; + } + + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); + + if (g.LogEnabled) + LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); + + if (window->DC.ColumnsSet) + { + PushColumnClipRect(); + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; + } +} + +void ImGui::VerticalSeparator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); + ItemSize(ImVec2(bb.GetWidth(), 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); +} + +bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool item_add = ItemAdd(bb, id); + window->DC.ItemFlags = item_flags_backup; + if (!item_add) + return false; + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + + if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // Minimum pane size + if (mouse_delta < min_size1 - *size1) + mouse_delta = min_size1 - *size1; + if (mouse_delta > *size2 - min_size2) + mouse_delta = *size2 - min_size2; + + // Apply resize + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + } + + // Render + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding); + + return held; +} + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0,0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + ItemAdd(bb, 0); +} + +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) +void ImGui::BeginGroup() +{ + ImGuiWindow* window = GetCurrentWindow(); + + window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndentX = window->DC.IndentX; + group_data.BackupGroupOffsetX = window->DC.GroupOffsetX; + group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight; + group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; + group_data.BackupLogLinePosY = window->DC.LogLinePosY; + group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive; + group_data.AdvanceCursor = true; + + window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX; + window->DC.IndentX = window->DC.GroupOffsetX; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.CurrentLineHeight = 0.0f; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; +} + +void ImGui::EndGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls + + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + + ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); + group_bb.Max = ImMax(group_bb.Min, group_bb.Max); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight; + window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; + window->DC.IndentX = group_data.BackupIndentX; + window->DC.GroupOffsetX = group_data.BackupGroupOffsetX; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + + if (group_data.AdvanceCursor) + { + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); + ItemAdd(group_bb, 0); + } + + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context. + const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); + if (active_id_within_group) + window->DC.LastItemId = g.ActiveId; + window->DC.LastItemRect = group_bb; + + window->DC.GroupStack.pop_back(); + + //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] +} + +// Gets back to previous line and continue with horizontal layout +// pos_x == 0 : follow right after previous item +// pos_x != 0 : align to specified x position (relative to window/group left) +// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float pos_x, float spacing_w) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + if (pos_x != 0.0f) + { + if (spacing_w < 0.0f) spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + else + { + if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrentLineHeight = window->DC.PrevLineHeight; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + if (window->DC.CurrentLineHeight > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0,0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.ColumnsSet == NULL) + return; + + ImGuiContext& g = *GImGui; + PopItemWidth(); + PopClipRect(); + + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + if (++columns->Current < columns->Count) + { + // Columns 1+ cancel out IndentX + window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(columns->Current); + } + else + { + window->DC.ColumnsOffsetX = 0.0f; + window->DrawList->ChannelsSetCurrent(0); + columns->Current = 0; + columns->CellMinY = columns->CellMaxY; + } + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + window->DC.CursorPos.y = columns->CellMinY; + window->DC.CurrentLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = 0.0f; + + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; +} + +static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) +{ + return offset_norm * (columns->MaxX - columns->MinX); +} + +static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) +{ + return offset / (columns->MaxX - columns->MinX); +} + +static inline float GetColumnsRectHalfWidth() { return 4.0f; } + +static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + /* + if (g.ActiveId) + { + ImGuiContext& g = *GImGui; + const ImGuiID column_id = columns->ColumnsSetId + ImGuiID(column_index); + if (g.ActiveId == column_id) + return GetDraggedColumnOffset(columns, column_index); + } + */ + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return OffsetNormToPixels(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + if (column_index < 0) + column_index = columns->Current; + + PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); +} + +static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +{ + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiColumnsSet()); + ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count > 1); + IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + // Acquire storage for the columns set + ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.ColumnsSet = columns; + + // Set state for first column + const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->Size.x -window->ScrollbarSizes.x); + columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range + //column->MaxX = content_region_width - window->Scroll.x - ((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x; + columns->MaxX = content_region_width - window->Scroll.x; + columns->StartPosY = window->DC.CursorPos.y; + columns->StartMaxPosX = window->DC.CursorMaxPos.x; + columns->CellMinY = columns->CellMaxY = window->DC.CursorPos.y; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize defaults + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count + 1; n++) + { + // Clamp position + ImGuiColumnData* column = &columns->Columns[n]; + float t = column->OffsetNorm; + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + t = ImMin(t, PixelsToOffsetNorm(columns, (columns->MaxX - columns->MinX) - g.Style.ColumnsMinSpacing * (columns->Count - n))); + column->OffsetNorm = t; + + if (n == columns_count) + continue; + + // Compute clipping rectangle + float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); + float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWith(window->ClipRect); + } + + window->DrawList->ChannelsSplit(columns->Count); + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + PopClipRect(); + window->DrawList->ChannelsMerge(); + + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->CellMaxY; + if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = ImMax(columns->StartMaxPosX, columns->MaxX); // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + bool is_being_resized = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) + { + const float y1 = columns->StartPosY; + const float y2 = window->DC.CursorPos.y; + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction + const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) + { + ButtonBehavior(column_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) + dragging_column = n; + } + + // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->DC.ColumnsSet = NULL; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); +} + +// [2017/12: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count != columns_count) + EndColumns(); + + ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); + //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +void ImGui::Indent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::Unindent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id ? str_id : "#TreePush"); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); +} + +void ImGui::TreePushRawID(ImGuiID id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + window->IDStack.push_back(id); +} + +void ImGui::TreePop() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Unindent(); + + window->DC.TreeDepth--; + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + if (g.NavIdIsAlive && (window->DC.TreeDepthMayCloseOnPop & (1 << window->DC.TreeDepth))) + { + SetNavID(window->IDStack.back(), g.NavLayer); + NavMoveRequestCancel(); + } + window->DC.TreeDepthMayCloseOnPop &= (1 << window->DC.TreeDepth) - 1; + + PopID(); +} + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//----------------------------------------------------------------------------- +// DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; +} + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags, int mouse_button) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case + return false; + if (g.IO.MouseDown[mouse_button] == false) + return false; + + if (source_id == 0) + { + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; + if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + if (is_hovered) + SetHoveredID(source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + } + else + { + window = NULL; + source_id = ImHash("#SourceExtern", 0); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // FIXME-DRAG + //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :( + SetNextWindowPos(g.IO.MousePos); + PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); + BeginTooltip(); + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + EndTooltip(); + PopStyleColor(); + //PopStyleVar(); + } + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 12 characters long"); + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy((void*)payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy((void*)payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + + const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(display_rect); + if (g.DragDropPayload.SourceId == id) + return false; + + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface < g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAG: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.DragDropActive); +} + +//----------------------------------------------------------------------------- +// PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#ifndef __MINGW32__ +#include +#else +#include +#endif +#endif + +// Win32 API clipboard implementation +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#endif + +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + static ImVector buf_local; + buf_local.clear(); + if (!OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return NULL; + } + if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle)) + { + int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; + buf_local.resize(buf_len); + ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); + } + GlobalUnlock(wbuf_handle); + CloseClipboard(); + return buf_local.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!OpenClipboard(NULL)) + return; + const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; + HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return; + } + ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle); + ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); + GlobalUnlock(wbuf_handle); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, wbuf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin(); +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + ImGuiContext& g = *GImGui; + g.PrivateClipboard.clear(); + const char* text_end = text + strlen(text); + g.PrivateClipboard.resize((int)(text_end - text) + 1); + memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); + g.PrivateClipboard[(int)(text_end - text)] = 0; +} + +#endif + +// Win32 API IME support (for Asian languages, etc.) +#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + +#include +#ifdef _MSC_VER +#pragma comment(lib, "imm32") +#endif + +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +{ + // Notify OS Input Method Editor of text input position + if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) + if (HIMC himc = ImmGetContext(hwnd)) + { + COMPOSITIONFORM cf; + cf.ptCurrentPos.x = x; + cf.ptCurrentPos.y = y; + cf.dwStyle = CFS_FORCE_POSITION; + ImmSetCompositionWindow(himc, &cf); + } +} + +#else + +static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + +#endif + +//----------------------------------------------------------------------------- +// HELP +//----------------------------------------------------------------------------- + +void ImGui::ShowMetricsWindow(bool* p_open) +{ + if (ImGui::Begin("ImGui Metrics", p_open)) + { + ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3); + ImGui::Text("%d allocations", (int)GImAllocatorActiveAllocationsCount); + static bool show_clip_rects = true; + ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_clip_rects); + ImGui::Separator(); + + struct Funcs + { + static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label) + { + bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); + if (draw_list == ImGui::GetWindowDrawList()) + { + ImGui::SameLine(); + ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) ImGui::TreePop(); + return; + } + + ImDrawList* overlay_draw_list = ImGui::GetOverlayDrawList(); // Render additional visuals into the top-most draw list + if (window && ImGui::IsItemHovered()) + overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!node_open) + return; + + int elem_offset = 0; + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; + if (pcmd->UserCallback) + { + ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + if (show_clip_rects && ImGui::IsItemHovered()) + { + ImRect clip_rect = pcmd->ClipRect; + ImRect vtxs_rect; + for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) + vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); + clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); + vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); + } + if (!pcmd_node_open) + continue; + + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + { + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangles_pos[3]; + for (int n = 0; n < 3; n++, vtx_i++) + { + ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; + triangles_pos[n] = v.pos; + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + } + ImGui::Selectable(buf, false); + if (ImGui::IsItemHovered()) + { + ImDrawListFlags backup_flags = overlay_draw_list->Flags; + overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. + overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); + overlay_draw_list->Flags = backup_flags; + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + static void NodeWindows(ImVector& windows, const char* label) + { + if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) + return; + for (int i = 0; i < windows.Size; i++) + Funcs::NodeWindow(windows[i], "Window"); + ImGui::TreePop(); + } + + static void NodeWindow(ImGuiWindow* window, const char* label) + { + if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) + return; + ImGuiWindowFlags flags = window->Flags; + NodeDrawList(window, window->DrawList, "DrawList"); + ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s..)", flags, + (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", + (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : ""); + ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window)); + ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed); + ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask); + ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + if (window->NavRectRel[0].IsFinite()) + ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); + else + ImGui::BulletText("NavRectRel[0]: "); + if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); + if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); + ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); + ImGui::TreePop(); + } + }; + + // Access private state, we are going to display the draw lists from last frame + ImGuiContext& g = *GImGui; + Funcs::NodeWindows(g.Windows, "Windows"); + if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size)) + { + for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++) + Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) + { + for (int i = 0; i < g.OpenPopupStack.Size; i++) + { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Internal state")) + { + const char* input_source_names[] = { "None", "Mouse", "Nav", "NavGamepad", "NavKeyboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_Count_); + ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec)", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, input_source_names[g.ActiveIdSource]); + ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); + ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + ImGui::TreePop(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- + +// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. +// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. +#ifdef IMGUI_INCLUDE_IMGUI_USER_INL +#include "imgui_user.inl" +#endif + +//----------------------------------------------------------------------------- diff --git a/apps/bench_shared_offscreen/imgui/imgui.h b/apps/bench_shared_offscreen/imgui/imgui.h new file mode 100644 index 00000000..7629e06c --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui.h @@ -0,0 +1,1787 @@ +// dear imgui, v1.60 WIP +// (headers) + +// See imgui.cpp file for documentation. +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui + +#pragma once + +// User-editable configuration files (edit stock imconfig.h or define IMGUI_USER_CONFIG to your own filename) +#ifdef IMGUI_USER_CONFIG +#include IMGUI_USER_CONFIG +#endif +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" +#endif + +#include // FLT_MAX +#include // va_list +#include // ptrdiff_t, NULL +#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + +#define IMGUI_VERSION "1.60 WIP" + +// Define attributes of all API symbols declarations, e.g. for DLL under Windows. +#ifndef IMGUI_API +#define IMGUI_API +#endif + +// Define assertion handler. +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) +#endif + +// Helpers +// Some compilers support applying printf-style warnings to user functions. +#if defined(__clang__) || defined(__GNUC__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) +#define IM_OFFSETOF(_TYPE,_MEMBER) ((size_t)&(((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in modern C++. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +// Forward declarations +struct ImDrawChannel; // Temporary storage for outputting drawing commands out of order, used by ImDrawList::ChannelsSplit() +struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call) +struct ImDrawData; // All draw command lists required to render the frame +struct ImDrawList; // A single draw command list (generally one per window) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawVert; // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) +struct ImFont; // Runtime data for a single font within a parent ImFontAtlas +struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader +struct ImFontConfig; // Configuration data when adding a font or merging fonts +struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiOnceUponAFrame; // Simple helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro +struct ImGuiStorage; // Simple custom key value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTextFilter; // Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextBuffer; // Text buffer for logging/accumulating text +struct ImGuiTextEditCallbackData; // Shared state of ImGui::InputText() when using custom ImGuiTextEditCallback (rare/advanced use) +struct ImGuiSizeCallbackData; // Structure used to constraint window size in custom ways when using custom ImGuiSizeCallback (rare/advanced use) +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiContext; // ImGui context (opaque) + +// Typedefs and Enumerations (declared as int for compatibility and to not pollute the top of this file) +typedef unsigned int ImU32; // 32-bit unsigned integer (typically used to store packed colors) +typedef unsigned int ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) +typedef unsigned short ImWchar; // character for keyboard input/display +typedef void* ImTextureID; // user data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) +typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_ +typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ +typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_ +typedef int ImGuiNavInput; // enum: an input identifier for navigation // enum ImGuiNavInput_ +typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_ +typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImDrawCornerFlags; // flags: for ImDrawList::AddRect*() etc. // enum ImDrawCornerFlags_ +typedef int ImDrawListFlags; // flags: for ImDrawList // enum ImDrawListFlags_ +typedef int ImFontAtlasFlags; // flags: for ImFontAtlas // enum ImFontAtlasFlags_ +typedef int ImGuiColorEditFlags; // flags: for ColorEdit*(), ColorPicker*() // enum ImGuiColorEditFlags_ +typedef int ImGuiColumnsFlags; // flags: for *Columns*() // enum ImGuiColumnsFlags_ +typedef int ImGuiDragDropFlags; // flags: for *DragDrop*() // enum ImGuiDragDropFlags_ +typedef int ImGuiComboFlags; // flags: for BeginCombo() // enum ImGuiComboFlags_ +typedef int ImGuiFocusedFlags; // flags: for IsWindowFocused() // enum ImGuiFocusedFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() etc. // enum ImGuiHoveredFlags_ +typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ +typedef int ImGuiNavFlags; // flags: for io.NavFlags // enum ImGuiNavFlags_ +typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ +typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(),CollapsingHeader()// enum ImGuiTreeNodeFlags_ +typedef int ImGuiWindowFlags; // flags: for Begin*() // enum ImGuiWindowFlags_ +typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); +typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); +#if defined(_MSC_VER) && !defined(__clang__) +typedef unsigned __int64 ImU64; // 64-bit unsigned integer +#else +typedef unsigned long long ImU64; // 64-bit unsigned integer +#endif + +// Others helpers at bottom of the file: +// class ImVector<> // Lightweight std::vector like class. +// IMGUI_ONCE_UPON_A_FRAME // Execute a block of code once per frame only (convenient for creating UI within deep-nested code that runs multiple times) + +struct ImVec2 +{ + float x, y; + ImVec2() { x = y = 0.0f; } + ImVec2(float _x, float _y) { x = _x; y = _y; } + float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, thus an assert is fine. +#ifdef IM_VEC2_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec2. + IM_VEC2_CLASS_EXTRA +#endif +}; + +struct ImVec4 +{ + float x, y, z, w; + ImVec4() { x = y = z = w = 0.0f; } + ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } +#ifdef IM_VEC4_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec4. + IM_VEC4_CLASS_EXTRA +#endif +}; + +// ImGui end-user API +// In a namespace so that user can add extra functions in a separate file (e.g. Value() helpers for your vector or common types) +namespace ImGui +{ + // Context creation and access, if you want to use multiple context, share context between modules (e.g. DLL). + // All contexts share a same ImFontAtlas by default. If you want different font atlas, you can new() them and overwrite the GetIO().Fonts variable of an ImGui context. + // All those functions are not reliant on the current context. + IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); + IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = Destroy current context + IMGUI_API ImGuiContext* GetCurrentContext(); + IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + + // Main + IMGUI_API ImGuiIO& GetIO(); + IMGUI_API ImGuiStyle& GetStyle(); + IMGUI_API void NewFrame(); // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame(). + IMGUI_API void Render(); // ends the ImGui frame, finalize the draw data. (Obsolete: optionally call io.RenderDrawListsFn if set. Nowadays, prefer calling your render function yourself.) + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. (Obsolete: this used to be passed to your io.RenderDrawListsFn() function.) + IMGUI_API void EndFrame(); // ends the ImGui frame. automatically called by Render(), so most likely don't need to ever call that yourself directly. If you don't need to render you may call EndFrame() but you'll have wasted CPU already. If you don't need to render, better to not create any imgui windows instead! + + // Demo, Debug, Informations + IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create metrics window. display ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc. + IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) + IMGUI_API bool ShowStyleSelector(const char* label); + IMGUI_API void ShowFontSelector(const char* label); + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + IMGUI_API const char* GetVersion(); + + // Styles + IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // New, recommended style + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // Classic imgui style (default) + IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // Best used with borders and a custom, thicker font + + // Window + IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // push window to the stack and start appending to it. see .cpp for details. return false when window is collapsed (so you can early out in your code) but you always need to call End() regardless. 'bool* p_open' creates a widget on the upper-right to close the window (which sets your bool to false). + IMGUI_API void End(); // always call even if Begin() return false (which indicates a collapsed window)! finish appending to current window, pop it off the window stack. + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). + IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // " + IMGUI_API void EndChild(); // always call even if BeginChild() return false (which indicates a collapsed or clipping child window) + IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates + IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() + IMGUI_API float GetContentRegionAvailWidth(); // + IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API float GetWindowContentRegionWidth(); // + IMGUI_API ImDrawList* GetWindowDrawList(); // get rendering command-list if you want to append your own draw primitives + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList api) + IMGUI_API ImVec2 GetWindowSize(); // get current window size + IMGUI_API float GetWindowWidth(); + IMGUI_API float GetWindowHeight(); + IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowAppearing(); + IMGUI_API void SetWindowFontScale(float scale); // per-window font scale. Adjust IO.FontGlobalScale if you want to scale all windows + + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. + IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() + IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() + IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() + IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg. + IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. + IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. + IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). + IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / front-most. prefer using SetNextWindowFocus(). + IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. + IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. + IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state + IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / front-most. use NULL to remove focus. + + IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()] + IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()] + IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X + IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y + IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] + IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] + IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. + IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it) + IMGUI_API ImGuiStorage* GetStateStorage(); + + // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); + IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); + IMGUI_API void PopStyleColor(int count = 1); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); + IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwhise use GetColorU32() to get style color + style alpha. + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier + IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied + IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align width to the right side) + IMGUI_API void PopItemWidth(); + IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position + IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space + IMGUI_API void PopTextWrapPos(); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); + + // Cursor / Layout + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. + IMGUI_API void SameLine(float pos_x = 0.0f, float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally + IMGUI_API void NewLine(); // undo a SameLine() + IMGUI_API void Spacing(); // add vertical spacing + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) + IMGUI_API void EndGroup(); + IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position + IMGUI_API float GetCursorPosX(); // " + IMGUI_API float GetCursorPosY(); // " + IMGUI_API void SetCursorPos(const ImVec2& local_pos); // " + IMGUI_API void SetCursorPosX(float x); // " + IMGUI_API void SetCursorPosY(float y); // " + IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] + IMGUI_API void AlignTextToFramePadding(); // vertically align/lower upcoming text to FramePadding.y so that it will aligns to upcoming widgets (call if you have text on a line before regular widgets) + IMGUI_API float GetTextLineHeight(); // ~ FontSize + IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) + IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 + IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + + // Columns + // You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather lacking. + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API int GetColumnsCount(); + + // ID scopes + // If you are creating widgets in a loop you most likely want to push a unique identifier (e.g. object pointer, loop index) so ImGui can differentiate them. + // You can also use the "##foobar" syntax within widget label to distinguish them from each others. Read "A primer on the use of labels/IDs" in the FAQ for more details. + IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); + IMGUI_API void PushID(const void* ptr_id); + IMGUI_API void PushID(int int_id); + IMGUI_API void PopID(); + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Main + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); + IMGUI_API bool RadioButton(const char* label, bool active); + IMGUI_API bool RadioButton(const char* label, int* v, int v_button); + IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL); + + // Widgets: Combo Box + // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it. + // The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + + // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds) + // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x + // Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound + IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", const char* display_format_max = NULL, float power = 1.0f); + IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); // If v_min >= v_max we have no bound + IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f", const char* display_format_max = NULL); + + // Widgets: Input with Keyboard + IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0); + + // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds) + IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); // adjust display_format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for logarithmic sliders + IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f); + IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be left-clicked to open a picker, and right-clicked to open an option menu.) + // Note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x + IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); + IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0)); // display a colored square/button, hover for details, return true when pressed. + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + + // Widgets: Trees + IMGUI_API bool TreeNode(const char* label); // if returning 'true' the node is open and the tree id is pushed into the id stack. user is responsible for calling TreePop(). + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call Push/Pop yourself for layout purpose + IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePop(); // ~ Unindent()+PopId() + IMGUI_API void TreeAdvanceToLabelPos(); // advance cursor x position by GetTreeNodeToLabelSpacing() + IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode + IMGUI_API void SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). + IMGUI_API bool CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small close button on upper right of the header + + // Widgets: Selectable / Lists + IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height + IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + IMGUI_API bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. make sure to call ListBoxFooter() afterwards. + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " + IMGUI_API void ListBoxFooter(); // terminate the scrolling region + + // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) + IMGUI_API void Value(const char* prefix, bool b); + IMGUI_API void Value(const char* prefix, int v); + IMGUI_API void Value(const char* prefix, unsigned int v); + IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + + // Tooltips + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set text tooltip under mouse-cursor, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of contents). + IMGUI_API void EndTooltip(); + + // Menus + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. + IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). + IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); // only call EndBegin() if BeginMenu() returns true! + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + + // Popups + IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returns true! + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). + IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // modal dialog (regular window with title bar, block interactions behind the modal window, can't close the modal window by clicking outside) + IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. + IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open + IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. + + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. + IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty + IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file + IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard + IMGUI_API void LogFinish(); // stop logging (close file, etc.) + IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + + // Drag and Drop + // [BETA API] Missing Demo code. API may evolve. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0, int mouse_button = 0); // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond = 0);// type is a user defined string of maximum 12 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! + IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! + + // Clipping + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); + IMGUI_API void PopClipRect(); + + // Focus, Activation + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged) + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. Please use instead of "if (IsWindowAppearing()) SetScrollHere()" to signify "default item". + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + + // Utilities + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) + IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? + IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) + IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) + IMGUI_API bool IsAnyItemHovered(); + IMGUI_API bool IsAnyItemActive(); + IMGUI_API bool IsAnyItemFocused(); + IMGUI_API ImVec2 GetItemRectMin(); // get bounding rectangle of last item, in screen space + IMGUI_API ImVec2 GetItemRectMax(); // " + IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space + IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. + IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags = 0); // is current window focused? or its root/child, depending on flags. see flags for options. + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. + IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. + IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. + IMGUI_API float GetTime(); + IMGUI_API int GetFrameCount(); + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text + IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); + IMGUI_API const char* GetStyleColorName(ImGuiCol idx); + IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. + + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) + + IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); + IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); + IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); + IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + + // Inputs + IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] + IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]! + IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate + IMGUI_API bool IsMouseDown(int button); // is mouse button held + IMGUI_API bool IsAnyMouseDown(); // is any mouse button held + IMGUI_API bool IsMouseClicked(int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) + IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime. + IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) + IMGUI_API bool IsMouseDragging(int button = 0, float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings. disregarding of consideration of focus/window ordering/blocked by a popup. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // + IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls + IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse positioning at the time of opening popup we have BeginPopup() into + IMGUI_API ImVec2 GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API void ResetMouseDragDelta(int button = 0); // + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor type); // set desired cursor type + IMGUI_API void CaptureKeyboardFromApp(bool capture = true); // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application handle). e.g. force capture keyboard when your widget is being hovered. + IMGUI_API void CaptureMouseFromApp(bool capture = true); // manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application handle). + + // Clipboard Utilities (also see the LogToClipboard() function to capture or output text data to the clipboard) + IMGUI_API const char* GetClipboardText(); + IMGUI_API void SetClipboardText(const char* text); + + // Memory Utilities + // All those functions are not reliant on the current context. + // If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again. + IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data = NULL); + IMGUI_API void* MemAlloc(size_t size); + IMGUI_API void MemFree(void* ptr); + +} // namespace ImGui + +// Flags for ImGui::Begin() +enum ImGuiWindowFlags_ +{ + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + //ImGuiWindowFlags_ShowBorders = 1 << 7, // Show borders around windows and items (OBSOLETE! Use e.g. style.FrameBorderSize=1.0f to enable borders). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // (WIP) Enable resize from any corners and borders. Your back-end needs to honor the different values of io.MouseCursor set by imgui. + ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + + // [Internal] + ImGuiWindowFlags_NavFlattened = 1 << 23, // (WIP) Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!) + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() +}; + +// Flags for ImGui::InputText() +enum ImGuiInputTextFlags_ +{ + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to when the value was modified) + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ +{ + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Full colored frame (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_NavCloseFromChild = 1 << 13, // (WIP) Nav: left direction may close this TreeNode() when focusing on any child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoAutoOpenOnLog + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap +#endif +}; + +// Flags for ImGui::Selectable() +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ +{ + ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_AnyWindow = 1 << 2, // IsWindowFocused(): Return true if any window is focused + ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows +}; + +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_Default = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 3, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 4, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 5, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 6, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return true, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + +// Standard Drag and Drop payload types. You can define you own payload types using 12-characters long strings. Types starting with '_' are defined by Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3] // Standard type for colors, without alpha. User code may use this type. +#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4] // Standard type for colors. User code may use this type. + +// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +enum ImGuiKey_ +{ + ImGuiKey_Tab, + ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, + ImGuiKey_UpArrow, + ImGuiKey_DownArrow, + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, + ImGuiKey_End, + ImGuiKey_Insert, + ImGuiKey_Delete, + ImGuiKey_Backspace, + ImGuiKey_Space, + ImGuiKey_Enter, + ImGuiKey_Escape, + ImGuiKey_A, // for text edit CTRL+A: select all + ImGuiKey_C, // for text edit CTRL+C: copy + ImGuiKey_V, // for text edit CTRL+V: paste + ImGuiKey_X, // for text edit CTRL+X: cut + ImGuiKey_Y, // for text edit CTRL+Y: redo + ImGuiKey_Z, // for text edit CTRL+Z: undo + ImGuiKey_COUNT +}; + +// [BETA] Gamepad/Keyboard directional navigation +// Keyboard: Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays. +// Gamepad: Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). +// Read instructions in imgui.cpp for more details. +enum ImGuiNavInput_ +{ + // Gamepad Mapping + ImGuiNavInput_Activate, // activate / open / toggle / tweak value // e.g. Circle (PS4), A (Xbox), B (Switch), Space (Keyboard) + ImGuiNavInput_Cancel, // cancel / close / exit // e.g. Cross (PS4), B (Xbox), A (Switch), Escape (Keyboard) + ImGuiNavInput_Input, // text input / on-screen keyboard // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard) + ImGuiNavInput_Menu, // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard) + ImGuiNavInput_DpadLeft, // move / tweak / resize window (w/ PadMenu) // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard) + ImGuiNavInput_DpadRight, // + ImGuiNavInput_DpadUp, // + ImGuiNavInput_DpadDown, // + ImGuiNavInput_LStickLeft, // scroll / move window (w/ PadMenu) // e.g. Left Analog Stick Left/Right/Up/Down + ImGuiNavInput_LStickRight, // + ImGuiNavInput_LStickUp, // + ImGuiNavInput_LStickDown, // + ImGuiNavInput_FocusPrev, // next window (w/ PadMenu) // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_FocusNext, // prev window (w/ PadMenu) // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + ImGuiNavInput_TweakSlow, // slower tweaks // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_TweakFast, // faster tweaks // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + + // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them. + // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) may be directly reading from io.KeyDown[] instead of io.NavInputs[]. + ImGuiNavInput_KeyMenu_, // toggle menu // = io.KeyAlt + ImGuiNavInput_KeyLeft_, // move left // = Arrow keys + ImGuiNavInput_KeyRight_, // move right + ImGuiNavInput_KeyUp_, // move up + ImGuiNavInput_KeyDown_, // move down + ImGuiNavInput_COUNT, + ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_ +}; + +// [BETA] Gamepad/Keyboard directional navigation flags, stored in io.NavFlags +enum ImGuiNavFlags_ +{ + ImGuiNavFlags_EnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. NewFrame() will automatically fill io.NavInputs[] based on io.KeyDown[]. + ImGuiNavFlags_EnableGamepad = 1 << 1, // Master gamepad navigation enable flag. This is mostly to instruct your imgui back-end to fill io.NavInputs[]. + ImGuiNavFlags_MoveMouse = 1 << 2, // Request navigation to allow moving the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantMoveMouse=true. If enabled you MUST honor io.WantMoveMouse requests in your binding, otherwise ImGui will react as if the mouse is jumping around back and forth. + ImGuiNavFlags_NoCaptureKeyboard = 1 << 3 // Do not set the io.WantCaptureKeyboard flag with io.NavActive is set. +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ +{ + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_CloseButton, + ImGuiCol_CloseButtonHovered, + ImGuiCol_CloseButtonActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TextSelectedBg, + ImGuiCol_ModalWindowDarkening, // darken entire screen when a modal window is active + ImGuiCol_DragDropTarget, + ImGuiCol_NavHighlight, // gamepad/keyboard: current highlighted item + ImGuiCol_NavWindowingHighlight, // gamepad/keyboard: when holding NavMenu to focus/move/resize windows + ImGuiCol_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //, ImGuiCol_ComboBg = ImGuiCol_PopupBg // ComboBg has been merged with PopupBg, so a redirect isn't accurate. + , ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, ImGuiCol_Column = ImGuiCol_Separator, ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive +#endif +}; + +// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. +// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During initialization, feel free to just poke into ImGuiStyle directly. +// NB: if changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. +enum ImGuiStyleVar_ +{ + // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_Count_ + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding +#endif +}; + +// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ +{ + ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on colored square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable colored square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview colored square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small colored square preview instead. + // User Options (right-click on widget to change some of them). You can set application defaults using SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the user choose and/or call SetColorEditOptions() during startup. + ImGuiColorEditFlags_AlphaBar = 1 << 9, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaPreview = 1 << 10, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 11, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_HDR = 1 << 12, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). + ImGuiColorEditFlags_RGB = 1 << 13, // [Inputs] // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any combination using RGB/HSV/HEX. + ImGuiColorEditFlags_HSV = 1 << 14, // [Inputs] // " + ImGuiColorEditFlags_HEX = 1 << 15, // [Inputs] // " + ImGuiColorEditFlags_Uint8 = 1 << 16, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 17, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 18, // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value. + ImGuiColorEditFlags_PickerHueWheel = 1 << 19, // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value. + // Internals/Masks + ImGuiColorEditFlags__InputsMask = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX, + ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_Float, + ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel|ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_PickerHueBar // Change application default using SetColorEditOptions() +}; + +// Enumeration for GetMouseCursor() +enum ImGuiMouseCursor_ +{ + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_ResizeAll, // Unused + ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Count_ +}; + +// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +// All those functions treat 0 as a shortcut to ImGuiCond_Always. From the point of view of the user use this as an enum (don't combine multiple values into flags). +enum ImGuiCond_ +{ + ImGuiCond_Always = 1 << 0, // Set the variable + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the window has no saved data (if doesn't exist in the .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the window is appearing after being hidden/inactive (or the first time) + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiSetCond_Always = ImGuiCond_Always, ImGuiSetCond_Once = ImGuiCond_Once, ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing = ImGuiCond_Appearing +#endif +}; + +// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). +// During the frame, prefer using ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. +struct ImGuiStyle +{ + float Alpha; // Global alpha applies to everything in ImGui. + ImVec2 WindowPadding; // Padding within a window. + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float PopupRounding; // Radius of popup window corners rounding. + float PopupBorderSize; // Thickness of border around popup windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. + float ScrollbarRounding; // Radius of grab corners for scrollbar. + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. + ImVec2 DisplayWindowPadding; // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. + bool AntiAliasedFill; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + ImVec4 Colors[ImGuiCol_COUNT]; + + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); +}; + +// This is where your app communicate with ImGui. Access via ImGui::GetIO(). +// Read 'Programmer guide' section in .cpp file for general usage. +struct ImGuiIO +{ + //------------------------------------------------------------------ + // Settings (fill once) // Default value: + //------------------------------------------------------------------ + + ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + ImGuiNavFlags NavFlags; // = 0x00 // See ImGuiNavFlags_. Gamepad/keyboard navigation options. + float IniSavingRate; // = 5.0f // Maximum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. + const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + void* UserData; // = NULL // Store your own data for retrieval by callbacks. + + ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui. + ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area. + ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize + + // Advanced/subtle behaviors + bool OptMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl + bool OptCursorBlink; // = true // Enable blinking cursor, for users who consider it annoying. + + //------------------------------------------------------------------ + // Settings (User Functions) + //------------------------------------------------------------------ + + // Optional: access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; + + // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) + // (default to use native imm32 api on Windows) + void (*ImeSetInputScreenPosFn)(int x, int y); + void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // [OBSOLETE] Rendering function, will be automatically called in Render(). Please call your rendering function yourself now! You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). + // See example applications if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawData* data); +#endif + + //------------------------------------------------------------------ + // Input - Fill before calling NewFrame() + //------------------------------------------------------------------ + + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: left, right, middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel: 1 unit scrolls about 5 lines text. + float MouseWheelH; // Mouse wheel (Horizontal). Most users don't have a mouse with an horizontal wheel, may not be filled by all back-ends. + bool MouseDrawCursor; // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). + bool KeyCtrl; // Keyboard modifier pressed: Control + bool KeyShift; // Keyboard modifier pressed: Shift + bool KeyAlt; // Keyboard modifier pressed: Alt + bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows + bool KeysDown[512]; // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). + ImWchar InputCharacters[16+1]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. + float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs (keyboard keys will be auto-mapped and be written here by ImGui::NewFrame) + + // Functions + IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[] + IMGUI_API void AddInputCharactersUTF8(const char* utf8_chars); // Add new characters into InputCharacters[] from an UTF-8 string + inline void ClearInputCharacters() { InputCharacters[0] = 0; } // Clear the text input buffer manually + + //------------------------------------------------------------------ + // Output - Retrieve after calling NewFrame() + //------------------------------------------------------------------ + + bool WantCaptureMouse; // When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. This is set by ImGui when it wants to use your mouse (e.g. unclicked mouse is hovering a window, or a widget is active). + bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. This is set by ImGui when it wants to use your keyboard inputs. + bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). + bool WantMoveMouse; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when ImGuiNavFlags_MoveMouse flag is enabled in io.NavFlags. + bool NavActive; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. + bool NavVisible; // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsActiveWindows; // Number of visible root windows (exclude child windows) + ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + + //------------------------------------------------------------------ + // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! + //------------------------------------------------------------------ + + ImVec2 MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) + ImVec2 MouseClickedPos[5]; // Position at time of clicking + float MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down + float NavInputsDownDuration[ImGuiNavInput_COUNT]; + float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + // OBSOLETED in 1.60 (from Dec 2017) + static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } + static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = 0.f) { (void)on_edge; (void)outward; IM_ASSERT(0); return pos; } + // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + static inline void ShowTestWindow() { return ShowDemoWindow(); } + static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } + static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } + static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } + static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } + // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead. + static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } + static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } + static inline void SetNextWindowPosCenter(ImGuiCond c=0) { ImGuiIO& io = GetIO(); SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); } + // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } + static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead. + static inline bool IsMouseHoveringAnyWindow() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } + // OBSOLETED IN 1.49 (between Apr 2016 and May 2016) + static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } +} +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +// Lightweight std::vector<> like class to avoid dragging dependencies (also: windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). +// Our implementation does NOT call C++ constructors/destructors. This is intentional and we do not require it. Do not use this class as a straight std::vector replacement in your code! +template +class ImVector +{ +public: + int Size; + int Capacity; + T* Data; + + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + inline ImVector() { Size = Capacity = 0; Data = NULL; } + inline ~ImVector() { if (Data) ImGui::MemFree(Data); } + + inline bool empty() const { return Size == 0; } + inline int size() const { return Size; } + inline int capacity() const { return Capacity; } + + inline value_type& operator[](int i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& operator[](int i) const { IM_ASSERT(i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } + inline iterator begin() { return Data; } + inline const_iterator begin() const { return Data; } + inline iterator end() { return Data + Size; } + inline const_iterator end() const { return Data + Size; } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > sz ? new_capacity : sz; } + + inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size, const T& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) Data[n] = v; Size = new_size; } + inline void reserve(int new_capacity) + { + if (new_capacity <= Capacity) + return; + T* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(T)); + if (Data) + memcpy(new_data, Data, (size_t)Size * sizeof(T)); + ImGui::MemFree(Data); + Data = new_data; + Capacity = new_capacity; + } + + // NB: &v cannot be pointing inside the ImVector Data itself! e.g. v.push_back(v[10]) is forbidden. + inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); Data[Size++] = v; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const value_type& v) { if (Size == 0) push_back(v); else insert(Data, v); } + + inline iterator erase(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } + inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } + inline bool contains(const value_type& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } +}; + +// Helper: execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Usage: +// static ImGuiOnceUponAFrame oaf; +// if (oaf) +// ImGui::Text("This will be called only once per frame"); +struct ImGuiOnceUponAFrame +{ + ImGuiOnceUponAFrame() { RefFrame = -1; } + mutable int RefFrame; + operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } +}; + +// Helper macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it within e.g. an if() statement without curly braces. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Will obsolete +#define IMGUI_ONCE_UPON_A_FRAME static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf) +#endif + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextFilter +{ + struct TextRange + { + const char* b; + const char* e; + + TextRange() { b = e = NULL; } + TextRange(const char* _b, const char* _e) { b = _b; e = _e; } + const char* begin() const { return b; } + const char* end() const { return e; } + bool empty() const { return b == e; } + char front() const { return *b; } + static bool is_blank(char c) { return c == ' ' || c == '\t'; } + void trim_blanks() { while (b < e && is_blank(*b)) b++; while (e > b && is_blank(*(e-1))) e--; } + IMGUI_API void split(char separator, ImVector& out); + }; + + char InputBuf[256]; + ImVector Filters; + int CountGrep; + + IMGUI_API ImGuiTextFilter(const char* default_filter = ""); + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; + IMGUI_API void Build(); + void Clear() { InputBuf[0] = 0; Build(); } + bool IsActive() const { return !Filters.empty(); } +}; + +// Helper: Text buffer for logging/accumulating text +struct ImGuiTextBuffer +{ + ImVector Buf; + + ImGuiTextBuffer() { Buf.push_back(0); } + inline char operator[](int i) { return Buf.Data[i]; } + const char* begin() const { return &Buf.front(); } + const char* end() const { return &Buf.back(); } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const { return Buf.Size - 1; } + bool empty() { return Buf.Size <= 1; } + void clear() { Buf.clear(); Buf.push_back(0); } + void reserve(int capacity) { Buf.reserve(capacity); } + const char* c_str() const { return Buf.Data; } + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); +}; + +// Helper: Simple Key->value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1), store color edit options. +// This is optimized for efficient reading (dichotomy into a contiguous buffer), rare writing (typically tied to user interactions) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage +{ + struct Pair + { + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + Pair(ImGuiID _key, int _val_i) { key = _key; val_i = _val_i; } + Pair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; } + Pair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; } + }; + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ImGuiID key, float val); + IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +// Shared state of InputText(), passed to callback when a ImGuiInputTextFlags_Callback* flag is used and the corresponding callback is triggered. +struct ImGuiTextEditCallbackData +{ + ImGuiInputTextFlags EventFlag; // One of ImGuiInputTextFlags_Callback* // Read-only + ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only + void* UserData; // What user passed to InputText() // Read-only + bool ReadOnly; // Read-only mode // Read-only + + // CharFilter event: + ImWchar EventChar; // Character input // Read-write (replace character or set to zero) + + // Completion,History,Always events: + // If you modify the buffer contents make sure you update 'BufTextLen' and set 'BufDirty' to true. + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only + char* Buf; // Current text buffer // Read-write (pointed data only, can't replace the actual pointer) + int BufTextLen; // Current text length in bytes // Read-write + int BufSize; // Maximum text length in bytes // Read-only + bool BufDirty; // Set if you modify Buf/BufTextLen!! // Write + int CursorPos; // // Read-write + int SelectionStart; // // Read-write (== to SelectionEnd when no selection) + int SelectionEnd; // // Read-write + + // NB: Helper functions for text manipulation. Calling those function loses selection. + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); + bool HasSelection() const { return SelectionStart != SelectionEnd; } +}; + +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. +struct ImGuiSizeCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + +// Data payload for Drag and Drop operations +struct ImGuiPayload +{ + // Members + const void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + int DataFrameCount; // Data timestamp + char DataType[12 + 1]; // Data type tag (short user-supplied string, 12 characters max) + bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) + bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsPreview() const { return Preview; } + bool IsDelivery() const { return Delivery; } +}; + +// Helpers macros to generate 32-bits encoded colors +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#define IM_COL32(R,G,B,A) (((ImU32)(A)<>IM_COL32_R_SHIFT)&0xFF) * sc; Value.y = (float)((rgba>>IM_COL32_G_SHIFT)&0xFF) * sc; Value.z = (float)((rgba>>IM_COL32_B_SHIFT)&0xFF) * sc; Value.w = (float)((rgba>>IM_COL32_A_SHIFT)&0xFF) * sc; } + ImColor(float r, float g, float b, float a = 1.0f) { Value.x = r; Value.y = g; Value.z = b; Value.w = a; } + ImColor(const ImVec4& col) { Value = col; } + inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } + inline operator ImVec4() const { return Value; } + + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. + inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } + static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r,g,b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r,g,b,a); } +}; + +// Helper: Manually clip large list of items. +// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse clipping based on visibility to save yourself from processing those items at all. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// ImGui already clip items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this cost and your own data fetching/submission cost null. +// Usage: +// ImGuiListClipper clipper(1000); // we have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height (step skipped if we passed a known height as second arg to constructor). +// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. +// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call Step(). Does nothing and switch to Step 3.) +// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. +struct ImGuiListClipper +{ + float StartPosY; + float ItemsHeight; + int ItemsCount, StepNo, DisplayStart, DisplayEnd; + + // items_count: Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step). + // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). + // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step(). + ImGuiListClipper(int items_count = -1, float items_height = -1.0f) { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want). + ~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. + + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. +}; + +//----------------------------------------------------------------------------- +// Draw List +// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. +//----------------------------------------------------------------------------- + +// Draw callbacks for advanced uses. +// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that) +// Draw callback may be useful for example, A) Change your GPU render state, B) render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else RenderTriangles()' +typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); + +// Typically, 1 command = 1 GPU draw call (unless command is a callback) +struct ImDrawCmd +{ + unsigned int ElemCount; // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. + ImVec4 ClipRect; // Clipping rectangle (x1, y1, x2, y2) + ImTextureID TextureId; // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImDrawCallback UserCallback; // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void* UserCallbackData; // The draw callback code can access this. + + ImDrawCmd() { ElemCount = 0; ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; TextureId = NULL; UserCallback = NULL; UserCallbackData = NULL; } +}; + +// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h) +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; +#endif + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert +{ + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. +// The type has to be described within the macro (you can either declare the struct or use a typedef) +// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. +IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; +#endif + +// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together. +// You can also use them to simulate drawing layers and submit primitives in a different order than how they will be rendered. +struct ImDrawChannel +{ + ImVector CmdBuffer; + ImVector IdxBuffer; +}; + +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 + ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 + ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 + ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA + ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience +}; + +enum ImDrawListFlags_ +{ + ImDrawListFlags_AntiAliasedLines = 1 << 0, + ImDrawListFlags_AntiAliasedFill = 1 << 1 +}; + +// Draw command list +// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. +// Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), however you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) +// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. +struct ImDrawList +{ + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those + ImVector VtxBuffer; // Vertex buffer. + + // [Internal, used while building lists] + ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + const char* _OwnerName; // Pointer to owner window's name for debugging + unsigned int _VtxCurrentIdx; // [Internal] == VtxBuffer.Size + ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + int _ChannelsCurrent; // [Internal] current channel number (0) + int _ChannelsCount; // [Internal] number of active channels (1+) + ImVector _Channels; // [Internal] draw channels for columns API (not resized down so _ChannelsCount may be smaller than _Channels.Size) + + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) + ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; _OwnerName = NULL; Clear(); } + ~ImDrawList() { ClearFreeMemory(); } + IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) + IMGUI_API void PushClipRectFullScreen(); + IMGUI_API void PopClipRect(); + IMGUI_API void PushTextureID(const ImTextureID& texture_id); + IMGUI_API void PopTextureID(); + inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } + inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + + // Primitives + IMGUI_API void AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f); // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round + IMGUI_API void AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); // a: upper-left, b: lower-right + IMGUI_API void AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); + IMGUI_API void AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col); + IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); + IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,0), const ImVec2& uv_c = ImVec2(1,1), const ImVec2& uv_d = ImVec2(0,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, const int num_points, ImU32 col); + IMGUI_API void AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 0); + + // Stateful path API, add points then finish with PathFill() or PathStroke() + inline void PathClear() { _Path.resize(0); } + inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } + inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path[_Path.Size-1], &pos, 8) != 0) _Path.push_back(pos); } + inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); PathClear(); } + inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); PathClear(); } + IMGUI_API void PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10); + IMGUI_API void PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + IMGUI_API void PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0); + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); + + // Channels + // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives before background primitives) + // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, prefer to append into separate channels then merge at the end) + IMGUI_API void ChannelsSplit(int channels_count); + IMGUI_API void ChannelsMerge(); + IMGUI_API void ChannelsSetCurrent(int channel_index); + + // Advanced + IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible + + // Internal helpers + // NB: all primitives needs to be reserved via PrimReserve() beforehand! + IMGUI_API void Clear(); + IMGUI_API void ClearFreeMemory(); + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); + IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); + inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col){ _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } + inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } + inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } + IMGUI_API void UpdateClipRect(); + IMGUI_API void UpdateTextureID(); +}; + +// All draw data to render an ImGui frame +struct ImDrawData +{ + bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + ImDrawList** CmdLists; + int CmdListsCount; + int TotalVtxCount; // For convenience, sum of all cmd_lists vtx_buffer.Size + int TotalIdxCount; // For convenience, sum of all cmd_lists idx_buffer.Size + + // Functions + ImDrawData() { Clear(); } + void Clear() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; } // Draw lists are owned by the ImGuiContext and only pointed to here. + IMGUI_API void DeIndexAllBuffers(); // For backward compatibility or convenience: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! + IMGUI_API void ScaleClipRects(const ImVec2& sc); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +}; + +struct ImFontConfig +{ + void* FontData; // // TTF/OTF data + int FontDataSize; // // TTF/OTF data size + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer. + int OversampleH, OversampleV; // 3, 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. + + // [Internal] + char Name[32]; // Name (strictly to ease debugging) + ImFont* DstFont; + + IMGUI_API ImFontConfig(); +}; + +struct ImFontGlyph +{ + ImWchar Codepoint; // 0x0000..0xFFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +enum ImFontAtlasFlags_ +{ + ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two + ImFontAtlasFlags_NoMouseCursors = 1 << 1 // Don't build software mouse cursors into the atlas +}; + +// Load and rasterize multiple TTF/OTF fonts into a same texture. +// Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. +// We also add custom graphic data into the texture that serves for ImGui. +// 1. (Optional) Call AddFont*** functions. If you don't call any, the default font will be loaded for you. +// 2. Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// 3. Upload the pixels data into a texture within your graphics system. +// 4. Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture. This value will be passed back to you during rendering to identify the texture. +// IMPORTANT: If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the ImFont is build (when calling GetTextData*** or Build()). We only copy the pointer, not the data. +struct ImFontAtlas +{ + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after Build(). Set font_cfg->FontDataOwnedByAtlas to false to keep ownership. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void ClearTexData(); // Clear the CPU-side texture data. Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearInputData(); // Clear the input TTF data (inc sizes, glyph ranges) + IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates) + IMGUI_API void Clear(); // Clear all + + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // RGBA32 format is provided for convenience and compatibility, but note that unless you use CustomRect to draw color data, the RGB pixels emitted from Fonts will all be white (~75% of waste). + // Pitch = Width * BytesPerPixels + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Default + Japanese + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters + IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters + + // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges(). + struct GlyphRangesBuilder + { + ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + GlyphRangesBuilder() { UsedChars.resize(0x10000 / 8); memset(UsedChars.Data, 0, 0x10000 / 8); } + bool GetBit(int n) { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; } + void SetBit(int n) { UsedChars[n >> 3] |= 1 << (n & 7); } // Set bit 'c' in the array + void AddChar(ImWchar c) { SetBit(c); } // Add character + IMGUI_API void AddText(const char* text, const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) + IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault) to force add all of ASCII/Latin+Ext + IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges + }; + + //------------------------------------------- + // Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels. + // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. + struct CustomRect + { + unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data. + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset + ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font + CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } + }; + + IMGUI_API int AddCustomRectRegular(unsigned int id, int width, int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. + const CustomRect* GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; } + + // Internals + IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + + //------------------------------------------- + // Members + //------------------------------------------- + + ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; // Texture width calculated during Build(). + int TexHeight; // Texture height calculated during Build(). + ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. + ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. + ImVector ConfigData; // Internal data + int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList +}; + +// Font runtime data and rendering +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +struct ImFont +{ + // Members: Hot ~62/78 bytes + float FontSize; // // Height of characters, set during loading (don't change after loading) + float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() + ImVec2 DisplayOffset; // = (0.f,1.f) // Offset font rendering by xx pixels + ImVector Glyphs; // // All glyphs. + ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). + ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. + const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + float FallbackAdvanceX; // == FallbackGlyph->AdvanceX + ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() + + // Members: Cold ~18/26 bytes + short ConfigDataCount; // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. + ImFontConfig* ConfigData; // // Pointer within ContainerAtlas->ConfigData + ImFontAtlas* ContainerAtlas; // // What we has been loaded into + float Ascent, Descent; // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + int MetricsTotalSurface;// // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API void ClearOutputData(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; + IMGUI_API void SetFallbackChar(ImWchar c); + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + + // [Internal] + IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); + IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ +#endif +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif diff --git a/apps/bench_shared_offscreen/imgui/imgui_demo.cpp b/apps/bench_shared_offscreen/imgui/imgui_demo.cpp new file mode 100644 index 00000000..2a197278 --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_demo.cpp @@ -0,0 +1,3150 @@ +// dear imgui, v1.60 WIP +// (demo code) + +// Message to the person tempted to delete this file when integrating ImGui into their code base: +// Don't do it! Do NOT remove this file from your project! It is useful reference code that you and other users will want to refer to. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// During development, you can call ImGui::ShowDemoWindow() in your code to learn about various features of ImGui. Have it wired in a debug menu! +// Removing this file from your project is hindering access to documentation for everyone in your team, likely leading you to poorer usage of the library. +// Note that you can #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h for the same effect. +// If you want to link core ImGui in your final builds but not those demo windows, #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h and those functions will be empty. +// In other situation, when you have ImGui available you probably want this to be available for reference and execution. +// Thank you, +// -Your beloved friend, imgui_demo.cpp (that you won't delete) + +// Message to beginner C/C++ programmers. About the meaning of 'static': in this demo code, we frequently we use 'static' variables inside functions. +// We do this as a way to gather code and data in the same place, just to make the demo code faster to read, faster to write, and use less code. +// A static variable persist across calls, so it is essentially like a global variable but declared inside the scope of the function. +// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant or used in threads. +// This might be a pattern you occasionally want to use in your code, but most of the real data you would be editing is likely to be stored outside your function. + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include // toupper, isprint +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored "-Wformat-security" // warning : warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning : format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#if (__GNUC__ >= 6) +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#endif +#endif + +// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) + +//----------------------------------------------------------------------------- +// DEMO CODE +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Obsolete name since 1.53, TEST->DEMO +#define IMGUI_DISABLE_DEMO_WINDOWS +#endif + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +static void ShowExampleAppConsole(bool* p_open); +static void ShowExampleAppLog(bool* p_open); +static void ShowExampleAppLayout(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppLongText(bool* p_open); +static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); +static void ShowExampleAppFixedOverlay(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); +static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleAppMainMenuBar(); +static void ShowExampleMenuFile(); + +static void ShowHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +void ImGui::ShowUserGuide() +{ + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents)."); + ImGui::BulletText("Click and drag on any empty space to move window."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + if (ImGui::GetIO().FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("Mouse Wheel to scroll."); + ImGui::BulletText("While editing text:\n"); + ImGui::Indent(); + ImGui::BulletText("Hold SHIFT or use mouse to select text."); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); +} + +// Demonstrate most ImGui features (big function!) +void ImGui::ShowDemoWindow(bool* p_open) +{ + // Examples apps + static bool show_app_main_menu_bar = false; + static bool show_app_console = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_long_text = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_fixed_overlay = false; + static bool show_app_window_titles = false; + static bool show_app_custom_rendering = false; + static bool show_app_style_editor = false; + + static bool show_app_metrics = false; + static bool show_app_about = false; + + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } + if (show_app_about) + { + ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Text("Dear ImGui, %s", ImGui::GetVersion()); + ImGui::Separator(); + ImGui::Text("By Omar Cornut and all dear imgui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); + ImGui::End(); + } + + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + static bool no_nav = false; + + // Demonstrate the various window flags. Typically you would just use the default. + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + + ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); // 2/3 of the space for widget and 1/3 for labels + ImGui::PushItemWidth(-140); // Right align, keep 140 pixels for labels + + ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + ImGui::MenuItem("Metrics", NULL, &show_app_metrics); + ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + if (ImGui::CollapsingHeader("Help")) + { + ImGui::TextWrapped("This window is being created by the ShowDemoWindow() function. Please refer to the code in imgui_demo.cpp for reference.\n\n"); + ImGui::Text("USER GUIDE:"); + ImGui::ShowUserGuide(); + } + + if (ImGui::CollapsingHeader("Window options")) + { + ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); + ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); + ImGui::Checkbox("No menu", &no_menu); + ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); + ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); + ImGui::Checkbox("No collapse", &no_collapse); + ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); + ImGui::Checkbox("No nav", &no_nav); + + if (ImGui::TreeNode("Style")) + { + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Capture/Logging")) + { + ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. You can also call ImGui::LogText() to output directly to the log without a visual output."); + ImGui::LogButtons(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Widgets")) + { + if (ImGui::TreeNode("Basic")) + { + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + ImGui::Text("Hover over me"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::Text("- or me"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + // Testing ImGuiOnceUponAFrame helper. + //static ImGuiOnceUponAFrame once; + //for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + ImGui::Separator(); + + ImGui::LabelText("label", "Value"); + + { + // Simplified one-liner Combo() API, using values packed in a single constant string + static int current_item_1 = 1; + ImGui::Combo("combo", ¤t_item_1, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + //ImGui::Combo("combo w/ array of char*", ¤t_item_2_idx, items, IM_ARRAYSIZE(items)); // Combo using proper array. You can also pass a callback to retrieve array value, no need to create/copy an array just for that. + + // General BeginCombo() API, you have full control over your selection data and display type + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO", "PPPP", "QQQQQQQQQQ", "RRR", "SSSS" }; + static const char* current_item_2 = NULL; + if (ImGui::BeginCombo("combo 2", current_item_2)) // The second parameter is the label previewed before opening the combo. + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + bool is_selected = (current_item_2 == items[n]); // You can store your selection however you want, outside or inside your objects + if (ImGui::Selectable(items[n], is_selected)) + current_item_2 = items[n]; + if (is_selected) + ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch) + } + ImGui::EndCombo(); + } + } + + { + static char str0[128] = "Hello, world!"; + static int i0=123; + static float f0=0.001f; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); ShowHelpMarker("Hold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n"); + + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); + + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + ImGui::InputFloat3("input float3", vec4a); + } + + { + static int i1=50, i2=42; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%.0f%%"); + + static float f1=1.00f, f2=0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + } + + { + static int i1=0; + ImGui::SliderInt("slider int", &i1, -1, 3); + ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value."); + + static float f1=0.123f, f2=0.0f; + ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); + ImGui::SliderFloat("slider log float", &f2, -10.0f, 10.0f, "%.4f", 3.0f); + static float angle = 0.0f; + ImGui::SliderAngle("slider angle", &angle); + } + + static float col1[3] = { 1.0f,0.0f,0.2f }; + static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n"); + + ImGui::ColorEdit4("color 2", col2); + + const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int listbox_item_current = 1; + ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + + //static int listbox_item_current2 = 2; + //ImGui::PushItemWidth(-1); + //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + //ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Trees")) + { + if (ImGui::TreeNode("Basic trees")) + { + for (int i = 0; i < 5; i++) + if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("button")) { }; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced, with Selectable nodes")) + { + ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static bool align_label_with_current_x_position = false; + ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position); + ImGui::Text("Hello!"); + if (align_label_with_current_x_position) + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + + static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit. + int node_clicked = -1; // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc. + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents. + for (int i = 0; i < 6; i++) + { + // Disable the default open on single-click behavior and pass in Selected flag according to our selection state. + ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0); + if (i < 3) + { + // Node + bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (node_open) + { + ImGui::Text("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } + else + { + // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + } + } + if (node_clicked != -1) + { + // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame. + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + ImGui::PopStyleVar(); + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collapsing Headers")) + { + static bool closable_group = true; + ImGui::Checkbox("Enable extra group", &closable_group); + if (ImGui::CollapsingHeader("Header")) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Bullets")) + { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text")) + { + if (ImGui::TreeNode("Colored Text")) + { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Word Wrapping")) + { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); + ImGui::Spacing(); + + static float wrap_width = 200.0f; + ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + + ImGui::Text("Test paragraph 1:"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::Text("Test paragraph 2:"); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("UTF-8 Text")) + { + // UTF-8 test with Japanese characters + // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature') + // - HOWEVER, FOR THIS DEMO FILE, BECAUSE WE WANT TO SUPPORT COMPILER, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE. + // Instead we are encoding a few string with hexadecimal constants. Don't do this in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // "nihongo" + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Images")) + { + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); + ImGuiIO& io = ImGui::GetIO(); + + // Here we are grabbing the font texture because that's the only one we have access to inside the demo code. + // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure. + // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID. + // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.) + // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc. + // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this. + // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). + ImTextureID my_tex_id = io.Fonts->TexID; + float my_tex_w = (float)io.Fonts->TexWidth; + float my_tex_h = (float)io.Fonts->TexHeight; + + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float focus_sz = 32.0f; + float focus_x = io.MousePos.x - pos.x - focus_sz * 0.5f; if (focus_x < 0.0f) focus_x = 0.0f; else if (focus_x > my_tex_w - focus_sz) focus_x = my_tex_w - focus_sz; + float focus_y = io.MousePos.y - pos.y - focus_sz * 0.5f; if (focus_y < 0.0f) focus_y = 0.0f; else if (focus_y > my_tex_h - focus_sz) focus_y = my_tex_h - focus_sz; + ImGui::Text("Min: (%.2f, %.2f)", focus_x, focus_y); + ImGui::Text("Max: (%.2f, %.2f)", focus_x + focus_sz, focus_y + focus_sz); + ImVec2 uv0 = ImVec2((focus_x) / my_tex_w, (focus_y) / my_tex_h); + ImVec2 uv1 = ImVec2((focus_x + focus_sz) / my_tex_w, (focus_y + focus_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(128,128), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::EndTooltip(); + } + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); + int frame_padding = -1 + i; // -1 = uses default padding + if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255))) + pressed_count += 1; + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Selectables")) + { + // Selectable() has 2 overloads: + // - The one taking "bool selected" as a read-only selection information. When Selectable() has been clicked is returns true and you can alter selection state accordingly. + // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) + // The earlier is more flexible, as in real application your selection may be stored in a different manner (in flags within objects, as an external list, etc). + if (ImGui::TreeNode("Basic")) + { + static bool selection[5] = { false, true, false, false, false }; + ImGui::Selectable("1. I am selectable", &selection[0]); + ImGui::Selectable("2. I am selectable", &selection[1]); + ImGui::Text("3. I am not selectable"); + ImGui::Selectable("4. I am selectable", &selection[3]); + if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selection[4] = !selection[4]; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Single Selection")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Multiple Selection")) + { + ShowHelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; + } + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rendering more text into the same line")) + { + // Using the Selectable() override that takes "bool* p_selected" parameter and toggle your booleans automatically. + static bool selected[3] = { false, false, false }; + ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); + ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("In columns")) + { + ImGui::Columns(3, NULL, false); + static bool selected[16] = { 0 }; + for (int i = 0; i < 16; i++) + { + char label[32]; sprintf(label, "Item %d", i); + if (ImGui::Selectable(label, &selected[i])) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Grid")) + { + static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true }; + for (int i = 0; i < 16; i++) + { + ImGui::PushID(i); + if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50))) + { + int x = i % 4, y = i / 4; + if (x > 0) selected[i - 1] ^= 1; + if (x < 3) selected[i + 1] ^= 1; + if (y > 0) selected[i - 4] ^= 1; + if (y < 3) selected[i + 4] ^= 1; + } + if ((i % 4) < 3) ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Filtered Text Input")) + { + static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); + static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); + static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); + static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); + struct TextFilters { static int FilterImGuiLetters(ImGuiTextEditCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } }; + static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); + + ImGui::Text("Password input"); + static char bufpass[64] = "password123"; + ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank); + ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-line Text Input")) + { + static bool read_only = false; + static char text[1024*16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\n\n" + "label:\n" + "\tlock cmpxchg8b eax\n"; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Read-only", &read_only); + ImGui::PopStyleVar(); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Plots widgets")) + { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + + // Create a dummy array of contiguous float values to plot + // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter. + static float values[90] = { 0 }; + static int values_offset = 0; + static float refresh_time = 0.0f; + if (!animate || refresh_time == 0.0f) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset+1) % IM_ARRAYSIZE(values); + phase += 0.10f*values_offset; + refresh_time += 1.0f/60.0f; + } + ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80)); + + // Use functions to generate output + // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count. + struct Funcs + { + static float Sin(void*, int i) { return sinf(i * 0.1f); } + static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } + }; + static int func_type = 0, display_count = 70; + ImGui::Separator(); + ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::Separator(); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) + { + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + } + + // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; + char buf[32]; + sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Color/Picker Widgets")) + { + static ImVec4 color = ImColor(114, 144, 154, 200); + + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool options_menu = true; + static bool hdr = false; + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options."); + ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + ImGui::Text("Color widget:"); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n"); + ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); + + ImGui::Text("Color widget HSV with Alpha:"); + ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags); + + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); + + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup."); + ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); + + ImGui::Text("Color button with Custom Picker Popup:"); + + // Generate a dummy palette + static bool saved_palette_inited = false; + static ImVec4 saved_palette[32]; + if (!saved_palette_inited) + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_inited = true; + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(); + open_popup |= ImGui::Button("Palette"); + if (open_popup) + { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) + { + // FIXME: Adding a drag and drop example here would be perfect! + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("Current"); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::PushID(n); + if ((n % 8) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20))) + color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); + EndDragDropTarget(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::EndPopup(); + } + + ImGui::Text("Color button only:"); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); + + ImGui::Text("Color picker:"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f); + static int inputs_mode = 2; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) + { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) + { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0"); + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); + ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; + if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; + if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB; + if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV; + if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX; + ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + + ImGui::Text("Programmatically set defaults/options:"); + ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Uint8 + HSV")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV); + ImGui::SameLine(); + if (ImGui::Button("Float + HDR")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_RGB); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Range Widgets")) + { + static float begin = 10, end = 90; + static int begin_i = 100, end_i = 1000; + ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%"); + ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %.0f units", "Max: %.0f units"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-component Widgets")) + { + static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + static int vec4i[4] = { 1, 5, 100, 255 }; + + ImGui::InputFloat2("input float2", vec4f); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); + ImGui::InputInt2("input int2", vec4i); + ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat3("input float3", vec4f); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); + ImGui::InputInt3("input int3", vec4i); + ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat4("input float4", vec4f); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Vertical Sliders")) + { + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + static int int_value = 0; + ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5); + ImGui::SameLine(); + + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; + ImGui::PushID("set1"); + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; + const int rows = 3; + const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows); + for (int nx = 0; nx < 4; nx++) + { + if (nx > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) + { + ImGui::PushID(nx*rows+ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Layout")) + { + if (ImGui::TreeNode("Child regions")) + { + static bool disable_mouse_wheel = false; + static bool disable_menu = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + ImGui::Checkbox("Disable Menu", &disable_menu); + + static int line = 50; + bool goto_line = ImGui::Button("Goto"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopItemWidth(); + + // Child 1: no border, enable horizontal scrollbar + { + ImGui::BeginChild("Child1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); + for (int i = 0; i < 100; i++) + { + ImGui::Text("%04d: scrollable region", i); + if (goto_line && line == i) + ImGui::SetScrollHere(); + } + if (goto_line && line >= 100) + ImGui::SetScrollHere(); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("Child2", ImVec2(0,300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0) | (disable_menu ? 0 : ImGuiWindowFlags_MenuBar)); + if (!disable_menu && ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Columns(2); + for (int i = 0; i < 100; i++) + { + if (i == 50) + ImGui::NextColumn(); + char buf[32]; + sprintf(buf, "%08x", i*5731); + ImGui::Button(buf, ImVec2(-1.0f, 0.0f)); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Widgets Width")) + { + static float f = 0.0f; + ImGui::Text("PushItemWidth(100)"); + ImGui::SameLine(); ShowHelpMarker("Fixed width."); + ImGui::PushItemWidth(100); + ImGui::DragFloat("float##1", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of window width."); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + ImGui::DragFloat("float##2", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f); + ImGui::DragFloat("float##3", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-100)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##4", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-1)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge"); + ImGui::PushItemWidth(-1); + ImGui::DragFloat("float##5", &f); + ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Basic Horizontal Layout")) + { + ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + + // Text + ImGui::Text("Two items: Hello"); ImGui::SameLine(); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Adjust spacing + ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Button + ImGui::AlignTextToFramePadding(); + ImGui::Text("Normal buttons"); ImGui::SameLine(); + ImGui::Button("Banana"); ImGui::SameLine(); + ImGui::Button("Apple"); ImGui::SameLine(); + ImGui::Button("Corniflower"); + + // Button + ImGui::Text("Small buttons"); ImGui::SameLine(); + ImGui::SmallButton("Like this one"); ImGui::SameLine(); + ImGui::Text("can fit within a text block."); + + // Aligned to arbitrary position. Easy/cheap column. + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::Text("x=150"); + ImGui::SameLine(300); ImGui::Text("x=300"); + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::SmallButton("x=150"); + ImGui::SameLine(300); ImGui::SmallButton("x=300"); + + // Checkbox + static bool c1=false,c2=false,c3=false,c4=false; + ImGui::Checkbox("My", &c1); ImGui::SameLine(); + ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); + ImGui::Checkbox("Is", &c3); ImGui::SameLine(); + ImGui::Checkbox("Rich", &c4); + + // Various + static float f0=1.0f, f1=2.0f, f2=3.0f; + ImGui::PushItemWidth(80); + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Y", &f1, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Z", &f2, 0.0f,5.0f); + ImGui::PopItemWidth(); + + ImGui::PushItemWidth(80); + ImGui::Text("Lists:"); + static int selection[4] = { 0, 1, 2, 3 }; + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); + } + ImGui::PopItemWidth(); + + // Dummy + ImVec2 sz(30,30); + ImGui::Button("A", sz); ImGui::SameLine(); + ImGui::Dummy(sz); ImGui::SameLine(); + ImGui::Button("B", sz); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Groups")) + { + ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + ImVec2 size = ImGui::GetItemRectSize(); + const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; + ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::SameLine(); + ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::Button("LEVERAGE\nBUZZWORD", size); + ImGui::SameLine(); + + ImGui::ListBoxHeader("List", size); + ImGui::Selectable("Selected", true); + ImGui::Selectable("Not Selected", false); + ImGui::ListBoxFooter(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text Baseline Alignment")) + { + ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)"); + + ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("One\nTwo\nThree"); + + ImGui::Button("HOP##1"); ImGui::SameLine(); + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("HOP##2"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("TEST##1"); ImGui::SameLine(); + ImGui::Text("TEST"); ImGui::SameLine(); + ImGui::SmallButton("TEST##2"); + + ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. + ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); + ImGui::Button("Widget##1"); ImGui::SameLine(); + ImGui::Text("Widget"); ImGui::SameLine(); + ImGui::SmallButton("Widget##2"); ImGui::SameLine(); + ImGui::Button("Widget##3"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). + bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + // Bullet + ImGui::Button("Button##3"); + ImGui::SameLine(0.0f, spacing); + ImGui::BulletText("Bullet text"); + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Node"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Scrolling")) + { + ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)"); + static bool track = true; + static int track_line = 50, scroll_to_px = 200; + ImGui::Checkbox("Track", &track); + ImGui::PushItemWidth(100); + ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %.0f"); + bool scroll_to = ImGui::Button("Scroll To Pos"); + ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %.0f px"); + ImGui::PopItemWidth(); + if (scroll_to) track = false; + + for (int i = 0; i < 5; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom"); + ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true); + if (scroll_to) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f); + for (int line = 0; line < 100; line++) + { + if (track && line == track_line) + { + ImGui::TextColored(ImColor(255,255,0), "Line %d", line); + ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } + else + { + ImGui::Text("Line %d", line); + } + } + float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag."); + ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing()*7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) + { + // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off + // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API) + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) + { + if (n > 0) ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf; + float hue = n*0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("Scroll from code"); ImGui::SameLine(); + ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) + { + ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window) + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + ImGui::End(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Clipping")) + { + static ImVec2 size(100, 100), offset(50, 20); + ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::TextWrapped("(Click and drag)"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y); + ImGui::InvisibleButton("##dummy", size); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } + ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x+size.x,pos.y+size.y), IM_COL32(90,90,120,255)); + ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x+offset.x,pos.y+offset.y), IM_COL32(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Popups & Modal windows")) + { + if (ImGui::TreeNode("Popups")) + { + ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); + + static int selected_fish = -1; + const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; + static bool toggles[] = { true, false, false, false, false }; + + // Simple selection popup + // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label) + if (ImGui::Button("Select..")) + ImGui::OpenPopup("select"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("select")) + { + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("toggle"); + if (ImGui::BeginPopup("toggle")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); + } + + if (ImGui::Button("Popup Menu..")) + ImGui::OpenPopup("FilePopup"); + if (ImGui::BeginPopup("FilePopup")) + { + ShowExampleMenuFile(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Context menus")) + { + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (IsItemHovered() && IsMouseClicked(0)) + // OpenPopup(id); + // return BeginPopup(id); + // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. + static float value = 0.5f; + ImGui::Text("Value = %.3f (<-- right-click here)", value); + if (ImGui::BeginPopupContextItem("item context menu")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::PushItemWidth(-1); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PopItemWidth(); + ImGui::EndPopup(); + } + + static char name[32] = "Label1"; + char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Modals")) + { + ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window."); + + if (ImGui::Button("Delete..")) + ImGui::OpenPopup("Delete?"); + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Separator(); + + //static int dummy_i = 0; + //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0"); + + static bool dont_ask_me_next_time = false; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); + ImGui::PopStyleVar(); + + if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::Button("Stacked modals..")) + ImGui::OpenPopup("Stacked 1"); + if (ImGui::BeginPopupModal("Stacked 1")) + { + ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDarkening] for darkening."); + static int item = 1; + ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal + + if (ImGui::Button("Add another modal..")) + ImGui::OpenPopup("Stacked 2"); + if (ImGui::BeginPopupModal("Stacked 2")) + { + ImGui::Text("Hello from Stacked The Second!"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. + // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here + // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Columns")) + { + ImGui::PushID("Columns"); + + // Basic columns + if (ImGui::TreeNode("Basic")) + { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) {} + //if (ImGui::Button(label, ImVec2(-1,0))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); + ImGui::Separator(); + const char* names[3] = { "One", "Two", "Three" }; + const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; + static int selected = -1; + for (int i = 0; i < 3; i++) + { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); ImGui::NextColumn(); + ImGui::Text(paths[i]); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Create multiple items in a same cell before switching to next column + if (ImGui::TreeNode("Mixed items")) + { + ImGui::Columns(3, "mixed"); + ImGui::Separator(); + + ImGui::Text("Hello"); + ImGui::Button("Banana"); + ImGui::NextColumn(); + + ImGui::Text("ImGui"); + ImGui::Button("Apple"); + static float foo = 1.0f; + ImGui::InputFloat("red", &foo, 0.05f, 0, 3); + ImGui::Text("An extra line here."); + ImGui::NextColumn(); + + ImGui::Text("Sailor"); + ImGui::Button("Corniflower"); + static float bar = 1.0f; + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); + ImGui::NextColumn(); + + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + if (ImGui::TreeNode("Word-wrapping")) + { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Borders")) + { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(4, NULL, v_borders); + for (int i = 0; i < 4*3; i++) + { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i); + ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + ImGui::Separator(); + ImGui::TreePop(); + } + + // Scrolling columns + /* + if (ImGui::TreeNode("Vertical Scrolling")) + { + ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); + ImGui::Columns(3); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::EndChild(); + ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); + ImGui::Columns(3); + for (int i = 0; i < 10; i++) + { + ImGui::Text("%04d", i); ImGui::NextColumn(); + ImGui::Text("Foobar"); ImGui::NextColumn(); + ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + */ + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) + { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + bool node_open = ImGui::TreeNode("Tree within single cell"); + ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell."); + if (node_open) + { + ImGui::Columns(2, "tree items"); + ImGui::Separator(); + if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn(); + if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::PopID(); + } + + if (ImGui::CollapsingHeader("Filtering")) + { + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + } + + if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) + { + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantMoveMouse: %d", io.WantMoveMouse); + ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); + + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to render a mouse cursor for you in software. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::CheckboxFlags("io.NavFlags: EnableGamepad", (unsigned int *)&io.NavFlags, ImGuiNavFlags_EnableGamepad); + ImGui::CheckboxFlags("io.NavFlags: EnableKeyboard", (unsigned int *)&io.NavFlags, ImGuiNavFlags_EnableKeyboard); + ImGui::CheckboxFlags("io.NavFlags: MoveMouse", (unsigned int *)&io.NavFlags, ImGuiNavFlags_MoveMouse); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to move your move cursor when using gamepad/keyboard navigation. NewFrame() will change io.MousePos and set the io.WantMoveMouse flag, your backend will need to apply the new mouse position."); + + if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) + { + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + + ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } + ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } + ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } + + ImGui::Button("Hovering me sets the\nkeyboard capture flag"); + if (ImGui::IsItemHovered()) + ImGui::CaptureKeyboardFromApp(true); + ImGui::SameLine(); + ImGui::Button("Holding me clears the\nthe keyboard capture flag"); + if (ImGui::IsItemActive()) + ImGui::CaptureKeyboardFromApp(false); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tabbing")) + { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "dummy"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushAllowKeyboardFocus(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets."); + ImGui::PopAllowKeyboardFocus(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focus from code")) + { + bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); + bool focus_3 = ImGui::Button("Focus on 3"); + int has_focus = 0; + static char buf[128] = "click on a button to set focus"; + + if (focus_1) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 1; + + if (focus_2) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 2; + + ImGui::PushAllowKeyboardFocus(false); + if (focus_3) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 3; + ImGui::PopAllowKeyboardFocus(); + + if (has_focus) + ImGui::Text("Item with focus: %d", has_focus); + else + ImGui::Text("Item with focus: "); + + // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item + static float f3[3] = { 0.0f, 0.0f, 0.0f }; + int focus_ahead = -1; + if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine(); + if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine(); + if (ImGui::Button("Focus on Z")) focus_ahead = 2; + if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); + ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); + + ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focused & Hovered Test")) + { + static bool embed_all_inside_a_child_window = false; + ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); + if (embed_all_inside_a_child_window) + ImGui::BeginChild("embeddingchild", ImVec2(0, ImGui::GetFontSize() * 25), true); + + // Testing IsWindowFocused() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n" + "IsWindowFocused(_AnyWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + + // Testing IsWindowHovered() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n" + "IsWindowHovered(_AnyWindow) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); + + // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) + ImGui::Button("ITEM"); + ImGui::BulletText( + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemhovered(_RectOnly) = %d\n", + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + + ImGui::BeginChild("child", ImVec2(0,50), true); + ImGui::Text("This is another child window for testing IsWindowHovered() flags."); + ImGui::EndChild(); + + if (embed_all_inside_a_child_window) + EndChild(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Dragging")) + { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + ImGui::Text("IsMouseDragging(%d):\n w/ default threshold: %d,\n w/ zero threshold: %d\n w/ large threshold: %d", + button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f)); + ImGui::Button("Drag Me"); + if (ImGui::IsItemActive()) + { + // Draw a line between the button and the mouse cursor + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRectFullScreen(); + draw_list->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); + draw_list->PopClipRect(); + + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold) + // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta() + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Mouse cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_Count_); + + ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_Count_; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + } + + ImGui::End(); +} + +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsClassic(); break; + case 1: ImGui::StyleColorsDark(); break; + case 2: ImGui::StyleColorsLight(); break; + } + return true; + } + return false; +} + +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = ImGui::GetIO(); + ImFont* font_current = ImGui::GetFont(); + if (ImGui::BeginCombo(label, font_current->GetDebugName())) + { + for (int n = 0; n < io.Fonts->Fonts.Size; n++) + if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current)) + io.FontDefault = io.Fonts->Fonts[n]; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker( + "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and documentation in misc/fonts/ for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} + +void ImGui::ShowStyleEditor(ImGuiStyle* ref) +{ + // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + init = false; + if (ref == NULL) + ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere."); + + if (ImGui::TreeNode("Rendering")) + { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(100); + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f); + if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f; + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Settings")) + { + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui::Text("BorderSize"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::Text("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::Text("Alignment"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Colors")) + { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export Unsaved")) + { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu."); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + + static ImGuiColorEditFlags alpha_flags = 0; + ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine(); + ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine(); + ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf); + + ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons. + // Read the FAQ and misc/fonts/README.txt about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i]; + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::TreePop(); + } + + bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size); + if (fonts_opened) + { + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::TreePop(); + } + ImGui::PushItemWidth(100); + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); + bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); + ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; + if (font_details_opened) + { + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font + ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, 0); + ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)"); + ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); + ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface)); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + { + ImFontConfig* cfg = &font->ConfigData[config_i]; + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); + } + if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + // Display all glyphs of the fonts in separate pages of 256 characters + const ImFontGlyph* glyph_fallback = font->FallbackGlyph; // Forcefully/dodgily make FindGlyph() return NULL on fallback, which isn't the default behavior. + font->FallbackGlyph = NULL; + for (int base = 0; base < 0x10000; base += 256) + { + int count = 0; + for (int n = 0; n < 256; n++) + count += font->FindGlyph((ImWchar)(base + n)) ? 1 : 0; + if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph")) + { + float cell_spacing = style.ItemSpacing.y; + ImVec2 cell_size(font->FontSize * 1, font->FontSize * 1); + ImVec2 base_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (int n = 0; n < 256; n++) + { + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size.x + cell_spacing), base_pos.y + (n / 16) * (cell_size.y + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size.x, cell_p1.y + cell_size.y); + const ImFontGlyph* glyph = font->FindGlyph((ImWchar)(base+n));; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); + font->RenderChar(draw_list, cell_size.x, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. + if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) + { + ImGui::BeginTooltip(); + ImGui::Text("Codepoint: U+%04X", base+n); + ImGui::Separator(); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); + ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + ImGui::EndTooltip(); + } + } + ImGui::Dummy(ImVec2((cell_size.x + cell_spacing) * 16, (cell_size.y + cell_spacing) * 16)); + ImGui::TreePop(); + } + } + font->FallbackGlyph = glyph_fallback; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + static float window_scale = 1.0f; + ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window + ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything + ImGui::PopItemWidth(); + ImGui::SetWindowFontScale(window_scale); + ImGui::TreePop(); + } + + ImGui::PopItemWidth(); +} + +// Demonstrate creating a fullscreen menu bar and populating it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(dummy menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + static bool b = true; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::Checkbox("Check", &b); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Colors")) + { + float sz = ImGui::GetTextLineHeight(); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName((ImGuiCol)i); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x+sz, p.y+sz), ImGui::GetColorU32((ImGuiCol)i)); + ImGui::Dummy(ImVec2(sz, sz)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + +// Demonstrate creating a window which gets auto-resized according to its content. +static void ShowExampleAppAutoResize(bool* p_open) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + static int lines = 10; + ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + ImGui::Text("%*sThis is line %d", i*4, "", i); // Pad with space to extend size horizontally + ImGui::End(); +} + +// Demonstrate creating a window with custom resize constraints. +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints // Helper functions to demonstrate programmatic constraints + { + static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } + static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + static bool auto_resize = false; + static int type = 0; + static int display_lines = 10; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step + + ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + { + const char* desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } + ImGui::PushItemWidth(200); + ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::PopItemWidth(); + ImGui::Checkbox("Auto-resize", &auto_resize); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + ImGui::End(); +} + +// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. +static void ShowExampleAppFixedOverlay(bool* p_open) +{ + const float DISTANCE = 10.0f; + static int corner = 0; + ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background + if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoFocusOnAppearing|ImGuiWindowFlags_NoNav)) + { + ImGui::Text("Simple overlay\nin the corner of the screen.\n(right-click to change position)"); + ImGui::Separator(); + ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + if (p_open && ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndPopup(); + } + ImGui::End(); + } +} + +// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. +// This apply to regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. +static void ShowExampleAppWindowTitles(bool*) +{ + // By default, Windows are uniquely identified by their title. + // You can use the "##" and "###" markers to manipulate the display/ID. + + // Using "##" to display same title but have unique identifier. + ImGui::SetNextWindowPos(ImVec2(100,100), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##1"); + ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(100,200), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##2"); + ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); + ImGui::End(); + + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" + char buf[128]; + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime()/0.25f)&3], ImGui::GetFrameCount()); + ImGui::SetNextWindowPos(ImVec2(100,300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(350,560), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Custom rendering", p_open)) + { + ImGui::End(); + return; + } + + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc. + // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4. + // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types) + // In this example we are not using the maths operators! + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Primitives + ImGui::Text("Primitives"); + static float sz = 36.0f; + static ImVec4 col = ImVec4(1.0f,1.0f,0.4f,1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); + ImGui::ColorEdit3("Color", &col.x); + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImU32 col32 = ImColor(col); + float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; + for (int n = 0; n < 2; n++) + { + float thickness = (n == 0) ? 1.0f : 4.0f; + draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, thickness); x += sz+spacing; + draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, thickness); x += spacing; + draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, thickness); + x = p.x + 4; + y += sz+spacing; + } + draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; + draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255)); + ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); + } + ImGui::Separator(); + { + static ImVector points; + static bool adding_line = false; + ImGui::Text("Canvas example"); + if (ImGui::Button("Clear")) points.clear(); + if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } } + ImGui::Text("Left-click and drag to add lines,\nRight-click to undo"); + + // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered() + // However you can draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos(). + // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max). + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + if (canvas_size.x < 50.0f) canvas_size.x = 50.0f; + if (canvas_size.y < 50.0f) canvas_size.y = 50.0f; + draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(50,50,50,255), IM_COL32(50,50,60,255), IM_COL32(60,60,70,255), IM_COL32(50,50,60,255)); + draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(255,255,255,255)); + + bool adding_preview = false; + ImGui::InvisibleButton("canvas", canvas_size); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + if (adding_line) + { + adding_preview = true; + points.push_back(mouse_pos_in_canvas); + if (!ImGui::IsMouseDown(0)) + adding_line = adding_preview = false; + } + if (ImGui::IsItemHovered()) + { + if (!adding_line && ImGui::IsMouseClicked(0)) + { + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (ImGui::IsMouseClicked(1) && !points.empty()) + { + adding_line = adding_preview = false; + points.pop_back(); + points.pop_back(); + } + } + draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x+canvas_size.x, canvas_pos.y+canvas_size.y), true); // clip lines within the canvas (if we resize it, etc.) + for (int i = 0; i < points.Size - 1; i += 2) + draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i+1].x, canvas_pos.y + points[i+1].y), IM_COL32(255,255,0,255), 2.0f); + draw_list->PopClipRect(); + if (adding_preview) + points.pop_back(); + } + ImGui::End(); +} + +// Demonstrating creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + bool ScrollToBottom; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImVector Commands; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); // "classify" is here to provide an example of "C"+[tab] completing to "CL" and displaying matches. + AddLog("Welcome to ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } + static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + ScrollToBottom = true; + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf)-1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close")) + *p_open = false; + ImGui::EndPopup(); + } + + ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); + if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + static ImGuiTextFilter filter; + filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::PopStyleVar(); + ImGui::Separator(); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. + // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. + // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: + // ImGuiListClipper clipper(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // However take note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! + // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!filter.PassFilter(item)) + continue; + ImVec4 col = col_default_text; + if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f); + else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(item); + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + if (ScrollToBottom) + ImGui::SetScrollHere(); + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + bool reclaim_focus = false; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) + { + char* input_end = InputBuf+strlen(InputBuf); + while (input_end > InputBuf && input_end[-1] == ' ') { input_end--; } *input_end = 0; + if (InputBuf[0]) + ExecCommand(InputBuf); + strcpy(InputBuf, ""); + reclaim_focus = true; + } + + // Demonstrate keeping focus on the input box + ImGui::SetItemDefaultFocus(); + if (reclaim_focus) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size-1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + } + + static int TextEditCallbackStub(ImGuiTextEditCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiTextEditCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing + data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + data->CursorPos = data->SelectionStart = data->SelectionEnd = data->BufTextLen = (int)snprintf(data->Buf, (size_t)data->BufSize, "%s", (HistoryPos >= 0) ? History[HistoryPos] : ""); + data->BufDirty = true; + } + } + } + return 0; + } +}; + +static void ShowExampleAppConsole(bool* p_open) +{ + static ExampleAppConsole console; + console.Draw("Example: Console", p_open); +} + +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct ExampleAppLog +{ + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear() { Buf.clear(); LineOffsets.clear(); } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open = NULL) + { + ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver); + ImGui::Begin(title, p_open); + if (ImGui::Button("Clear")) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + if (copy) ImGui::LogToClipboard(); + + if (Filter.IsActive()) + { + const char* buf_begin = Buf.begin(); + const char* line = buf_begin; + for (int line_no = 0; line != NULL; line_no++) + { + const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL; + if (Filter.PassFilter(line, line_end)) + ImGui::TextUnformatted(line, line_end); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } + else + { + ImGui::TextUnformatted(Buf.begin()); + } + + if (ScrollToBottom) + ImGui::SetScrollHere(1.0f); + ScrollToBottom = false; + ImGui::EndChild(); + ImGui::End(); + } +}; + +// Demonstrate creating a simple log window with basic filtering. +static void ShowExampleAppLog(bool* p_open) +{ + static ExampleAppLog log; + + // Demo: add random items (unless Ctrl is held) + static float last_time = -1.0f; + float time = ImGui::GetTime(); + if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) + { + const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; + log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); + last_time = time; + } + + log.Draw("Example: Log", p_open); +} + +// Demonstrate create a window with multiple child windows. +static void ShowExampleAppLayout(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // left + static int selected = 0; + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) + { + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + ImGui::SameLine(); + + // right + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + ImGui::End(); +} + +// Demonstrate create a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) + { + ImGui::End(); + return; + } + + ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API."); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2)); + ImGui::Columns(2); + ImGui::Separator(); + + struct funcs + { + static void ShowDummyObject(const char* prefix, int uid) + { + ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::NextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("my sailor is rich"); + ImGui::NextColumn(); + if (node_open) + { + static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f }; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) + { + ShowDummyObject("Child", 424242); + } + else + { + ImGui::AlignTextToFramePadding(); + // Here we use a Selectable (instead of Text) to highlight on hover + //ImGui::Text("Field_%d", i); + char label[32]; + sprintf(label, "Field_%d", i); + ImGui::Bullet(); + ImGui::Selectable(label); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + if (i >= 5) + ImGui::InputFloat("##value", &dummy_members[i], 1.0f); + else + ImGui::DragFloat("##value", &dummy_members[i], 0.01f); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + }; + + // Iterate dummy objects with dummy members (all the same data) + for (int obj_i = 0; obj_i < 3; obj_i++) + funcs::ShowDummyObject("Object", obj_i); + + ImGui::Columns(1); + ImGui::Separator(); + ImGui::PopStyleVar(); + ImGui::End(); +} + +// Demonstrate/test rendering huge amount of text, and the incidence of clipping. +static void ShowExampleAppLongText(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) + { + ImGui::End(); + return; + } + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { log.clear(); lines = 0; } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) + { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) + { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: + { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + ImGuiListClipper clipper(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + ImGui::EndChild(); + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowDemoWindow(bool*) {} +void ImGui::ShowUserGuide() {} +void ImGui::ShowStyleEditor(ImGuiStyle*) {} + +#endif diff --git a/apps/bench_shared_offscreen/imgui/imgui_draw.cpp b/apps/bench_shared_offscreen/imgui/imgui_draw.cpp new file mode 100644 index 00000000..118acabc --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_draw.cpp @@ -0,0 +1,2940 @@ +// dear imgui, v1.60 WIP +// (drawing and font code) + +// Contains implementation for +// - Default styles +// - ImDrawList +// - ImDrawData +// - ImFontAtlas +// - ImFont +// - Default font data + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#ifdef _WIN32 +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#elif defined(__GLIBC__) || defined(__sun) +#include // alloca +#else +#include // alloca +#endif +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" // warning : possible misuse of comma operator here // +#endif +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#endif + +//------------------------------------------------------------------------- +// STB libraries implementation +//------------------------------------------------------------------------- + +//#define IMGUI_STB_NAMESPACE ImGuiStb +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION + +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE +{ +#endif + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] +#endif + +#define STBRP_ASSERT(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#include "stb_rect_pack.h" + +#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) +#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) +#define STBTT_assert(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#include "stb_truetype.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +#ifdef IMGUI_STB_NAMESPACE +} // namespace ImGuiStb +using namespace IMGUI_STB_NAMESPACE; +#endif + +//----------------------------------------------------------------------------- +// Style functions +//----------------------------------------------------------------------------- + +void ImGui::StyleColorsDark(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.41f, 0.41f, 0.41f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); +} + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); +} + +// Those light colors are better suited with a thicker font than the default one + FrameBorder +void ImGui::StyleColorsLight(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + //colors[ImGuiCol_TextHovered] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + //colors[ImGuiCol_TextActive] = ImVec4(1.00f, 1.00f, 0.00f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.59f, 0.59f, 0.59f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); +} + +//----------------------------------------------------------------------------- +// ImDrawListData +//----------------------------------------------------------------------------- + +ImDrawListSharedData::ImDrawListSharedData() +{ + Font = NULL; + FontSize = 0.0f; + CurveTessellationTol = 0.0f; + ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); + + // Const data + for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++) + { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12); + CircleVtx12[i] = ImVec2(cosf(a), sinf(a)); + } +} + +//----------------------------------------------------------------------------- +// ImDrawList +//----------------------------------------------------------------------------- + +void ImDrawList::Clear() +{ + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + // NB: Do not clear channels so our allocations are re-used after the first frame. +} + +void ImDrawList::ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + for (int i = 0; i < _Channels.Size; i++) + { + if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0])); // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i].CmdBuffer.clear(); + _Channels[i].IdxBuffer.clear(); + } + _Channels.clear(); +} + +// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds +#define GetCurrentClipRect() (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1] : _Data->ClipRectFullscreen) +#define GetCurrentTextureId() (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL) + +void ImDrawList::AddDrawCmd() +{ + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = GetCurrentClipRect(); + draw_cmd.TextureId = GetCurrentTextureId(); + + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); + CmdBuffer.push_back(draw_cmd); +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +{ + ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL) + { + AddDrawCmd(); + current_cmd = &CmdBuffer.back(); + } + current_cmd->UserCallback = callback; + current_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. +// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. +void ImDrawList::UpdateClipRect() +{ + // If current command is used with different settings we need to add a new command + const ImVec4 curr_clip_rect = GetCurrentClipRect(); + ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->ClipRect = curr_clip_rect; +} + +void ImDrawList::UpdateTextureID() +{ + // If current command is used with different settings we need to add a new command + const ImTextureID curr_texture_id = GetCurrentTextureId(); + ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->TextureId = curr_texture_id; +} + +#undef GetCurrentClipRect +#undef GetCurrentTextureId + +// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) +void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect && _ClipRectStack.Size) + { + ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1]; + if (cr.x < current.x) cr.x = current.x; + if (cr.y < current.y) cr.y = current.y; + if (cr.z > current.z) cr.z = current.z; + if (cr.w > current.w) cr.w = current.w; + } + cr.z = ImMax(cr.x, cr.z); + cr.w = ImMax(cr.y, cr.w); + + _ClipRectStack.push_back(cr); + UpdateClipRect(); +} + +void ImDrawList::PushClipRectFullScreen() +{ + PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); +} + +void ImDrawList::PopClipRect() +{ + IM_ASSERT(_ClipRectStack.Size > 0); + _ClipRectStack.pop_back(); + UpdateClipRect(); +} + +void ImDrawList::PushTextureID(const ImTextureID& texture_id) +{ + _TextureIdStack.push_back(texture_id); + UpdateTextureID(); +} + +void ImDrawList::PopTextureID() +{ + IM_ASSERT(_TextureIdStack.Size > 0); + _TextureIdStack.pop_back(); + UpdateTextureID(); +} + +void ImDrawList::ChannelsSplit(int channels_count) +{ + IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) + _Channels.resize(channels_count); + _ChannelsCount = channels_count; + + // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer + // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. + // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } + else + { + _Channels[i].CmdBuffer.resize(0); + _Channels[i].IdxBuffer.resize(0); + } + if (_Channels[i].CmdBuffer.Size == 0) + { + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = _ClipRectStack.back(); + draw_cmd.TextureId = _TextureIdStack.back(); + _Channels[i].CmdBuffer.push_back(draw_cmd); + } + } +} + +void ImDrawList::ChannelsMerge() +{ + // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. + if (_ChannelsCount <= 1) + return; + + ChannelsSetCurrent(0); + if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0) + CmdBuffer.pop_back(); + + int new_cmd_buffer_count = 0, new_idx_buffer_count = 0; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0) + ch.CmdBuffer.pop_back(); + new_cmd_buffer_count += ch.CmdBuffer.Size; + new_idx_buffer_count += ch.IdxBuffer.Size; + } + CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count); + IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count); + + ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count; + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } + if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } + } + UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. + _ChannelsCount = 1; +} + +void ImDrawList::ChannelsSetCurrent(int idx) +{ + IM_ASSERT(idx < _ChannelsCount); + if (_ChannelsCurrent == idx) return; + memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times + memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer)); + _ChannelsCurrent = idx; + memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer)); + memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer)); + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size; +} + +// NB: this can be called with negative count for removing primitives (as long as the result does not underflow) +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1]; + draw_cmd.ElemCount += idx_count; + + int vtx_buffer_old_size = VtxBuffer.Size; + VtxBuffer.resize(vtx_buffer_old_size + vtx_count); + _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + + int idx_buffer_old_size = IdxBuffer.Size; + IdxBuffer.resize(idx_buffer_old_size + idx_count); + _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; +} + +// Fully unrolled with inline call to keep our debug builds decently fast. +void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +// TODO: Thickness anti-aliased lines cap are missing their AA fringe. +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) +{ + if (points_count < 2) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + int count = points_count; + if (!closed) + count = points_count-1; + + const bool thick_line = thickness > 1.0f; + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + // Anti-aliased stroke + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const int idx_count = thick_line ? count*18 : count*12; + const int vtx_count = thick_line ? points_count*4 : points_count*3; + PrimReserve(idx_count, vtx_count); + + // Temporary buffer + ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); + ImVec2* temp_points = temp_normals + points_count; + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + ImVec2 diff = points[i2] - points[i1]; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i1].x = diff.y; + temp_normals[i1].y = -diff.x; + } + if (!closed) + temp_normals[points_count-1] = temp_normals[points_count-2]; + + if (!thick_line) + { + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; + temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; + temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; + temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE; + temp_points[i2*2+0] = points[i2] + dm; + temp_points[i2*2+1] = points[i2] - dm; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1); + _IdxWritePtr += 12; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; + _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans; + _VtxWritePtr += 3; + } + } + else + { + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE); + ImVec2 dm_in = dm * half_inner_thickness; + temp_points[i2*4+0] = points[i2] + dm_out; + temp_points[i2*4+1] = points[i2] + dm_in; + temp_points[i2*4+2] = points[i2] - dm_in; + temp_points[i2*4+3] = points[i2] - dm_out; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+1); _IdxWritePtr[1] = (ImDrawIdx)(idx1+1); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3); + _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2); + _IdxWritePtr += 18; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Stroke + const int idx_count = count*6; + const int vtx_count = count*4; // FIXME-OPT: Not sharing edges + PrimReserve(idx_count, vtx_count); + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + const ImVec2& p1 = points[i1]; + const ImVec2& p2 = points[i2]; + ImVec2 diff = p2 - p1; + diff *= ImInvLength(diff, 1.0f); + + const float dx = diff.x * (thickness * 0.5f); + const float dy = diff.y * (thickness * 0.5f); + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2); + _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3); + _IdxWritePtr += 6; + _VtxCurrentIdx += 4; + } + } +} + +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count-2)*3 + points_count*6; + const int vtx_count = (points_count*2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx+1; + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); + _IdxWritePtr += 3; + } + + // Compute normals + ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + ImVec2 diff = p1 - p0; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i0].x = diff.y; + temp_normals[i0].y = -diff.x; + } + + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + ImVec2 dm = (n0 + n1) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count-2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius == 0.0f || a_min_of_12 > a_max_of_12) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); + for (int a = a_min_of_12; a <= a_max_of_12; a++) + { + const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)]; + _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); + } +} + +void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) +{ + if (radius == 0.0f) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (num_segments + 1)); + for (int i = 0; i <= num_segments; i++) + { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _Path.push_back(ImVec2(centre.x + cosf(a) * radius, centre.y + sinf(a) * radius)); + } +} + +static void PathBezierToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy)) + { + path->push_back(ImVec2(x4, y4)); + } + else if (level < 10) + { + float x12 = (x1+x2)*0.5f, y12 = (y1+y2)*0.5f; + float x23 = (x2+x3)*0.5f, y23 = (y2+y3)*0.5f; + float x34 = (x3+x4)*0.5f, y34 = (y3+y4)*0.5f; + float x123 = (x12+x23)*0.5f, y123 = (y12+y23)*0.5f; + float x234 = (x23+x34)*0.5f, y234 = (y23+y34)*0.5f; + float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f; + + PathBezierToCasteljau(path, x1,y1, x12,y12, x123,y123, x1234,y1234, tess_tol, level+1); + PathBezierToCasteljau(path, x1234,y1234, x234,y234, x34,y34, x4,y4, tess_tol, level+1); + } +} + +void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + // Auto-tessellated + PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + { + float t = t_step * i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t*t*t; + _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y)); + } + } +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) +{ + rounding = ImMin(rounding, fabsf(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, fabsf(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); + + if (rounding <= 0.0f || rounding_corners == 0) + { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } + else + { + const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; + const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; + const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; + const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); + } +} + +void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(a + ImVec2(0.5f,0.5f)); + PathLineTo(b + ImVec2(0.5f,0.5f)); + PathStroke(col, false, thickness); +} + +// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly. +void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.5f,0.5f), rounding, rounding_corners_flags); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (rounding > 0.0f) + { + PathRect(a, b, rounding, rounding_corners_flags); + PathFillConvex(col); + } + else + { + PrimReserve(6, 4); + PrimRect(a, b, col); + } +} + +void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) +{ + if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + PrimReserve(6, 4); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3)); + PrimWriteVtx(a, uv, col_upr_left); + PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right); + PrimWriteVtx(c, uv, col_bot_right); + PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left); +} + +void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathFillConvex(col); +} + +void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathFillConvex(col); +} + +void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius, 0.0f, a_max, num_segments); + PathFillConvex(col); +} + +void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(pos0); + PathBezierCurveTo(cp0, cp1, pos1, num_segments); + PathStroke(col, false, thickness); +} + +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // Pull default font/size from the shared ImDrawListSharedData instance + if (font == NULL) + font = _Data->Font; + if (font_size == 0.0f) + font_size = _Data->FontSize; + + IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + + ImVec4 clip_rect = _ClipRectStack.back(); + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); +} + +void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) +{ + AddText(NULL, 0.0f, pos, col, text_begin, text_end); +} + +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(a, b, uv_a, uv_b, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) + { + AddImage(user_texture_id, a, b, uv_a, uv_b, col); + return; + } + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + int vert_start_idx = VtxBuffer.Size; + PathRect(a, b, rounding, rounding_corners); + PathFillConvex(col); + int vert_end_idx = VtxBuffer.Size; + ImGui::ShadeVertsLinearUV(VtxBuffer.Data + vert_start_idx, VtxBuffer.Data + vert_end_idx, a, b, uv_a, uv_b, true); + + if (push_texture_id) + PopTextureID(); +} + +//----------------------------------------------------------------------------- +// ImDrawData +//----------------------------------------------------------------------------- + +// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! +void ImDrawData::DeIndexAllBuffers() +{ + ImVector new_vtx_buffer; + TotalVtxCount = TotalIdxCount = 0; + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + if (cmd_list->IdxBuffer.empty()) + continue; + new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); + for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; + cmd_list->VtxBuffer.swap(new_vtx_buffer); + cmd_list->IdxBuffer.resize(0); + TotalVtxCount += cmd_list->VtxBuffer.Size; + } +} + +// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +void ImDrawData::ScaleClipRects(const ImVec2& scale) +{ + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y); + } + } +} + +//----------------------------------------------------------------------------- +// Shade functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Scan and shade backward from the end of given vertices. Assume vertices are text only (= vert_start..vert_end going left to right) so we can break as soon as we are out the gradient bounds. +void ImGui::ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x) +{ + float gradient_extent_x = gradient_p1_x - gradient_p0_x; + float gradient_inv_length2 = 1.0f / (gradient_extent_x * gradient_extent_x); + int full_alpha_count = 0; + for (ImDrawVert* vert = vert_end - 1; vert >= vert_start; vert--) + { + float d = (vert->pos.x - gradient_p0_x) * (gradient_extent_x); + float alpha_mul = 1.0f - ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + if (alpha_mul >= 1.0f && ++full_alpha_count > 2) + return; // Early out + int a = (int)(((vert->col >> IM_COL32_A_SHIFT) & 0xFF) * alpha_mul); + vert->col = (vert->col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); + } +} + +// Distribute UV over (a, b) rectangle +void ImGui::ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + const ImVec2 scale = ImVec2( + size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, + size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + + if (clamp) + { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } + else + { + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + FontData = NULL; + FontDataSize = 0; + FontDataOwnedByAtlas = true; + FontNo = 0; + SizePixels = 0.0f; + OversampleH = 3; + OversampleV = 1; + PixelSnapH = false; + GlyphExtraSpacing = ImVec2(0.0f, 0.0f); + GlyphOffset = ImVec2(0.0f, 0.0f); + GlyphRanges = NULL; + MergeMode = false; + RasterizerFlags = 0x00; + RasterizerMultiply = 1.0f; + memset(Name, 0, sizeof(Name)); + DstFont = NULL; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas +//----------------------------------------------------------------------------- + +// A work of art lies ahead! (. = white layer, X = black layer, others are blank) +// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. +const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 90; +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX" + "..- -X.....X- X.X - X.X -X.....X - X.....X" + "--- -XXX.XXX- X...X - X...X -X....X - X....X" + "X - X.X - X.....X - X.....X -X...X - X...X" + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X" + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X" + "X..X - X.X - X.X - X.X -XX X.X - X.X XX" + "X...X - X.X - X.X - XX X.X XX - X.X - X.X " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X " + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X " + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X " + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X " + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X " + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X " + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X " + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------" + "X.X X..X - -X.......X- X.......X - XX XX - " + "XX X..X - - X.....X - X.....X - X.X X.X - " + " X..X - X...X - X...X - X..X X..X - " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - " + "------------ - X - X -X.....................X- " + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_Count_][3] = +{ + // Pos ........ Size ......... Offset ...... + { ImVec2(0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow + { ImVec2(13,0), ImVec2(7,16), ImVec2( 4, 8) }, // ImGuiMouseCursor_TextInput + { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll + { ImVec2(21,0), ImVec2( 9,23), ImVec2( 5,11) }, // ImGuiMouseCursor_ResizeNS + { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 5) }, // ImGuiMouseCursor_ResizeEW + { ImVec2(73,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNESW + { ImVec2(55,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNWSE +}; + +ImFontAtlas::ImFontAtlas() +{ + Flags = 0x00; + TexID = NULL; + TexDesiredWidth = 0; + TexGlyphPadding = 1; + + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; + TexWidth = TexHeight = 0; + TexUvScale = ImVec2(0.0f, 0.0f); + TexUvWhitePixel = ImVec2(0.0f, 0.0f); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + for (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) + { + ImGui::MemFree(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) + { + Fonts[i]->ConfigData = NULL; + Fonts[i]->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +void ImFontAtlas::ClearTexData() +{ + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + +void ImFontAtlas::ClearFonts() +{ + for (int i = 0; i < Fonts.Size; i++) + IM_DELETE(Fonts[i]); + Fonts.clear(); +} + +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Build atlas on demand + if (TexPixelsAlpha8 == NULL) + { + if (ConfigData.empty()) + AddFontDefault(); + Build(); + } + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Convert to RGBA32 format on demand + // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels = NULL; + GetTexDataAsAlpha8(&pixels, NULL, NULL); + if (pixels) + { + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + } + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +{ + IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT(font_cfg->SizePixels > 0.0f); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + + ConfigData.push_back(*font_cfg); + ImFontConfig& new_font_cfg = ConfigData.back(); + if (!new_font_cfg.DstFont) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) + { + new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize); + new_font_cfg.FontDataOwnedByAtlas = true; + memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + } + + // Invalidate texture + ClearTexData(); + return new_font_cfg.DstFont; +} + +// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) +static unsigned int stb_decompress_length(unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); +static const char* GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } +static void Decode85(const unsigned char* src, unsigned char* dst) +{ + while (*src) + { + unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px"); + if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f; + + const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, GetGlyphRangesDefault()); + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + int data_size = 0; + void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); + if (!data) + { + IM_ASSERT(0); // Could not load file. + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') + { + // Store a short copy of filename into into the font name for convenience + const char* p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + snprintf(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges); +} + +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels; + if (glyph_ranges) + font_cfg.GlyphRanges = glyph_ranges; + return AddFont(&font_cfg); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + const unsigned int buf_decompressed_size = stb_decompress_length((unsigned char*)compressed_ttf_data); + unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed_data, (unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontDataOwnedByAtlas = true; + return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) +{ + int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size); + Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); + ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + ImGui::MemFree(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) +{ + IM_ASSERT(id >= 0x10000); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) +{ + IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed + *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); + *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); +} + +bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) +{ + if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_Count_) + return false; + if (Flags & ImFontAtlasFlags_NoMouseCursors) + return false; + + ImFontAtlas::CustomRect& r = CustomRects[CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r.X, (float)r.Y); + ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; + *out_size = size; + *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; + out_uv_border[0] = (pos) * TexUvScale; + out_uv_border[1] = (pos + size) * TexUvScale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + out_uv_fill[0] = (pos) * TexUvScale; + out_uv_fill[1] = (pos + size) * TexUvScale; + return true; +} + +bool ImFontAtlas::Build() +{ + return ImFontAtlasBuildWithStbTruetype(this); +} + +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + +bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->ConfigData.Size > 0); + + ImFontAtlasBuildRegisterDefaultCustomRects(atlas); + + atlas->TexID = NULL; + atlas->TexWidth = atlas->TexHeight = 0; + atlas->TexUvScale = ImVec2(0.0f, 0.0f); + atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); + atlas->ClearTexData(); + + // Count glyphs/ranges + int total_glyphs_count = 0; + int total_ranges_count = 0; + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + if (!cfg.GlyphRanges) + cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++) + total_glyphs_count += (in_range[1] - in_range[0]) + 1; + } + + // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. + // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. + atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; + atlas->TexHeight = 0; + + // Start packing + const int max_tex_height = 1024*32; + stbtt_pack_context spc = {}; + if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL)) + return false; + stbtt_PackSetOversampling(&spc, 1, 1); + + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + + // Initialize font information (so we can error without any cleanup) + struct ImFontTempBuildData + { + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + int RectsCount; + stbtt_pack_range* Ranges; + int RangesCount; + }; + ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData)); + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0); + if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + { + atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure + ImGui::MemFree(tmp_array); + return false; + } + } + + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; + stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); + stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); + stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); + memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); + memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. + memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); + + // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + + // Setup ranges + int font_glyphs_count = 0; + int font_ranges_count = 0; + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) + font_glyphs_count += (in_range[1] - in_range[0]) + 1; + tmp.Ranges = buf_ranges + buf_ranges_n; + tmp.RangesCount = font_ranges_count; + buf_ranges_n += font_ranges_count; + for (int i = 0; i < font_ranges_count; i++) + { + const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; + stbtt_pack_range& range = tmp.Ranges[i]; + range.font_size = cfg.SizePixels; + range.first_unicode_codepoint_in_range = in_range[0]; + range.num_chars = (in_range[1] - in_range[0]) + 1; + range.chardata_for_range = buf_packedchars + buf_packedchars_n; + buf_packedchars_n += range.num_chars; + } + + // Pack + tmp.Rects = buf_rects + buf_rects_n; + tmp.RectsCount = font_glyphs_count; + buf_rects_n += font_glyphs_count; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + IM_ASSERT(n == font_glyphs_count); + stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n); + + // Extend texture height + for (int i = 0; i < n; i++) + if (tmp.Rects[i].was_packed) + atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h); + } + IM_ASSERT(buf_rects_n == total_glyphs_count); + IM_ASSERT(buf_packedchars_n == total_glyphs_count); + IM_ASSERT(buf_ranges_n == total_ranges_count); + + // Create texture + atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); + atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); + memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + spc.pixels = atlas->TexPixelsAlpha8; + spc.height = atlas->TexHeight; + + // Second pass: render font characters + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); + } + tmp.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + ImGui::MemFree(buf_rects); + buf_rects = NULL; + + // Third pass: setup ImFont and glyphs for runtime + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) + + const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = unscaled_ascent * font_scale; + const float descent = unscaled_descent * font_scale; + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float off_x = cfg.GlyphOffset.x; + const float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); + + for (int i = 0; i < tmp.RangesCount; i++) + { + stbtt_pack_range& range = tmp.Ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) + { + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + const int codepoint = range.first_unicode_codepoint_in_range + char_idx; + if (cfg.MergeMode && dst_font->FindGlyph((unsigned short)codepoint)) + continue; + + stbtt_aligned_quad q; + float dummy_x = 0.0f, dummy_y = 0.0f; + stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); + dst_font->AddGlyph((ImWchar)codepoint, q.x0 + off_x, q.y0 + off_y, q.x1 + off_x, q.y1 + off_y, q.s0, q.t0, q.s1, q.t1, pc.xadvance); + } + } + } + + // Cleanup temporaries + ImGui::MemFree(buf_packedchars); + ImGui::MemFree(buf_ranges); + ImGui::MemFree(tmp_array); + + ImFontAtlasBuildFinish(atlas); + + return true; +} + +void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) +{ + if (atlas->CustomRectIds[0] >= 0) + return; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + else + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, 2, 2); +} + +void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) + { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) +{ + stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; + + ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + + ImVector pack_rects; + pack_rects.resize(user_rects.Size); + memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); + for (int i = 0; i < user_rects.Size; i++) + { + pack_rects[i].w = user_rects[i].Width; + pack_rects[i].h = user_rects[i].Height; + } + stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); + for (int i = 0; i < pack_rects.Size; i++) + if (pack_rects[i].was_packed) + { + user_rects[i].X = pack_rects[i].x; + user_rects[i].Y = pack_rects[i].y; + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + } +} + +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->CustomRectIds[0] >= 0); + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + IM_ASSERT(r.IsPacked()); + + const int w = atlas->TexWidth; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + { + // Render/copy pixels + IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * 2 + 1 && r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++) + for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++) + { + const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * w; + const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00; + atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00; + } + } + else + { + IM_ASSERT(r.Width == 2 && r.Height == 2); + const int offset = (int)(r.X) + (int)(r.Y) * w; + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + } + atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * atlas->TexUvScale.x, (r.Y + 0.5f) * atlas->TexUvScale.y); +} + +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data block + ImFontAtlasBuildRenderDefaultTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; + if (r.Font == NULL || r.ID > 0x10000) + continue; + + IM_ASSERT(r.Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + atlas->Fonts[i]->BuildLookupTable(); +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD79D, // Korean characters + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesChinese() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() +{ + // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. + // This encoding is designed to helps us reduce the source code size. + // FIXME: Source a list of the revised 2136 joyo kanji list from 2010 and rebuild this. + // The current list was sourced from http://theinstructionlimit.com/author/renaudbedardrenaudbedard/page/3 + // Note that you may use ImFontAtlas::GlyphRangesBuilder to create your own ranges, by merging existing ranges or adding new characters. + static const short offsets_from_0x4E00[] = + { + -1,0,1,3,0,0,0,0,1,0,5,1,1,0,7,4,6,10,0,1,9,9,7,1,3,19,1,10,7,1,0,1,0,5,1,0,6,4,2,6,0,0,12,6,8,0,3,5,0,1,0,9,0,0,8,1,1,3,4,5,13,0,0,8,2,17, + 4,3,1,1,9,6,0,0,0,2,1,3,2,22,1,9,11,1,13,1,3,12,0,5,9,2,0,6,12,5,3,12,4,1,2,16,1,1,4,6,5,3,0,6,13,15,5,12,8,14,0,0,6,15,3,6,0,18,8,1,6,14,1, + 5,4,12,24,3,13,12,10,24,0,0,0,1,0,1,1,2,9,10,2,2,0,0,3,3,1,0,3,8,0,3,2,4,4,1,6,11,10,14,6,15,3,4,15,1,0,0,5,2,2,0,0,1,6,5,5,6,0,3,6,5,0,0,1,0, + 11,2,2,8,4,7,0,10,0,1,2,17,19,3,0,2,5,0,6,2,4,4,6,1,1,11,2,0,3,1,2,1,2,10,7,6,3,16,0,8,24,0,0,3,1,1,3,0,1,6,0,0,0,2,0,1,5,15,0,1,0,0,2,11,19, + 1,4,19,7,6,5,1,0,0,0,0,5,1,0,1,9,0,0,5,0,2,0,1,0,3,0,11,3,0,2,0,0,0,0,0,9,3,6,4,12,0,14,0,0,29,10,8,0,14,37,13,0,31,16,19,0,8,30,1,20,8,3,48, + 21,1,0,12,0,10,44,34,42,54,11,18,82,0,2,1,2,12,1,0,6,2,17,2,12,7,0,7,17,4,2,6,24,23,8,23,39,2,16,23,1,0,5,1,2,15,14,5,6,2,11,0,8,6,2,2,2,14, + 20,4,15,3,4,11,10,10,2,5,2,1,30,2,1,0,0,22,5,5,0,3,1,5,4,1,0,0,2,2,21,1,5,1,2,16,2,1,3,4,0,8,4,0,0,5,14,11,2,16,1,13,1,7,0,22,15,3,1,22,7,14, + 22,19,11,24,18,46,10,20,64,45,3,2,0,4,5,0,1,4,25,1,0,0,2,10,0,0,0,1,0,1,2,0,0,9,1,2,0,0,0,2,5,2,1,1,5,5,8,1,1,1,5,1,4,9,1,3,0,1,0,1,1,2,0,0, + 2,0,1,8,22,8,1,0,0,0,0,4,2,1,0,9,8,5,0,9,1,30,24,2,6,4,39,0,14,5,16,6,26,179,0,2,1,1,0,0,0,5,2,9,6,0,2,5,16,7,5,1,1,0,2,4,4,7,15,13,14,0,0, + 3,0,1,0,0,0,2,1,6,4,5,1,4,9,0,3,1,8,0,0,10,5,0,43,0,2,6,8,4,0,2,0,0,9,6,0,9,3,1,6,20,14,6,1,4,0,7,2,3,0,2,0,5,0,3,1,0,3,9,7,0,3,4,0,4,9,1,6,0, + 9,0,0,2,3,10,9,28,3,6,2,4,1,2,32,4,1,18,2,0,3,1,5,30,10,0,2,2,2,0,7,9,8,11,10,11,7,2,13,7,5,10,0,3,40,2,0,1,6,12,0,4,5,1,5,11,11,21,4,8,3,7, + 8,8,33,5,23,0,0,19,8,8,2,3,0,6,1,1,1,5,1,27,4,2,5,0,3,5,6,3,1,0,3,1,12,5,3,3,2,0,7,7,2,1,0,4,0,1,1,2,0,10,10,6,2,5,9,7,5,15,15,21,6,11,5,20, + 4,3,5,5,2,5,0,2,1,0,1,7,28,0,9,0,5,12,5,5,18,30,0,12,3,3,21,16,25,32,9,3,14,11,24,5,66,9,1,2,0,5,9,1,5,1,8,0,8,3,3,0,1,15,1,4,8,1,2,7,0,7,2, + 8,3,7,5,3,7,10,2,1,0,0,2,25,0,6,4,0,10,0,4,2,4,1,12,5,38,4,0,4,1,10,5,9,4,0,14,4,2,5,18,20,21,1,3,0,5,0,7,0,3,7,1,3,1,1,8,1,0,0,0,3,2,5,2,11, + 6,0,13,1,3,9,1,12,0,16,6,2,1,0,2,1,12,6,13,11,2,0,28,1,7,8,14,13,8,13,0,2,0,5,4,8,10,2,37,42,19,6,6,7,4,14,11,18,14,80,7,6,0,4,72,12,36,27, + 7,7,0,14,17,19,164,27,0,5,10,7,3,13,6,14,0,2,2,5,3,0,6,13,0,0,10,29,0,4,0,3,13,0,3,1,6,51,1,5,28,2,0,8,0,20,2,4,0,25,2,10,13,10,0,16,4,0,1,0, + 2,1,7,0,1,8,11,0,0,1,2,7,2,23,11,6,6,4,16,2,2,2,0,22,9,3,3,5,2,0,15,16,21,2,9,20,15,15,5,3,9,1,0,0,1,7,7,5,4,2,2,2,38,24,14,0,0,15,5,6,24,14, + 5,5,11,0,21,12,0,3,8,4,11,1,8,0,11,27,7,2,4,9,21,59,0,1,39,3,60,62,3,0,12,11,0,3,30,11,0,13,88,4,15,5,28,13,1,4,48,17,17,4,28,32,46,0,16,0, + 18,11,1,8,6,38,11,2,6,11,38,2,0,45,3,11,2,7,8,4,30,14,17,2,1,1,65,18,12,16,4,2,45,123,12,56,33,1,4,3,4,7,0,0,0,3,2,0,16,4,2,4,2,0,7,4,5,2,26, + 2,25,6,11,6,1,16,2,6,17,77,15,3,35,0,1,0,5,1,0,38,16,6,3,12,3,3,3,0,9,3,1,3,5,2,9,0,18,0,25,1,3,32,1,72,46,6,2,7,1,3,14,17,0,28,1,40,13,0,20, + 15,40,6,38,24,12,43,1,1,9,0,12,6,0,6,2,4,19,3,7,1,48,0,9,5,0,5,6,9,6,10,15,2,11,19,3,9,2,0,1,10,1,27,8,1,3,6,1,14,0,26,0,27,16,3,4,9,6,2,23, + 9,10,5,25,2,1,6,1,1,48,15,9,15,14,3,4,26,60,29,13,37,21,1,6,4,0,2,11,22,23,16,16,2,2,1,3,0,5,1,6,4,0,0,4,0,0,8,3,0,2,5,0,7,1,7,3,13,2,4,10, + 3,0,2,31,0,18,3,0,12,10,4,1,0,7,5,7,0,5,4,12,2,22,10,4,2,15,2,8,9,0,23,2,197,51,3,1,1,4,13,4,3,21,4,19,3,10,5,40,0,4,1,1,10,4,1,27,34,7,21, + 2,17,2,9,6,4,2,3,0,4,2,7,8,2,5,1,15,21,3,4,4,2,2,17,22,1,5,22,4,26,7,0,32,1,11,42,15,4,1,2,5,0,19,3,1,8,6,0,10,1,9,2,13,30,8,2,24,17,19,1,4, + 4,25,13,0,10,16,11,39,18,8,5,30,82,1,6,8,18,77,11,13,20,75,11,112,78,33,3,0,0,60,17,84,9,1,1,12,30,10,49,5,32,158,178,5,5,6,3,3,1,3,1,4,7,6, + 19,31,21,0,2,9,5,6,27,4,9,8,1,76,18,12,1,4,0,3,3,6,3,12,2,8,30,16,2,25,1,5,5,4,3,0,6,10,2,3,1,0,5,1,19,3,0,8,1,5,2,6,0,0,0,19,1,2,0,5,1,2,5, + 1,3,7,0,4,12,7,3,10,22,0,9,5,1,0,2,20,1,1,3,23,30,3,9,9,1,4,191,14,3,15,6,8,50,0,1,0,0,4,0,0,1,0,2,4,2,0,2,3,0,2,0,2,2,8,7,0,1,1,1,3,3,17,11, + 91,1,9,3,2,13,4,24,15,41,3,13,3,1,20,4,125,29,30,1,0,4,12,2,21,4,5,5,19,11,0,13,11,86,2,18,0,7,1,8,8,2,2,22,1,2,6,5,2,0,1,2,8,0,2,0,5,2,1,0, + 2,10,2,0,5,9,2,1,2,0,1,0,4,0,0,10,2,5,3,0,6,1,0,1,4,4,33,3,13,17,3,18,6,4,7,1,5,78,0,4,1,13,7,1,8,1,0,35,27,15,3,0,0,0,1,11,5,41,38,15,22,6, + 14,14,2,1,11,6,20,63,5,8,27,7,11,2,2,40,58,23,50,54,56,293,8,8,1,5,1,14,0,1,12,37,89,8,8,8,2,10,6,0,0,0,4,5,2,1,0,1,1,2,7,0,3,3,0,4,6,0,3,2, + 19,3,8,0,0,0,4,4,16,0,4,1,5,1,3,0,3,4,6,2,17,10,10,31,6,4,3,6,10,126,7,3,2,2,0,9,0,0,5,20,13,0,15,0,6,0,2,5,8,64,50,3,2,12,2,9,0,0,11,8,20, + 109,2,18,23,0,0,9,61,3,0,28,41,77,27,19,17,81,5,2,14,5,83,57,252,14,154,263,14,20,8,13,6,57,39,38, + }; + static ImWchar base_ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + }; + static bool full_ranges_unpacked = false; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(offsets_from_0x4E00)*2 + 1]; + if (!full_ranges_unpacked) + { + // Unpack + int codepoint = 0x4e00; + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + ImWchar* dst = full_ranges + IM_ARRAYSIZE(base_ranges);; + for (int n = 0; n < IM_ARRAYSIZE(offsets_from_0x4E00); n++, dst += 2) + dst[0] = dst[1] = (ImWchar)(codepoint += (offsets_from_0x4E00[n] + 1)); + dst[0] = 0; + full_ranges_unpacked = true; + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas::GlyphRangesBuilder +//----------------------------------------------------------------------------- + +void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end) +{ + while (text_end ? (text < text_end) : *text) + { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + if (c < 0x10000) + AddChar((ImWchar)c); + } +} + +void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges) +{ + for (; ranges[0]; ranges += 2) + for (ImWchar c = ranges[0]; c <= ranges[1]; c++) + AddChar(c); +} + +void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector* out_ranges) +{ + for (int n = 0; n < 0x10000; n++) + if (GetBit(n)) + { + out_ranges->push_back((ImWchar)n); + while (n < 0x10000 && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + Scale = 1.0f; + FallbackChar = (ImWchar)'?'; + DisplayOffset = ImVec2(0.0f, 1.0f); + ClearOutputData(); +} + +ImFont::~ImFont() +{ + // Invalidate active font so that the user gets a clear crash instead of a dangling pointer. + // If you want to delete fonts you need to do it between Render() and NewFrame(). + // FIXME-CLEANUP + /* + ImGuiContext& g = *GImGui; + if (g.Font == this) + g.Font = NULL; + */ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + FallbackAdvanceX = 0.0f; + ConfigDataCount = 0; + ConfigData = NULL; + ContainerAtlas = NULL; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +void ImFont::BuildLookupTable() +{ + int max_codepoint = 0; + for (int i = 0; i != Glyphs.Size; i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + + IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + GrowIndex(max_codepoint + 1); + for (int i = 0; i < Glyphs.Size; i++) + { + int codepoint = (int)Glyphs[i].Codepoint; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; + IndexLookup[codepoint] = (unsigned short)i; + } + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (FindGlyph((unsigned short)' ')) + { + if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& tab_glyph = Glyphs.back(); + tab_glyph = *FindGlyph((unsigned short)' '); + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX *= 4; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; + IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); + } + + FallbackGlyph = NULL; + FallbackGlyph = FindGlyph(FallbackChar); + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + for (int i = 0; i < max_codepoint + 1; i++) + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; +} + +void ImFont::SetFallbackChar(ImWchar c) +{ + FallbackChar = c; + BuildLookupTable(); +} + +void ImFont::GrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (unsigned short)-1); +} + +void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX + + if (ConfigData->PixelSnapH) + glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. + int index_size = IndexLookup.Size; + + if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists + return; + if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op + return; + + GrowIndex(dst + 1); + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +{ + if (c < IndexLookup.Size) + { + const unsigned short i = IndexLookup[c]; + if (i != (unsigned short)-1) + return &Glyphs.Data[i]; + } + return FallbackGlyph; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ + // Simple word-wrapping for English, not full-featured. Please submit failing cases! + // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + + // For references, possible wrap point marked with ^ + // "aaa bbb, ccc,ddd. eee fff. ggg!" + // ^ ^ ^ ^ ^__ ^ ^ + + // List of hardcoded separators: .,;!?'" + + // Skip extra blanks after a line returns (that includes not counting them in width computation) + // e.g. "Hello world" --> "Hello" "World" + + // Cut words that cannot possibly fit within one line. + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + float line_width = 0.0f; + float word_width = 0.0f; + float blank_width = 0.0f; + wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + + const char* word_end = text; + const char* prev_word_end = NULL; + bool inside_word = true; + + const char* s = text; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + const char* next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) + break; + + if (c < 32) + { + if (c == '\n') + { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') + { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); + if (ImCharIsSpace(c)) + { + if (inside_word) + { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } + else + { + word_width += char_width; + if (inside_word) + { + word_end = next_s; + } + else + { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width >= wrap_width) + { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + s = prev_word_end ? prev_word_end : word_end; + break; + } + + s = next_s; + } + + return s; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + const char* s = text_begin; + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + const char* prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; + if (line_width + char_width >= max_width) + { + s = prev_s; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const +{ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. + return; + if (const ImFontGlyph* glyph = FindGlyph(c)) + { + float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + draw_list->PrimReserve(6, 4); + draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + } +} + +void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + float x = pos.x; + float y = pos.y; + if (y > clip_rect.w) + return; + + const float scale = size / FontSize; + const float line_height = FontSize * scale; + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + // Skip non-visible lines + const char* s = text_begin; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int vtx_count_max = (int)(text_end - s) * 4; + const int idx_count_max = (int)(text_end - s) * 6; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + x = pos.x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + x = pos.x; + y += line_height; + + if (y > clip_rect.w) + break; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + continue; + } + if (c == '\r') + continue; + } + + float char_width = 0.0f; + if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) + { + char_width = glyph->AdvanceX * scale; + + // Arbitrarily assume that both space and tabs are empty glyphs as an optimization + if (c != ' ' && c != '\t') + { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) + { + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip) + { + if (x1 < clip_rect.x) + { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) + { + x += char_width; + continue; + } + } + + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); + vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; + vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + } + } + } + } + + x += char_width; + } + + // Give back unused vertices + draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); + draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; +} + +//----------------------------------------------------------------------------- +// Internals Drawing Helpers +//----------------------------------------------------------------------------- + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return acosf(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + +//----------------------------------------------------------------------------- +// DEFAULT FONT DATA +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array. +// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- + +static unsigned int stb_decompress_length(unsigned char *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +static unsigned char *stb__barrier, *stb__barrier2, *stb__barrier3, *stb__barrier4; +static unsigned char *stb__dout; +static void stb__match(unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(unsigned char *data, unsigned int length) +{ + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static unsigned char *stb_decompress_token(unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} + +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length) +{ + unsigned int olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier2 = i; + stb__barrier3 = i+length; + stb__barrier = output + olen; + stb__barrier4 = output; + i += 16; + + stb__dout = output; + for (;;) { + unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using binary_to_compressed_c.cpp +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980+1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char* GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} diff --git a/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl2.cpp b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl2.cpp new file mode 100644 index 00000000..381ff13a --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl2.cpp @@ -0,0 +1,355 @@ +// ImGui GLFW binding with OpenGL (legacy, fixed pipeline) +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in the opengl3_example/ folder** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-20: Inputs: Renamed GLFW callbacks exposed in .h to not include GL2 in their name. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL2_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-09: Misc: Renamed imgui_impl_glfw.* to imgui_impl_glfw_gl2.*. +// 2017-09-01: OpenGL: Save and restore current polygon mode. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. +// 2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal). +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. + +#include "imgui.h" +#include "imgui_impl_glfw_gl2.h" + +// GLFW +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WGL +#include +#endif + +// GLFW data +static GLFWwindow* g_Window = NULL; +static double g_Time = 0.0f; +static bool g_MouseJustPressed[3] = { false, false, false }; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_Count_] = { 0 }; + +// OpenGL data +static GLuint g_FontTexture = 0; + +// OpenGL2 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplGlfwGL2_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT | GL_TEXTURE_BIT); // DAR Was missing GL_TEXTURE_BIT. Need to store the glTexEnv setting as well. + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // DAR IMGUI uses GL_MODULATE when not using GLSL shaders. + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + + // Restore modified state + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplGlfwGL2_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfwGL2_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) +{ + if (action == GLFW_PRESS && button >= 0 && button < 3) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + (void)mods; // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) +{ + ImGuiIO& io = ImGui::GetIO(); + if (c > 0 && c < 0x10000) + io.AddInputCharacter((unsigned short)c); +} + +bool ImGui_ImplGlfwGL2_CreateDeviceObjects() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplGlfwGL2_InvalidateDeviceObjects() +{ + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +static void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) +{ + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); +} + +bool ImGui_ImplGlfwGL2_Init(GLFWwindow* window, bool install_callbacks) +{ + g_Window = window; + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.SetClipboardTextFn = ImGui_ImplGlfwGL2_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfwGL2_GetClipboardText; + io.ClipboardUserData = g_Window; +#ifdef _WIN32 + io.ImeWindowHandle = glfwGetWin32Window(g_Window); +#endif + + // Load cursors + // FIXME: GLFW doesn't expose suitable cursors for ResizeAll, ResizeNESW, ResizeNWSE. We revert to arrow cursor for those. + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + + if (install_callbacks) + ImGui_ImplGlfw_InstallCallbacks(window); + + return true; +} + +void ImGui_ImplGlfwGL2_Shutdown() +{ + // Destroy GLFW mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_Count_; cursor_n++) + glfwDestroyCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + + // Destroy OpenGL objects + ImGui_ImplGlfwGL2_InvalidateDeviceObjects(); +} + +void ImGui_ImplGlfwGL2_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplGlfwGL2_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) + { + if (io.WantMoveMouse) + { + glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y); // Set mouse position if requested by io.WantMoveMouse flag (used when io.NavMovesTrue is enabled by user and using directional navigation) + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } + else + { + io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX); + } + + for (int i = 0; i < 3; i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update OS/hardware mouse cursor if imgui isn't drawing a software cursor + ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || cursor == ImGuiMouseCursor_None) + { + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + glfwSetCursor(g_Window, g_MouseCursors[cursor] ? g_MouseCursors[cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. + ImGui::NewFrame(); +} diff --git a/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl2.h b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl2.h new file mode 100644 index 00000000..f7b5c22c --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl2.h @@ -0,0 +1,32 @@ +// ImGui GLFW binding with OpenGL (legacy, fixed pipeline) +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in the opengl3_example/ folder** +// See imgui_impl_glfw_gl2.cpp for details. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct GLFWwindow; + +IMGUI_API bool ImGui_ImplGlfwGL2_Init(GLFWwindow* window, bool install_callbacks); +IMGUI_API void ImGui_ImplGlfwGL2_Shutdown(); +IMGUI_API void ImGui_ImplGlfwGL2_NewFrame(); +IMGUI_API void ImGui_ImplGlfwGL2_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplGlfwGL2_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplGlfwGL2_CreateDeviceObjects(); + +// GLFW callbacks (registered by default to GLFW if you enable 'install_callbacks' during initialization) +// Provided here if you want to chain callbacks yourself. You may also handle inputs yourself and use those as a reference. +IMGUI_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl3.cpp b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl3.cpp new file mode 100644 index 00000000..6321e3cf --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl3.cpp @@ -0,0 +1,501 @@ +// ImGui GLFW binding with OpenGL3 + shaders +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (GL3W is a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Gamepad navigation mapping. Enable with 'io.NavFlags |= ImGuiNavFlags_EnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-20: Inputs: Renamed GLFW callbacks exposed in .h to not include GL3 in their name. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Added gamepad support if ImGuiNavFlags_EnableGamepad is set. +// 2018-01-25: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. (Also changed GL context from 3.3 to 3.2 in example's main.cpp) +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2017-05-01: OpenGL: Fixed save and restore of current blend function state. +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-04-30: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. + +#include "imgui.h" +#include "imgui_impl_glfw_gl3.h" + +// GL3W/GLFW +//#include // This example is using gl3w to access OpenGL functions (because it is small). You may use glew/glad/glLoadGen/etc. whatever already works for you. +// DAR HACK Yes, using GLEW here. +#include +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WGL +#include +#endif + +// GLFW data +static GLFWwindow* g_Window = NULL; +static double g_Time = 0.0f; +static bool g_MouseJustPressed[3] = { false, false, false }; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_Count_] = { 0 }; + +// OpenGL3 data +static GLuint g_FontTexture = 0; +static int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; +static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0; +static unsigned int g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0; + +// OpenGL3 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplGlfwGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); + GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); + GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); + + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + const float ortho_projection[4][4] = + { + { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + {-1.0f, 1.0f, 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + glBindVertexArray(g_VaoHandle); + glBindSampler(0, 0); // Rely on combined texture/sampler state. + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawIdx* idx_buffer_offset = 0; + + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + } + idx_buffer_offset += pcmd->ElemCount; + } + } + + // Restore modified GL state + glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindSampler(0, last_sampler); + glActiveTexture(last_active_texture); + glBindVertexArray(last_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplGlfwGL3_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfwGL3_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) +{ + if (action == GLFW_PRESS && button >= 0 && button < 3) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + (void)mods; // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) +{ + ImGuiIO& io = ImGui::GetIO(); + if (c > 0 && c < 0x10000) + io.AddInputCharacter((unsigned short)c); +} + +bool ImGui_ImplGlfwGL3_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +bool ImGui_ImplGlfwGL3_CreateDeviceObjects() +{ + // Backup GL state + GLint last_texture, last_array_buffer, last_vertex_array; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + + const GLchar *vertex_shader = + "#version 150\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader = + "#version 150\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" + "}\n"; + + g_ShaderHandle = glCreateProgram(); + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_VertHandle, 1, &vertex_shader, 0); + glShaderSource(g_FragHandle, 1, &fragment_shader, 0); + glCompileShader(g_VertHandle); + glCompileShader(g_FragHandle); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + glGenBuffers(1, &g_VboHandle); + glGenBuffers(1, &g_ElementsHandle); + + glGenVertexArrays(1, &g_VaoHandle); + glBindVertexArray(g_VaoHandle); + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glEnableVertexAttribArray(g_AttribLocationPosition); + glEnableVertexAttribArray(g_AttribLocationUV); + glEnableVertexAttribArray(g_AttribLocationColor); + + glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); + + ImGui_ImplGlfwGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindVertexArray(last_vertex_array); + + return true; +} + +void ImGui_ImplGlfwGL3_InvalidateDeviceObjects() +{ + if (g_VaoHandle) glDeleteVertexArrays(1, &g_VaoHandle); + if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle); + if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle); + g_VaoHandle = g_VboHandle = g_ElementsHandle = 0; + + if (g_ShaderHandle && g_VertHandle) glDetachShader(g_ShaderHandle, g_VertHandle); + if (g_VertHandle) glDeleteShader(g_VertHandle); + g_VertHandle = 0; + + if (g_ShaderHandle && g_FragHandle) glDetachShader(g_ShaderHandle, g_FragHandle); + if (g_FragHandle) glDeleteShader(g_FragHandle); + g_FragHandle = 0; + + if (g_ShaderHandle) glDeleteProgram(g_ShaderHandle); + g_ShaderHandle = 0; + + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +static void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) +{ + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); +} + +bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks) +{ + g_Window = window; + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.SetClipboardTextFn = ImGui_ImplGlfwGL3_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfwGL3_GetClipboardText; + io.ClipboardUserData = g_Window; +#ifdef _WIN32 + io.ImeWindowHandle = glfwGetWin32Window(g_Window); +#endif + + // Load cursors + // FIXME: GLFW doesn't expose suitable cursors for ResizeAll, ResizeNESW, ResizeNWSE. We revert to arrow cursor for those. + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + + if (install_callbacks) + ImGui_ImplGlfw_InstallCallbacks(window); + + return true; +} + +void ImGui_ImplGlfwGL3_Shutdown() +{ + // Destroy GLFW mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_Count_; cursor_n++) + glfwDestroyCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + + // Destroy OpenGL objects + ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); +} + +void ImGui_ImplGlfwGL3_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplGlfwGL3_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) + { + if (io.WantMoveMouse) + { + glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y); // Set mouse position if requested by io.WantMoveMouse flag (used when io.NavMovesTrue is enabled by user and using directional navigation) + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } + else + { + io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX); + } + + for (int i = 0; i < 3; i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update OS/hardware mouse cursor if imgui isn't drawing a software cursor + ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || cursor == ImGuiMouseCursor_None) + { + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + glfwSetCursor(g_Window, g_MouseCursors[cursor] ? g_MouseCursors[cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + // Gamepad navigation mapping [BETA] + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if (io.NavFlags & ImGuiNavFlags_EnableGamepad) + { + // Update gamepad inputs + #define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; } + #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; } + int axes_count = 0, buttons_count = 0; + const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + MAP_BUTTON(ImGuiNavInput_Activate, 0); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, 1); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, 2); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, 3); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, 13); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, 11); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, 10); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, 12); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, 5); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, 0, -0.3f, -0.9f); + MAP_ANALOG(ImGuiNavInput_LStickRight,0, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickUp, 1, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickDown, 1, -0.3f, -0.9f); + #undef MAP_BUTTON + #undef MAP_ANALOG + } + + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. + ImGui::NewFrame(); +} diff --git a/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl3.h b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl3.h new file mode 100644 index 00000000..71ea4122 --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_impl_glfw_gl3.h @@ -0,0 +1,31 @@ +// ImGui GLFW binding with OpenGL3 + shaders +// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (GL3W is a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc.) + +// Implemented features: +// [X] User texture binding. Cast 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Gamepad navigation mapping. Enable with 'io.NavFlags |= ImGuiNavFlags_EnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct GLFWwindow; + +IMGUI_API bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks); +IMGUI_API void ImGui_ImplGlfwGL3_Shutdown(); +IMGUI_API void ImGui_ImplGlfwGL3_NewFrame(); +IMGUI_API void ImGui_ImplGlfwGL3_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplGlfwGL3_CreateDeviceObjects(); + +// GLFW callbacks (installed by default if you enable 'install_callbacks' during initialization) +// Provided here if you want to chain callbacks. +// You can also handle inputs yourself and use those as a reference. +IMGUI_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/apps/bench_shared_offscreen/imgui/imgui_internal.h b/apps/bench_shared_offscreen/imgui/imgui_internal.h new file mode 100644 index 00000000..77d621bb --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/imgui_internal.h @@ -0,0 +1,1156 @@ +// dear imgui, v1.60 WIP +// (internals) + +// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! +// Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +#pragma once + +#ifndef IMGUI_VERSION +#error Must include imgui.h before imgui_internal.h +#endif + +#include // FILE* +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf +#include // INT_MIN, INT_MAX + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +//----------------------------------------------------------------------------- +// Forward Declarations +//----------------------------------------------------------------------------- + +struct ImRect; +struct ImGuiColMod; +struct ImGuiStyleMod; +struct ImGuiGroupData; +struct ImGuiMenuColumns; +struct ImGuiDrawContext; +struct ImGuiTextEditState; +struct ImGuiPopupRef; +struct ImGuiWindow; +struct ImGuiWindowSettings; + +typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_ +typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_ +typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_ +typedef int ImGuiItemStatusFlags; // flags: storage for DC.LastItemXXX // enum ImGuiItemStatusFlags_ +typedef int ImGuiNavHighlightFlags; // flags: for RenderNavHighlight() // enum ImGuiNavHighlightFlags_ +typedef int ImGuiNavDirSourceFlags; // flags: for GetNavInputAmount2d() // enum ImGuiNavDirSourceFlags_ +typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_ +typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_ + +//------------------------------------------------------------------------- +// STB libraries +//------------------------------------------------------------------------- + +namespace ImGuiStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiTextEditState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f +#include "stb_textedit.h" + +} // namespace ImGuiStb + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +#ifndef GImGui +extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +#define IM_PI 3.14159265358979323846f + +// Helpers: UTF-8 <> wchar +IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string as UTF-8 code-points + +// Helpers: Misc +IMGUI_API ImU32 ImHash(const void* data, int data_size, ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size = NULL, int padding_bytes = 0); +IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); +static inline bool ImCharIsSpace(int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } + +// Helpers: Geometry +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); + +// Helpers: String +IMGUI_API int ImStricmp(const char* str1, const char* str2); +IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); +IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); +IMGUI_API char* ImStrdup(const char* str); +IMGUI_API char* ImStrchrRange(const char* str_begin, const char* str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar* str); +IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line +IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); +IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); + +// Helpers: Math +// We are keeping those not leaking to the user by default, in the case the user has implicit cast operators between ImVec2 and its own types (when IM_VEC2_CLASS_EXTRA is defined) +#ifdef IMGUI_DEFINE_MATH_OPERATORS +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x/rhs, lhs.y/rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x*rhs.x, lhs.y*rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x/rhs.x, lhs.y/rhs.y); } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); } +#endif + +static inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } +static inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } +static inline float ImMin(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; } +static inline float ImMax(float lhs, float rhs) { return lhs >= rhs ? lhs : rhs; } +static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +static inline int ImClamp(int v, int mn, int mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline ImVec2 ImClamp(const ImVec2& f, const ImVec2& mn, ImVec2 mx) { return ImVec2(ImClamp(f.x,mn.x,mx.x), ImClamp(f.y,mn.y,mx.y)); } +static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline void ImSwap(int& a, int& b) { int tmp = a; a = b; b = tmp; } +static inline void ImSwap(float& a, float& b) { float tmp = a; a = b; b = tmp; } +static inline int ImLerp(int a, int b, float t) { return (int)(a + (b - a) * t); } +static inline float ImLerp(float a, float b, float t) { return a + (b - a) * t; } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } +static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / sqrtf(d); return fail_value; } +static inline float ImFloor(float f) { return (float)(int)f; } +static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } +static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + +// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. +// Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. +struct ImNewPlacementDummy {}; +inline void* operator new(size_t, ImNewPlacementDummy, void* ptr) { return ptr; } +inline void operator delete(void*, ImNewPlacementDummy, void*) {} // This is only required so we can use the symetrical new() +#define IM_PLACEMENT_NEW(_PTR) new(ImNewPlacementDummy(), _PTR) +#define IM_NEW(_TYPE) new(ImNewPlacementDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +template void IM_DELETE(T*& p) { if (p) { p->~T(); ImGui::MemFree(p); p = NULL; } } + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +enum ImGuiButtonFlags_ +{ + ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat + ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] + ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) + ImGuiButtonFlags_FlattenChildren = 1 << 5, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowItemOverlap = 1 << 6, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() + ImGuiButtonFlags_DontClosePopups = 1 << 7, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 8, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveID = 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12, // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) + ImGuiButtonFlags_NoNavFocus = 1 << 13 // don't override navigation focus when activated +}; + +enum ImGuiSliderFlags_ +{ + ImGuiSliderFlags_Vertical = 1 << 0 +}; + +enum ImGuiColumnsFlags_ +{ + // Default: 0 + ImGuiColumnsFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiColumnsFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + ImGuiColumnsFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns + ImGuiColumnsFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window + ImGuiColumnsFlags_GrowParentContentsSize= 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. +}; + +enum ImGuiSelectableFlagsPrivate_ +{ + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_Menu = 1 << 3, // -> PressedOnClick + ImGuiSelectableFlags_MenuItem = 1 << 4, // -> PressedOnRelease + ImGuiSelectableFlags_Disabled = 1 << 5, + ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 6 +}; + +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1 +}; + +// Storage for LastItem data +enum ImGuiItemStatusFlags_ +{ + ImGuiItemStatusFlags_HoveredRect = 1 << 0, + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1 +}; + +// FIXME: this is in development, not exposed/functional as a generic feature yet. +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Vertical, + ImGuiLayoutType_Horizontal +}; + +enum ImGuiAxis +{ + ImGuiAxis_None = -1, + ImGuiAxis_X = 0, + ImGuiAxis_Y = 1 +}; + +enum ImGuiPlotType +{ + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram +}; + +enum ImGuiDataType +{ + ImGuiDataType_Int, + ImGuiDataType_Float, + ImGuiDataType_Float2 +}; + +enum ImGuiDir +{ + ImGuiDir_None = -1, + ImGuiDir_Left = 0, + ImGuiDir_Right = 1, + ImGuiDir_Up = 2, + ImGuiDir_Down = 3, + ImGuiDir_Count_ +}; + +enum ImGuiInputSource +{ + ImGuiInputSource_None = 0, + ImGuiInputSource_Mouse, + ImGuiInputSource_Nav, + ImGuiInputSource_NavKeyboard, // Only used occasionally for storage, not tested/handled by most code + ImGuiInputSource_NavGamepad, // " + ImGuiInputSource_Count_, +}; + +// FIXME-NAV: Clarify/expose various repeat delay/rate +enum ImGuiInputReadMode +{ + ImGuiInputReadMode_Down, + ImGuiInputReadMode_Pressed, + ImGuiInputReadMode_Released, + ImGuiInputReadMode_Repeat, + ImGuiInputReadMode_RepeatSlow, + ImGuiInputReadMode_RepeatFast +}; + +enum ImGuiNavHighlightFlags_ +{ + ImGuiNavHighlightFlags_TypeDefault = 1 << 0, + ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, + ImGuiNavHighlightFlags_NoRounding = 1 << 3 +}; + +enum ImGuiNavDirSourceFlags_ +{ + ImGuiNavDirSourceFlags_Keyboard = 1 << 0, + ImGuiNavDirSourceFlags_PadDPad = 1 << 1, + ImGuiNavDirSourceFlags_PadLStick = 1 << 2 +}; + +enum ImGuiNavForward +{ + ImGuiNavForward_None, + ImGuiNavForward_ForwardQueued, + ImGuiNavForward_ForwardActive +}; + +// 2D axis aligned bounding-box +// NB: we can't rely on ImVec2 math operators being available here +struct IMGUI_API ImRect +{ + ImVec2 Min; // Upper-left + ImVec2 Max; // Lower-right + + ImRect() : Min(FLT_MAX,FLT_MAX), Max(-FLT_MAX,-FLT_MAX) {} + ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + + ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } + ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } + float GetWidth() const { return Max.x - Min.x; } + float GetHeight() const { return Max.y - Min.y; } + ImVec2 GetTL() const { return Min; } // Top-left + ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right + ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left + ImVec2 GetBR() const { return Max; } // Bottom-right + bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } + bool Contains(const ImRect& r) const { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; } + bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } + void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } + void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } + void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } + void Translate(const ImVec2& v) { Min.x += v.x; Min.y += v.y; Max.x += v.x; Max.y += v.y; } + void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. + void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. + void Floor() { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; } + void FixInverted() { if (Min.x > Max.x) ImSwap(Min.x, Max.x); if (Min.y > Max.y) ImSwap(Min.y, Max.y); } + bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } + bool IsFinite() const { return Min.x != FLT_MAX; } +}; + +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +// Stacked data for BeginGroup()/EndGroup() +struct ImGuiGroupData +{ + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + float BackupIndentX; + float BackupGroupOffsetX; + float BackupCurrentLineHeight; + float BackupCurrentLineTextBaseOffset; + float BackupLogLinePosY; + bool BackupActiveIdIsAlive; + bool AdvanceCursor; +}; + +// Simple column measurement currently used for MenuItem() only. This is very short-sighted/throw-away code and NOT a generic helper. +struct IMGUI_API ImGuiMenuColumns +{ + int Count; + float Spacing; + float Width, NextWidth; + float Pos[4], NextWidths[4]; + + ImGuiMenuColumns(); + void Update(int count, float spacing, bool clear); + float DeclColumns(float w0, float w1, float w2); + float CalcExtraSpace(float avail_w); +}; + +// Internal state of the currently focused/edited text input box +struct IMGUI_API ImGuiTextEditState +{ + ImGuiID Id; // widget id owning the text state + ImVector Text; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempTextBuffer; + int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. + int BufSizeA; // end-user buffer size + float ScrollX; + ImGuiStb::STB_TexteditState StbState; + float CursorAnim; + bool CursorFollow; + bool SelectedAllMouseLock; + + ImGuiTextEditState() { memset(this, 0, sizeof(*this)); } + void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); } + bool HasSelection() const { return StbState.select_start != StbState.select_end; } + void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } + void SelectAll() { StbState.select_start = 0; StbState.cursor = StbState.select_end = CurLenW; StbState.has_preferred_x = false; } + void OnKeyPressed(int key); +}; + +// Data saved in imgui.ini file +struct ImGuiWindowSettings +{ + char* Name; + ImGuiID Id; + ImVec2 Pos; + ImVec2 Size; + bool Collapsed; + + ImGuiWindowSettings() { Name = NULL; Id = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } +}; + +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) + void* (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name); + void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); + void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); + void* UserData; + + ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } +}; + +// Storage for current popup stack +struct ImGuiPopupRef +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* ParentWindow; // Set on OpenPopup() + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differenciate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup +}; + +struct ImGuiColumnData +{ + float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) + float OffsetNormBeforeResize; + ImGuiColumnsFlags Flags; // Not exposed + ImRect ClipRect; + + ImGuiColumnData() { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = 0; } +}; + +struct ImGuiColumnsSet +{ + ImGuiID ID; + ImGuiColumnsFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float MinX, MaxX; + float StartPosY; + float StartMaxPosX; // Backup of CursorMaxPos + float CellMinY, CellMaxY; + ImVector Columns; + + ImGuiColumnsSet() { Clear(); } + void Clear() + { + ID = 0; + Flags = 0; + IsFirstFrame = false; + IsBeingResized = false; + Current = 0; + Count = 1; + MinX = MaxX = 0.0f; + StartPosY = 0.0f; + StartMaxPosX = 0.0f; + CellMinY = CellMaxY = 0.0f; + Columns.clear(); + } +}; + +struct IMGUI_API ImDrawListSharedData +{ + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + ImFont* Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + + // Const data + // FIXME: Bake rounded corners fill/borders in atlas + ImVec2 CircleVtx12[12]; + + ImDrawListSharedData(); +}; + +struct ImDrawDataBuilder +{ + ImVector Layers[2]; // Global layers for: regular, tooltip + + void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); } + void ClearFreeMemory() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); } + IMGUI_API void FlattenIntoSingleLayer(); +}; + +struct ImGuiNavMoveResult +{ + ImGuiID ID; // Best candidate + ImGuiID ParentID; // Best candidate window->IDStack.back() - to compare context + ImGuiWindow* Window; // Best candidate window + float DistBox; // Best candidate box distance to current NavId + float DistCenter; // Best candidate center distance to current NavId + float DistAxial; + ImRect RectRel; // Best candidate bounding box in window relative space + + ImGuiNavMoveResult() { Clear(); } + void Clear() { ID = ParentID = 0; Window = NULL; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } +}; + +// Storage for SetNexWindow** functions +struct ImGuiNextWindowData +{ + ImGuiCond PosCond; + ImGuiCond SizeCond; + ImGuiCond ContentSizeCond; + ImGuiCond CollapsedCond; + ImGuiCond SizeConstraintCond; + ImGuiCond FocusCond; + ImGuiCond BgAlphaCond; + ImVec2 PosVal; + ImVec2 PosPivotVal; + ImVec2 SizeVal; + ImVec2 ContentSizeVal; + bool CollapsedVal; + ImRect SizeConstraintRect; // Valid if 'SetNextWindowSizeConstraint' is true + ImGuiSizeCallback SizeCallback; + void* SizeCallbackUserData; + float BgAlphaVal; + + ImGuiNextWindowData() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f); + ContentSizeVal = ImVec2(0.0f, 0.0f); + CollapsedVal = false; + SizeConstraintRect = ImRect(); + SizeCallback = NULL; + SizeCallbackUserData = NULL; + BgAlphaVal = FLT_MAX; + } + + void Clear() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + } +}; + +// Main state for ImGui +struct ImGuiContext +{ + bool Initialized; + bool FontAtlasOwnedByContext; // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. + ImGuiIO IO; + ImGuiStyle Style; + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + ImDrawListSharedData DrawListSharedData; + + float Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + ImVector Windows; + ImVector WindowsSortBuffer; + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; + int WindowsActiveCount; + ImGuiWindow* CurrentWindow; // Being drawn into + ImGuiWindow* HoveredWindow; // Will catch mouse inputs + ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) + ImGuiID HoveredId; // Hovered widget + bool HoveredIdAllowOverlap; + ImGuiID HoveredIdPreviousFrame; + float HoveredIdTimer; + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdPreviousFrame; + float ActiveIdTimer; + bool ActiveIdIsAlive; // Active widget has been seen this frame + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and move away from it) + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow* ActiveIdWindow; + ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actually window that is moved is generally MovingWindow->RootWindow. + ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() + ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() + ImVector FontStack; // Stack for PushFont()/PopFont() + ImVector OpenPopupStack; // Which popups are open (persistent) + ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + bool NextTreeNodeOpenVal; // Storage for SetNextTreeNode** functions + ImGuiCond NextTreeNodeOpenCond; + + // Navigation data (for gamepad/keyboard) + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' + ImGuiID NavId; // Focused item for navigation + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 + ImGuiID NavJustTabbedId; // Just tabbed to this id. + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest) + ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring. + int NavScoringCount; // Metrics for debugging + ImGuiWindow* NavWindowingTarget; // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) this window is temporarily displayed front-most. + float NavWindowingHighlightTimer; + float NavWindowingHighlightAlpha; + bool NavWindowingToggleLayer; + ImGuiInputSource NavWindowingInputSource; // Gamepad or keyboard mode + int NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. + int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRefRectRel is valid + bool NavMousePosDirty; // When set we will update mouse position if (NavFlags & ImGuiNavFlags_MoveMouse) if set (NB: this not enabled by default) + bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (nb: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) + bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest + bool NavInitRequest; // Init request for appearing window to select first item + bool NavInitRequestFromMove; + ImGuiID NavInitResultId; + ImRect NavInitResultRectRel; + bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items + bool NavMoveRequest; // Move request for this frame + ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) + ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request + ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow + ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using the NavFlattened flag) + + // Render + ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user + ImDrawDataBuilder DrawDataBuilder; + float ModalWindowDarkeningRatio; + ImDrawList OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays + ImGuiMouseCursor MouseCursor; + + // Drag and Drop + bool DragDropActive; + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; + ImGuiID DragDropTargetId; + float DragDropAcceptIdCurrRectSurface; + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly + unsigned char DragDropPayloadBufLocal[8]; + + // Widget state + ImGuiTextEditState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + ImVec4 ColorPickerRef; + float DragCurrentValue; // Currently dragged value, always float, not rounded by end-user precision settings + ImVec2 DragLastMouseDelta; + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + float DragSpeedScaleSlow; + float DragSpeedScaleFast; + ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + int TooltipOverrideCount; + ImVector PrivateClipboard; // If no custom clipboard handler is defined + ImVec2 OsImePosRequest, OsImePosSet; // Cursor position request & last passed to the OS Input Method Editor + + // Settings + bool SettingsLoaded; + float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero + ImVector SettingsWindows; // .ini settings for ImGuiWindow + ImVector SettingsHandlers; // List of .ini settings handlers + + // Logging + bool LogEnabled; + FILE* LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer* LogClipboard; // Else log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + int LogStartDepth; + int LogAutoExpandMaxDepth; + + // Misc + float FramerateSecPerFrame[120]; // calculate estimate of framerate for user + int FramerateSecPerFrameIdx; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // explicit capture via CaptureInputs() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; + char TempBuffer[1024*3+1]; // temporary text buffer + + ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL) + { + Initialized = false; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredRootWindow = NULL; + HoveredId = 0; + HoveredIdAllowOverlap = false; + HoveredIdPreviousFrame = 0; + HoveredIdTimer = 0.0f; + ActiveId = 0; + ActiveIdPreviousFrame = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsAlive = false; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdAllowNavDirFlags = 0; + ActiveIdClickOffset = ImVec2(-1,-1); + ActiveIdWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + MovingWindow = NULL; + NextTreeNodeOpenVal = false; + NextTreeNodeOpenCond = 0; + + NavWindow = NULL; + NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; + NavScoringRectScreen = ImRect(); + NavScoringCount = 0; + NavWindowingTarget = NULL; + NavWindowingHighlightTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + NavWindowingInputSource = ImGuiInputSource_None; + NavLayer = 0; + NavIdTabCounter = INT_MAX; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavInitResultId = 0; + NavMoveFromClampedRefRect = false; + NavMoveRequest = false; + NavMoveRequestForward = ImGuiNavForward_None; + NavMoveDir = NavMoveDirLast = ImGuiDir_None; + + ModalWindowDarkeningRatio = 0.0f; + OverlayDrawList._Data = &DrawListSharedData; + OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging + MouseCursor = ImGuiMouseCursor_Arrow; + + DragDropActive = false; + DragDropSourceFlags = 0; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ScalarAsInputTextId = 0; + ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + DragCurrentValue = 0.0f; + DragLastMouseDelta = ImVec2(0.0f, 0.0f); + DragSpeedDefaultRatio = 1.0f / 100.0f; + DragSpeedScaleSlow = 1.0f / 100.0f; + DragSpeedScaleFast = 10.0f; + ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); + TooltipOverrideCount = 0; + OsImePosRequest = OsImePosSet = ImVec2(-1.0f, -1.0f); + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + + LogEnabled = false; + LogFile = NULL; + LogClipboard = NULL; + LogStartDepth = 0; + LogAutoExpandMaxDepth = 2; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempBuffer, 0, sizeof(TempBuffer)); + } +}; + +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // FIXME-WIP: Disable interactions but doesn't affect visuals. Should be: grey out and disable interactions with widgets that affect data + view widgets (WIP) + ImGuiItemFlags_NoNav = 1 << 3, // false + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus +}; + +// Transient per-window data, reset at the beginning of the frame +// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiDrawContext is quite tenuous and could be reconsidered. +struct IMGUI_API ImGuiDrawContext +{ + ImVec2 CursorPos; + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; + ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame + float CurrentLineHeight; + float CurrentLineTextBaseOffset; + float PrevLineHeight; + float PrevLineTextBaseOffset; + float LogLinePosY; + int TreeDepth; + ImU32 TreeDepthMayCloseOnPop; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31 + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; // Interaction rect + ImRect LastItemDisplayRect; // End-user display rect (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) + bool NavHideHighlightOneFrame; + bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) + int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) + int NavLayerCurrentMask; // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping. + int NavLayerActiveMask; // Which layer have been written to (result from previous frame) + int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame) + bool MenuBarAppending; // FIXME: Remove this + float MenuBarOffsetX; + ImVector ChildWindows; + ImGuiStorage* StateStorage; + ImGuiLayoutType LayoutType; + ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] + float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window + float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] + ImVectorItemFlagsStack; + ImVector ItemWidthStack; + ImVector TextWrapPosStack; + ImVectorGroupStack; + int StackSizesBackup[6]; // Store size of various stacks for asserting + + float IndentX; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + float GroupOffsetX; + float ColumnsOffsetX; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + ImGuiColumnsSet* ColumnsSet; // Current columns set + + ImGuiDrawContext() + { + CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); + CurrentLineHeight = PrevLineHeight = 0.0f; + CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; + LogLinePosY = -1.0f; + TreeDepth = 0; + TreeDepthMayCloseOnPop = 0x00; + LastItemId = 0; + LastItemStatusFlags = 0; + LastItemRect = LastItemDisplayRect = ImRect(); + NavHideHighlightOneFrame = false; + NavHasScroll = false; + NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; + NavLayerCurrent = 0; + NavLayerCurrentMask = 1 << 0; + MenuBarAppending = false; + MenuBarOffsetX = 0.0f; + StateStorage = NULL; + LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical; + ItemWidth = 0.0f; + ItemFlags = ImGuiItemFlags_Default_; + TextWrapPos = -1.0f; + memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); + + IndentX = 0.0f; + GroupOffsetX = 0.0f; + ColumnsOffsetX = 0.0f; + ColumnsSet = NULL; + } +}; + +// Windows data +struct IMGUI_API ImGuiWindow +{ + char* Name; + ImGuiID ID; // == ImHash(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImVec2 PosFloat; + ImVec2 Pos; // Position rounded-up to nearest pixel + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars. + ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc. + ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() + ImRect ContentsRegionRect; // Maximum visible content position in window coordinates. ~~ (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis + ImVec2 WindowPadding; // Window padding at the time of begin. + float WindowRounding; // Window rounding at the time of begin. + float WindowBorderSize; // Window border size at the time of begin. + ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID ChildId; // Id of corresponding item in parent window (for child windows) + ImVec2 Scroll; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + bool ScrollbarX, ScrollbarY; + ImVec2 ScrollbarSizes; + bool Active; // Set to true on Begin(), unless Collapsed + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool CollapseToggleWanted; + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool CloseButton; // Set when the window has a close button (p_open != NULL) + int BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. + int BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order related issues. + int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + int AutoFitFramesX, AutoFitFramesY; + bool AutoFitOnlyGrows; + int AutoFitChildAxises; + ImGuiDir AutoPosLastDirection; + int HiddenFrames; + ImGuiCond SetWindowPosAllowFlags; // store condition flags for next SetWindowPos() call. + ImGuiCond SetWindowSizeAllowFlags; // store condition flags for next SetWindowSize() call. + ImGuiCond SetWindowCollapsedAllowFlags; // store condition flags for next SetWindowCollapsed() call. + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + + ImGuiDrawContext DC; // Temporary per-window data, reset at the beginning of the frame + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack + ImRect ClipRect; // = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2. + ImRect WindowRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. + ImRect InnerRect; + int LastFrameActive; + float ItemWidthDefault; + ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // Scale multiplier per-window + ImDrawList* DrawList; + ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. + ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. + ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. + ImGuiWindow* RootWindowForTabbing; // Point to ourself or first ancestor which can be CTRL-Tabbed into. + ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. + + ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) + ImGuiID NavLastIds[2]; // Last known NavId for this window, per layer (0/1) + ImRect NavRectRel[2]; // Reference rectangle, in window relative space + + // Navigation / Focus + // FIXME-NAV: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext + int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() + int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through) + int FocusIdxAllRequestCurrent; // Item being requested for focus + int FocusIdxTabRequestCurrent; // Tab-able item being requested for focus + int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame) + int FocusIdxTabRequestNext; // " + +public: + ImGuiWindow(ImGuiContext* context, const char* name); + ~ImGuiWindow(); + + ImGuiID GetID(const char* str, const char* str_end = NULL); + ImGuiID GetID(const void* ptr); + ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); + ImGuiID GetIDFromRectangle(const ImRect& r_abs); + + // We don't use g.FontSize because the window may be != g.CurrentWidow. + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } + float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } + float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } + float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } +}; + +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiItemHoveredDataBackup +{ + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; + ImRect LastItemDisplayRect; + + ImGuiItemHoveredDataBackup() { Backup(); } + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; } + void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } +}; + +//----------------------------------------------------------------------------- +// Internal API +// No guarantee of forward compatibility here. +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) + // If this ever crash because g.CurrentWindow is NULL it means that either + // - ImGui::NewFrame() has never been called, which is illegal. + // - You are calling ImGui functions after ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } + inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByName(const char* name); + IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void BringWindowToFront(ImGuiWindow* window); + IMGUI_API void BringWindowToBack(ImGuiWindow* window); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); + IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); + + IMGUI_API void Initialize(ImGuiContext* context); + IMGUI_API void Shutdown(ImGuiContext* context); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); + IMGUI_API ImGuiID GetActiveID(); + IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void ClearActiveID(); + IMGUI_API void SetHoveredID(ImGuiID id); + IMGUI_API ImGuiID GetHoveredID(); + IMGUI_API void KeepAliveID(ImGuiID id); + + IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); + IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested + IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); + IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); + IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + + IMGUI_API void SetCurrentFont(ImFont* font); + + IMGUI_API void OpenPopupEx(ImGuiID id); + IMGUI_API void ClosePopup(ImGuiID id); + IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window); + IMGUI_API bool IsPopupOpen(ImGuiID id); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); + + IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); + IMGUI_API void ActivateItem(ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. + + IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode); + IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); + IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); + + IMGUI_API void Scrollbar(ImGuiLayoutType direction); + IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). not exposed because it is misleading what it doesn't have an effect on regular layout. + IMGUI_API bool SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f); + + IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API void ClearDragDrop(); + IMGUI_API bool IsDragDropPayloadBeingAccepted(); + + // FIXME-WIP: New Columns API + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index = -1); + + // NB: All position are in absolute pixels coordinates (never using window coordinates internally) + // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. + IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); + IMGUI_API void RenderTriangle(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); + IMGUI_API void RenderBullet(ImVec2 pos); + IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. + + IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); + IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0); + IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); + IMGUI_API bool ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags = 0); + + IMGUI_API bool SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format); + + IMGUI_API bool DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power); + IMGUI_API bool DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format); + + IMGUI_API bool InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision); + + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); + + IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging + IMGUI_API void TreePushRawID(ImGuiID id); + + IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size); + + IMGUI_API int ParseFormatPrecision(const char* fmt, int default_value); + IMGUI_API float RoundScalar(float value, int decimal_precision); + + // Shade functions + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x); + IMGUI_API void ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); + +} // namespace ImGui + +// ImFontAtlas internals +IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); +IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif diff --git a/apps/bench_shared_offscreen/imgui/stb_rect_pack.h b/apps/bench_shared_offscreen/imgui/stb_rect_pack.h new file mode 100644 index 00000000..2b07dcc8 --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/stb_rect_pack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v0.11 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// +// Version history: +// +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/apps/bench_shared_offscreen/imgui/stb_textedit.h b/apps/bench_shared_offscreen/imgui/stb_textedit.h new file mode 100644 index 00000000..4b731a0c --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/stb_textedit.h @@ -0,0 +1,1322 @@ +// [ImGui] this is a slightly modified version of stb_truetype.h 1.9. Those changes would need to be pushed into nothings/sb +// [ImGui] - fixed linestart handler when over last character of multi-line buffer + simplified existing code (#588, #815) +// [ImGui] - fixed a state corruption/crash bug in stb_text_redo and stb_textedit_discard_redo (#715) +// [ImGui] - fixed a crash bug in stb_textedit_discard_redo (#681) +// [ImGui] - fixed some minor warnings + +// stb_textedit.h - v1.9 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Todo: +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + short insert_length; + short delete_length; + short char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + short undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = stb_text_locate_coord(str, x, y); + if (state->select_start == state->select_end) + state->select_start = state->cursor; + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + i = 0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicity clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + STB_TEXTEDIT_CHARTYPE *text = (STB_TEXTEDIT_CHARTYPE *) ctext; + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // remove the undo since we didn't actually insert the characters + if (state->undostate.undo_point) + --state->undostate.undo_point; + return 0; +} + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicity clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str,state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // now find character position down a row + if (find.length) { + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + int start = find.first_char + find.length; + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // can only go up if there's a previous row + if (find.prev_first != find.first_char) { + // now find character position up a row + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + +// @TODO: +// STB_TEXTEDIT_K_PGUP - move cursor up a page +// STB_TEXTEDIT_K_PGDOWN - move cursor down a page + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) ((size_t)state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) ((size_t)state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // delete n characters from all other records + state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((size_t)(STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05 + } + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point, state->undo_rec + state->redo_point-1, (size_t) ((size_t)(STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (short) insert_len; + r->delete_length = (short) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point = state->undo_char_point + (short) insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - (short) u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} +#endif//STB_TEXTEDIT_IMPLEMENTATION diff --git a/apps/bench_shared_offscreen/imgui/stb_truetype.h b/apps/bench_shared_offscreen/imgui/stb_truetype.h new file mode 100644 index 00000000..a08e929f --- /dev/null +++ b/apps/bench_shared_offscreen/imgui/stb_truetype.h @@ -0,0 +1,4853 @@ +// stb_truetype.h - v1.19 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/apps/bench_shared_offscreen/inc/Application.h b/apps/bench_shared_offscreen/inc/Application.h new file mode 100644 index 00000000..5032b120 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Application.h @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef APPLICATION_H +#define APPLICATION_H + +// DAR This renderer only uses the CUDA Driver API! +// (CMake uses the CUDA_CUDA_LIBRARY which is nvcuda.lib. At runtime that loads nvcuda.dll from the driver.) +//#include + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +//#include "imgui.h" +//#define IMGUI_DEFINE_MATH_OPERATORS 1 +//#include "imgui_internal.h" +//#include "imgui_impl_glfw_gl3.h" + +//#include +//#if defined( _WIN32 ) +//#include +//#endif + +#include "inc/Camera.h" +#include "inc/Options.h" +//#include "inc/Rasterizer.h" +#include "inc/TonemapperGUI.h" +#include "inc/Raytracer.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/Timer.h" + +#include + +#include "shaders/material_definition.h" + +// This include gl.h and needs to be done after glew.h +//#include + +// assimp include files. +#include +#include +#include +#include +#include + +#include +#include + + +constexpr int APP_EXIT_SUCCESS = 0; +constexpr int APP_ERROR_UNKNOWN = -1; +constexpr int APP_ERROR_CREATE_WINDOW = -2; +constexpr int APP_ERROR_GLFW_INIT = -3; +constexpr int APP_ERROR_GLEW_INIT = -4; +constexpr int APP_ERROR_APP_INIT = -5; + + +enum GuiState +{ + GUI_STATE_NONE, + GUI_STATE_ORBIT, + GUI_STATE_PAN, + GUI_STATE_DOLLY, + GUI_STATE_FOCUS +}; + + +enum KeywordScene +{ + KS_ALBEDO, + KS_ALBEDO_TEXTURE, + KS_CUTOUT_TEXTURE, + KS_ROUGHNESS, + KS_ABSORPTION, + KS_ABSORPTION_SCALE, + KS_IOR, + KS_THINWALLED, + KS_MATERIAL, + KS_IDENTITY, + KS_PUSH, + KS_POP, + KS_ROTATE, + KS_SCALE, + KS_TRANSLATE, + KS_MODEL +}; + + +class Application +{ +public: + + Application(/* GLFWwindow* window, */ const Options& options); + ~Application(); + + bool isValid() const; + + void reshape(const int w, const int h); + //bool render(); + void benchmark(); + + //void display(); + + //void guiNewFrame(); + //void guiWindow(); + //void guiEventHandler(); + //void guiReferenceManual(); // The ImGui "programming manual" in form of a live window. + //void guiRender(); + +private: + bool loadSystemDescription(const std::string& filename); + bool saveSystemDescription(); + bool loadSceneDescription(const std::string& filename); + + void restartRendering(); + + bool screenshot(const bool tonemap); + + void createPictures(); + void createCameras(); + void createLights(); + + void appendInstance(std::shared_ptr& group, + std::shared_ptr geometry, // Baseclass Node to be prepared for different geometric primitives. + const dp::math::Mat44f& matrix, + const std::string& reference, + unsigned int& idInstance); + + std::shared_ptr createASSIMP(const std::string& filename); + std::shared_ptr traverseScene(const struct aiScene *scene, const unsigned int indexSceneBase, const struct aiNode* node); + + void calculateTangents(std::vector& attributes, const std::vector& indices); + + //void guiRenderingIndicator(const bool isRendering); + + bool loadString(const std::string& filename, std::string& text); + bool saveString(const std::string& filename, const std::string& text); + std::string getDateTime(); + void convertPath(std::string& path); + void convertPath(char* path); + + bool generatePNG(const unsigned int width, + const unsigned int height, + const unsigned char r, + const unsigned char g, + const unsigned char b); + +private: + //GLFWwindow* m_window; + bool m_isValid; + + //GuiState m_guiState; + //bool m_isVisibleGUI; + + // Command line options: + int m_width; // Client window size. + int m_height; + int m_mode; // Application mode 0 = interactive, 1 = batched benchmark (single shot). + bool m_optimize; // Command line option to let the assimp importer optimize the graph (sorts by material). + + // System options: + int m_strategy; // "strategy" // Ignored in this renderer. Always behaves like RS_INTERACTIVE_MULTI_GPU_LOCAL_COPY. + int m_maskDevices; // "devicesMask" // Bitmask with enabled devices, default 0x00FFFFFF for 24 devices. Only the visible ones will be used. + size_t m_sizeArena; // "arenaSize" // Default size for Arena allocations in mega-bytes. + int m_light; // "light" + int m_miss; // "miss" + std::string m_environment; // "envMap" + int m_interop; // "interop" // 0 = none all through host, 1 = register texture image, 2 = register pixel buffer + int m_peerToPeer; // "peerToPeer" // Bitfield controlling P2P resource sharing.: + // Bit 0 = Allow peer-to-peer access via PCI-E (default off) + // Bit 1 = Share material textures (default on) + // Bit 2 = Share GAS (default on) + // Bit 3 = Share environment texture and CDFs (default off) + bool m_present; // "present" + //bool m_presentNext; // (derived) + //double m_presentAtSecond; // (derived) + //bool m_previousComplete; // (derived) // Prevents spurious benchmark prints and image updates. + + // GUI Data representing raytracer settings. + LensShader m_lensShader; // "lensShader" + int2 m_pathLengths; // "pathLengths" // min, max + int2 m_resolution; // "resolution" // The actual size of the rendering, independent of the window's client size. (Preparation for final frame rendering.) + int2 m_tileSize; // "tileSize" // Multi-GPU distribution tile size. Must be power-of-two values. + int m_samplesSqrt; // "sampleSqrt" + float m_epsilonFactor; // "epsilonFactor" + float m_environmentRotation; // "envRotation" + float m_clockFactor; // "clockFactor" + int m_peerSharing; // peerSharing bitfield. Bit 0 = texture sharing, bit 1 = GAS sharing. + + std::string m_prefixScreenshot; // "prefixScreenshot", allows to set a path and the prefix for the screenshot filename. spp, data, time and extension will be appended. + + TonemapperGUI m_tonemapperGUI; // "gamma", "whitePoint", "burnHighlights", "crushBlacks", "saturation", "brightness" + + Camera m_camera; // "center", "camera" + + float m_mouseSpeedRatio; + + Timer m_timer; + + std::map m_mapKeywordScene; + + //std::unique_ptr m_rasterizer; + + std::unique_ptr m_raytracer; + + DeviceState m_state; + + // The scene description: + // Unique identifiers per host scene node. + unsigned int m_idGroup; + unsigned int m_idInstance; + unsigned int m_idGeometry; + + std::shared_ptr m_scene; // Root group node of the scene. + + std::vector< std::shared_ptr > m_geometries; // All geometries in the scene. Baseclass Node to be prepared for different geometric primitives. + + // For the runtime generated objects, this allows to find geometries with the same type and construction parameters. + // DAR HACK DEBUG No Instancing in this versions: std::map m_mapGeometries; + + // For all model file format loaders. Allows instancing of full models in the host side scene graph. + std::map< std::string, std::shared_ptr > m_mapGroups; + + std::vector m_cameras; + std::vector m_lights; + std::vector m_materialsGUI; + + // Map of local material names to indices in the m_materialsGUI vector. + std::map m_mapMaterialReferences; + + std::map m_mapPictures; + + std::vector m_remappedMeshIndices; +}; + +#endif // APPLICATION_H + diff --git a/apps/bench_shared_offscreen/inc/Arena.h b/apps/bench_shared_offscreen/inc/Arena.h new file mode 100644 index 00000000..abe50a44 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Arena.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef ARENA_H +#define ARENA_H + +#include + +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include + +namespace cuda +{ + enum Usage + { + USAGE_STATIC, + USAGE_TEMP + }; + + class Arena; + + struct Block + { + Block(cuda::Arena* arena = nullptr, const CUdeviceptr addr = 0, const size_t size = 0, const CUdeviceptr ptr = 0); + + bool isValid() const; + bool operator<(const cuda::Block& rhs); + + cuda::Arena* m_arena; // The arena which owns this block. + CUdeviceptr m_addr; // The internal address inside the arena of the memory containing the aligned block at ptr. + size_t m_size; // The internal size of the allocation starting at m_addr. + CUdeviceptr m_ptr; // The aligned pointer to size bytes the user requested. + }; + + + class Arena + { + public: + Arena(const size_t size); + ~Arena(); + + bool allocBlock(cuda::Block& block, const size_t size, const size_t alignment, const cuda::Usage usage); + void freeBlock(const cuda::Block& block); + + private: + CUdeviceptr m_addr; // The start pointer of the CUDA memory for this arena. This is 256 bytes aligned due to cuMalloc(). + size_t m_size; // The overall size of the arena in bytes. + std::list m_blocksFree; // A list of free blocks inside this arena. This is normally very short because static and temporary allocations are not interleaved. + }; + + + class ArenaAllocator + { + public: + ArenaAllocator(const size_t sizeArenaBytes); + ~ArenaAllocator(); + + CUdeviceptr alloc(const size_t size, const size_t alignment, const cuda::Usage usage = cuda::USAGE_STATIC); + void free(const CUdeviceptr ptr); + + size_t getSizeMemoryAllocated() const; + + private: + size_t m_sizeArenaBytes; // The minimum size an arena is allocated with. If calls alloc() with a bigger size, that will be used. + size_t m_sizeMemoryAllocated; // The number of bytes which have been allocated (with alignment). + std::vector m_arenas; // A number of Arenas managing a CUdeviceptr with at least m_sizeArenaBytes each. + std::map m_blocksAllocated; // Map the user pointer to the internal block. This abstracts Arena and Block from the user. (Kind of expensive.) + }; + +} // namespace cuda + +#endif // ARENA_H diff --git a/apps/bench_shared_offscreen/inc/Camera.h b/apps/bench_shared_offscreen/inc/Camera.h new file mode 100644 index 00000000..29b72daa --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Camera.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CAMERA_H +#define CAMERA_H + +#include + + +class Camera +{ +public: + Camera(); + //~Camera(); + + void setResolution(int w, int h); + void setBaseCoordinates(int x, int y); + void setSpeedRatio(float f); + void setFocusDistance(float f); + + void orbit(int x, int y); + void pan(int x, int y); + void dolly(int x, int y); + void focus(int x, int y); + void zoom(float x); + + bool getFrustum(float3& p, float3& u, float3& v, float3& w, bool force = false); + float getAspectRatio() const; + void markDirty(); + +public: // public just to be able to load and save them easily. + float3 m_center; // Center of interest point, around which is orbited (and the sharp plane of a depth of field camera). + float m_distance; // Distance of the camera from the center of interest. + float m_phi; // Range [0.0f, 1.0f] from positive x-axis 360 degrees around the latitudes. + float m_theta; // Range [0.0f, 1.0f] from negative to positive y-axis. + float m_fov; // In degrees. Default is 60.0f + +private: + bool setDelta(int x, int y); + +private: + int m_widthResolution; + int m_heightResolution; + float m_aspect; // width / height + int m_baseX; + int m_baseY; + float m_speedRatio; + + // Derived values: + int m_dx; + int m_dy; + bool m_changed; + + float3 m_cameraP; + float3 m_cameraU; + float3 m_cameraV; + float3 m_cameraW; +}; + +#endif // CAMERA_H diff --git a/apps/bench_shared_offscreen/inc/CheckMacros.h b/apps/bench_shared_offscreen/inc/CheckMacros.h new file mode 100644 index 00000000..57060aa7 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/CheckMacros.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CHECK_MACROS_H +#define CHECK_MACROS_H + +#include +#include + +#include "inc/MyAssert.h" + +#define CU_CHECK(call) \ +{ \ + const CUresult result = call; \ + if (result != CUDA_SUCCESS) \ + { \ + const char* name; \ + cuGetErrorName(result, &name); \ + const char *error; \ + cuGetErrorString(result, &error); \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ") " << name << ": " << error; \ + MY_ASSERT(!"CU_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +#define CU_CHECK_NO_THROW(call) \ +{ \ + const CUresult result = call; \ + if (result != CUDA_SUCCESS) \ + { \ + const char* name; \ + cuGetErrorName(result, &name); \ + const char *error; \ + cuGetErrorString(result, &error); \ + std::cerr << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ") " << name << ": " << error << '\n'; \ + MY_ASSERT(!"CU_CHECK_NO_THROW"); \ + } \ +} + +#define OPTIX_CHECK(call) \ +{ \ + const OptixResult result = call; \ + if (result != OPTIX_SUCCESS) \ + { \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")"; \ + MY_ASSERT(!"OPTIX_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +#define OPTIX_CHECK_NO_THROW(call) \ +{ \ + const OptixResult result = call; \ + if (result != OPTIX_SUCCESS) \ + { \ + std::cerr << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")\n"; \ + MY_ASSERT(!"OPTIX_CHECK_NO_THROW"); \ + } \ +} + + +#endif // CHECK_MACROS_H diff --git a/apps/bench_shared_offscreen/inc/Device.h b/apps/bench_shared_offscreen/inc/Device.h new file mode 100644 index 00000000..12fc8eb4 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Device.h @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef DEVICE_H +#define DEVICE_H + +#include "shaders/config.h" + +#include +// This is needed for the __align__ only. +#include + +#include + +// OptiX 7 function table structure. +#include + +#include "inc/Arena.h" +#include "inc/MaterialGUI.h" +#include "inc/Picture.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/MyAssert.h" + +#include "shaders/system_data.h" +#include "shaders/per_ray_data.h" + +#include +#include +#include + +struct DeviceAttribute +{ + int maxThreadsPerBlock; + int maxBlockDimX; + int maxBlockDimY; + int maxBlockDimZ; + int maxGridDimX; + int maxGridDimY; + int maxGridDimZ; + int maxSharedMemoryPerBlock; + int sharedMemoryPerBlock; + int totalConstantMemory; + int warpSize; + int maxPitch; + int maxRegistersPerBlock; + int registersPerBlock; + int clockRate; + int textureAlignment; + int gpuOverlap; + int multiprocessorCount; + int kernelExecTimeout; + int integrated; + int canMapHostMemory; + int computeMode; + int maximumTexture1dWidth; + int maximumTexture2dWidth; + int maximumTexture2dHeight; + int maximumTexture3dWidth; + int maximumTexture3dHeight; + int maximumTexture3dDepth; + int maximumTexture2dLayeredWidth; + int maximumTexture2dLayeredHeight; + int maximumTexture2dLayeredLayers; + int maximumTexture2dArrayWidth; + int maximumTexture2dArrayHeight; + int maximumTexture2dArrayNumslices; + int surfaceAlignment; + int concurrentKernels; + int eccEnabled; + int pciBusId; + int pciDeviceId; + int tccDriver; + int memoryClockRate; + int globalMemoryBusWidth; + int l2CacheSize; + int maxThreadsPerMultiprocessor; + int asyncEngineCount; + int unifiedAddressing; + int maximumTexture1dLayeredWidth; + int maximumTexture1dLayeredLayers; + int canTex2dGather; + int maximumTexture2dGatherWidth; + int maximumTexture2dGatherHeight; + int maximumTexture3dWidthAlternate; + int maximumTexture3dHeightAlternate; + int maximumTexture3dDepthAlternate; + int pciDomainId; + int texturePitchAlignment; + int maximumTexturecubemapWidth; + int maximumTexturecubemapLayeredWidth; + int maximumTexturecubemapLayeredLayers; + int maximumSurface1dWidth; + int maximumSurface2dWidth; + int maximumSurface2dHeight; + int maximumSurface3dWidth; + int maximumSurface3dHeight; + int maximumSurface3dDepth; + int maximumSurface1dLayeredWidth; + int maximumSurface1dLayeredLayers; + int maximumSurface2dLayeredWidth; + int maximumSurface2dLayeredHeight; + int maximumSurface2dLayeredLayers; + int maximumSurfacecubemapWidth; + int maximumSurfacecubemapLayeredWidth; + int maximumSurfacecubemapLayeredLayers; + int maximumTexture1dLinearWidth; + int maximumTexture2dLinearWidth; + int maximumTexture2dLinearHeight; + int maximumTexture2dLinearPitch; + int maximumTexture2dMipmappedWidth; + int maximumTexture2dMipmappedHeight; + int computeCapabilityMajor; + int computeCapabilityMinor; + int maximumTexture1dMipmappedWidth; + int streamPrioritiesSupported; + int globalL1CacheSupported; + int localL1CacheSupported; + int maxSharedMemoryPerMultiprocessor; + int maxRegistersPerMultiprocessor; + int managedMemory; + int multiGpuBoard; + int multiGpuBoardGroupId; + int hostNativeAtomicSupported; + int singleToDoublePrecisionPerfRatio; + int pageableMemoryAccess; + int concurrentManagedAccess; + int computePreemptionSupported; + int canUseHostPointerForRegisteredMem; + int canUse64BitStreamMemOps; + int canUseStreamWaitValueNor; + int cooperativeLaunch; + int cooperativeMultiDeviceLaunch; + int maxSharedMemoryPerBlockOptin; + int canFlushRemoteWrites; + int hostRegisterSupported; + int pageableMemoryAccessUsesHostPageTables; + int directManagedMemAccessFromHost; +}; + +struct DeviceProperty +{ + unsigned int rtcoreVersion; + unsigned int limitMaxTraceDepth; + unsigned int limitMaxTraversableGraphDepth; + unsigned int limitMaxPrimitivesPerGas; + unsigned int limitMaxInstancesPerIas; + unsigned int limitMaxInstanceId; + unsigned int limitNumBitsInstanceVisibilityMask; + unsigned int limitMaxSbtRecordsPerGas; + unsigned int limitMaxSbtOffset; +}; + + +// All programs outside the hit groups do not have any per program data. +struct SbtRecordHeader +{ + __align__(OPTIX_SBT_RECORD_ALIGNMENT) char header[OPTIX_SBT_RECORD_HEADER_SIZE]; +}; + +// The hit group gets per instance data in addition. +template +struct SbtRecordData +{ + __align__(OPTIX_SBT_RECORD_ALIGNMENT) char header[OPTIX_SBT_RECORD_HEADER_SIZE]; + T data; +}; + +typedef SbtRecordData SbtRecordGeometryInstanceData; + + +enum ModuleIdentifier +{ + MODULE_ID_RAYGENERATION, + MODULE_ID_EXCEPTION, + MODULE_ID_MISS, + MODULE_ID_CLOSESTHIT, + MODULE_ID_ANYHIT, + MODULE_ID_LENS_SHADER, + MODULE_ID_LIGHT_SAMPLE, + MODULE_ID_BXDF_DIFFUSE, + MODULE_ID_BXDF_SPECULAR, + MODULE_ID_BXDF_GGX_SMITH, + NUM_MODULE_IDENTIFIERS +}; + + +enum ProgramGroupId +{ + // Programs using SbtRecordHeader + PGID_RAYGENERATION, + PGID_EXCEPTION, + PGID_MISS_RADIANCE, + PGID_MISS_SHADOW, + // Direct Callables using SbtRecordHeader + PGID_LENS_PINHOLE, + FIRST_DIRECT_CALLABLE_ID = PGID_LENS_PINHOLE, + PGID_LENS_FISHEYE, + PGID_LENS_SPHERE, + PGID_LIGHT_ENV, + PGID_LIGHT_AREA, + PGID_BRDF_DIFFUSE_SAMPLE, + PGID_BRDF_DIFFUSE_EVAL, + PGID_BRDF_SPECULAR_SAMPLE, + PGID_BRDF_SPECULAR_EVAL, + PGID_BSDF_SPECULAR_SAMPLE, + PGID_BSDF_SPECULAR_EVAL, + PGID_BRDF_GGX_SMITH_SAMPLE, + PGID_BRDF_GGX_SMITH_EVAL, + PGID_BSDF_GGX_SMITH_SAMPLE, + PGID_BSDF_GGX_SMITH_EVAL, + LAST_DIRECT_CALLABLE_ID = PGID_BSDF_GGX_SMITH_EVAL, + // Programs using SbtRecordGeometryInstanceData + PGID_HIT_RADIANCE, + PGID_HIT_SHADOW, + PGID_HIT_RADIANCE_CUTOUT, + PGID_HIT_SHADOW_CUTOUT, + // Number of all program group entries. + NUM_PROGRAM_GROUP_IDS +}; + +// This is in preparation for more than one geometric primitive type. +enum PrimitiveType +{ + PT_UNKNOWN, // It's an error when this is still set. + PT_TRIANGLES +}; + + +struct GeometryData +{ + GeometryData() + : primitiveType(PT_UNKNOWN) + , owner(-1) + , traversable(0) + , d_attributes(0) + , d_indices(0) + , numAttributes(0) + , numIndices(0) + , d_gas(0) + { + info = {}; + } + + PrimitiveType primitiveType; // In preparation for more geometric primitive types in a renderer. Used during SBT creation to assign the proper hit records. + int owner; // The device index which originally allocated all device side memory below. Needed when sharing GeometryData, resp. when freeing it. + OptixTraversableHandle traversable; // The traversable handle for this GAS. Assigned to the Instance above it. + CUdeviceptr d_attributes; // Array of VertexAttribute structs. + CUdeviceptr d_indices; // Array of unsigned int indices. + size_t numAttributes; // Count of attributes structs. + size_t numIndices; // Count of unsigned int indices (not triplets for triangles). + CUdeviceptr d_gas; // The geometry AS for the traversable handle. +#if (OPTIX_VERSION >= 70600) + OptixRelocationInfo info; // The relocation info allows to check if the AS is compatible across OptiX contexts. + // (In the NVLINK case that isn't really required, because the GPU configuration must be homogeneous inside an NVLINK island.) +#else + OptixAccelRelocationInfo info; +#endif +}; + +struct InstanceData +{ + InstanceData(unsigned int geometry, int material, int light) + : idGeometry(geometry) + , idMaterial(material) + , idLight(light) + { + } + + unsigned int idGeometry; + int idMaterial; // Negative is an error. + int idLight; // Negative means no light. +}; + +// GUI controllable settings in the device. +struct DeviceState +{ + int2 resolution; + int2 tileSize; + int2 pathLengths; + int samplesSqrt; + LensShader lensShader; + float epsilonFactor; + float envRotation; + float clockFactor; +}; + + +class Device +{ +public: + Device(const int ordinal, // The original CUDA ordinal ID. + const int index, // The zero based index of this device, required for multi-GPU work distribution. + const int count, // The number of active devices, required for multi-GPU work distribution. + const int miss, // The miss shader ID to use. + //const int interop, // The interop mode to use. + //const unsigned int tex, // OpenGL HDR texture object handle + //const unsigned int pbo, // OpenGL PBO handle. + const size_t sizeArena); // The default Arena size in mega-bytes. + ~Device(); + + //bool matchUUID(const char* uuid); + //bool matchLUID(const char* luid, const unsigned int nodeMask); + + void initCameras(const std::vector& cameras); + void initLights(const std::vector& lights); + void initMaterials(const std::vector& materialsGUI); + + GeometryData createGeometry(std::shared_ptr geometry); + void destroyGeometry(GeometryData& data); + void createInstance(const GeometryData& geometryData, const InstanceData& data, const float matrix[12]); + void createTLAS(); + void createHitGroupRecords(const std::vector& geometryData, const unsigned int stride, const unsigned int index); + + void updateCamera(const int idCamera, const CameraDefinition& camera); + void updateLight(const int idLight, const LightDefinition& light); + void updateMaterial(const int idMaterial, const MaterialGUI& materialGUI); + + void setState(const DeviceState& state); + void compositor(Device* other); + + void activateContext() const ; + void synchronizeStream() const; + void render(const unsigned int iterationIndex, void** buffer); + //void updateDisplayTexture(); + const void* getOutputBufferHost(); + + CUdeviceptr memAlloc(const size_t size, const size_t alignment, const cuda::Usage usage = cuda::USAGE_STATIC); + void memFree(const CUdeviceptr ptr); + + size_t getMemoryFree() const; + size_t getMemoryAllocated() const; + + Texture* initTexture(const std::string& name, const Picture* picture, const unsigned int flags); + void shareTexture(const std::string & name, const Texture* shared); + +private: + OptixResult initFunctionTable(); + void initDeviceAttributes(); + void initDeviceProperties(); + void initPipeline(); + +public: + // Constructor arguments: + int m_ordinal; // The ordinal number of this CUDA device. Used to get the CUdevice handle m_cudaDevice. + int m_index; // The index inside the m_devicesActive vector. + int m_count; // The number of active devices. + int m_miss; // Type of environment miss shader to use. 0 = black no light, 1 = constant white, 2 = spherical HDR env map. + //int m_interop; // The interop mode to use. + //unsigned int m_tex; // The OpenGL HDR texture object. + //unsigned int m_pbo; // The OpenGL PixelBufferObject handle when interop should be used. 0 when not. + + float m_clockFactor; // Clock Factor scaled by CLOCK_FACTOR_SCALE (1.0e-9f) for USE_TIME_VIEW + + CUuuid m_deviceUUID; + + // Not actually used because this only works under Windows WDDM mode, not in TCC mode! + // (Though using LUID is recommended under Windows when possible because you can potentially + // get the same UUID for MS hybrid configurations (like when running under Remote Desktop). + char m_deviceLUID[8]; + unsigned int m_nodeMask; + + std::string m_deviceName; + std::string m_devicePciBusId; // domain:bus:device.function, required to find matching CUDA device via NVML. + + DeviceAttribute m_deviceAttribute; // CUDA + DeviceProperty m_deviceProperty; // OptiX + + CUdevice m_cudaDevice; // The CUdevice handle of the CUDA device ordinal. (Usually identical to m_ordinal.) + CUcontext m_cudaContext; + CUstream m_cudaStream; + + OptixFunctionTable m_api; + OptixDeviceContext m_optixContext; + + std::vector m_moduleFilenames; + + OptixPipeline m_pipeline; + + OptixShaderBindingTable m_sbt; + + CUdeviceptr m_d_sbtRecordHeaders; + + SbtRecordGeometryInstanceData m_sbtRecordHitRadiance; + SbtRecordGeometryInstanceData m_sbtRecordHitShadow; + + SbtRecordGeometryInstanceData m_sbtRecordHitRadianceCutout; + SbtRecordGeometryInstanceData m_sbtRecordHitShadowCutout; + + CUdeviceptr m_d_ias; + + std::vector m_instances; + std::vector m_instanceData; // idGeometry, idMaterial, idLight + + std::vector m_sbtRecordGeometryInstanceData; + SbtRecordGeometryInstanceData* m_d_sbtRecordGeometryInstanceData; + + SystemData m_systemData; // This contains the root traversable handle as well. + SystemData* m_d_systemData; // Device side CUdeviceptr of the system data. + + int m_launchWidth; + + bool m_isDirtySystemData; + bool m_isDirtyOutputBuffer; + bool m_ownsSharedBuffer; // DAR DEBUG Used in asserts only. + + std::map m_mapTextures; + + std::vector m_materials; // Staging data for the device side sysData.materialDefinitions + + // From the previously derived class DeviceMultiGPULocalCopy. + //CUgraphicsResource m_cudaGraphicsResource; // The handle for the registered OpenGL PBO or texture when using interop. + + CUmodule m_moduleCompositor; + CUfunction m_functionCompositor; + CUdeviceptr m_d_compositorData; + + std::vector m_bufferHost; + + cuda::ArenaAllocator* m_allocator; + + // The sum of all texture CUarray resp. CUmipmappedArray data sent in bytes + // (without GPU padding and row alignment). + size_t m_sizeMemoryTextureArrays; +}; + +#endif // DEVICE_H diff --git a/apps/bench_shared_offscreen/inc/MaterialGUI.h b/apps/bench_shared_offscreen/inc/MaterialGUI.h new file mode 100644 index 00000000..7f4b3277 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/MaterialGUI.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MATERIAL_GUI_H +#define MATERIAL_GUI_H + +#include "shaders/function_indices.h" + +#include + + // Host side GUI material parameters +struct MaterialGUI +{ + std::string name; // The name used in the scene description to identify this material instance. + std::string nameTextureAlbedo; // The filename of the albedo texture for this material. Empty when none. + std::string nameTextureCutout; // The filename of the cutout opacity texture for this material. Empty when none. + FunctionIndex indexBSDF; // BSDF index to use in the closest hit program. + float3 albedo; // Tint, throughput change for specular materials. + float3 absorptionColor; // absorptionColor and absorptionScale together build the absorption coefficient + float absorptionScale; + float2 roughness; // Anisotropic roughness for microfacet distributions. + float ior; // Index of Refraction. + bool thinwalled; +}; + +#endif // MATERIAL_GUI_H diff --git a/apps/bench_shared_offscreen/inc/MyAssert.h b/apps/bench_shared_offscreen/inc/MyAssert.h new file mode 100644 index 00000000..6e2234a5 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/MyAssert.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MY_ASSERT_H +#define MY_ASSERT_H + +#undef MY_STATIC_ASSERT + +#define MY_STATIC_ASSERT(exp) static_assert(exp, #exp); + +#undef MY_ASSERT + +#if !defined(NDEBUG) + #if defined(_WIN32) // Windows 32/64-bit + #include + #define DBGBREAK() DebugBreak(); + #else + #define DBGBREAK() __builtin_trap(); + #endif + #define MY_ASSERT(expr) if (!(expr)) DBGBREAK() +#else + #define MY_ASSERT(expr) +#endif + +#if !defined(NDEBUG) + #define MY_VERIFY(expr) MY_ASSERT(expr) +#else + #define MY_VERIFY(expr) (static_cast(expr)) +#endif + +#endif // MY_ASSERT_H diff --git a/apps/bench_shared_offscreen/inc/NVMLImpl.h b/apps/bench_shared_offscreen/inc/NVMLImpl.h new file mode 100644 index 00000000..c104176a --- /dev/null +++ b/apps/bench_shared_offscreen/inc/NVMLImpl.h @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef NVML_IMPL_H +#define NVML_IMPL_H + +// This header ships with the CUDA Toolkit. +#include + + +#define NVML_CHECK(call) \ +{ \ + const nvmlReturn_t result = call; \ + if (result != NVML_SUCCESS) \ + { \ + std::ostringstream message; \ + message << "ERROR: " << __FILE__ << "(" << __LINE__ << "): " << #call << " (" << result << ")"; \ + MY_ASSERT(!"NVML_CHECK"); \ + throw std::runtime_error(message.str()); \ + } \ +} + +// These function entry point types and names support the automatic NVML API versioning inside nvml.h. +// Some of the NVML functions are versioned by a #define adding a version suffix (_v2, _v3) to the name, +// which requires a set of two macros to resolve the unversioned function name to the versioned one. + +#define FUNC_T_V(name) name##_t +#define FUNC_T(name) FUNC_T_V(name) + +#define FUNC_P_V(name) name##_t name +#define FUNC_P(name) FUNC_P_V(name) + +typedef nvmlReturn_t (*FUNC_T(nvmlInit))(void); +//typedef nvmlReturn_t (*FUNC_T(nvmlInitWithFlags))(unsigned int flags); +typedef nvmlReturn_t (*FUNC_T(nvmlShutdown))(void); +//typedef const char* (*FUNC_T(nvmlErrorString))(nvmlReturn_t result); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetDriverVersion))(char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetNVMLVersion))(char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetCudaDriverVersion))(int *cudaDriverVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetCudaDriverVersion_v2))(int *cudaDriverVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetProcessName))(unsigned int pid, char *name, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetCount))(unsigned int *unitCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetHandleByIndex))(unsigned int index, nvmlUnit_t *unit); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetUnitInfo))(nvmlUnit_t unit, nvmlUnitInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetLedState))(nvmlUnit_t unit, nvmlLedState_t *state); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetPsuInfo))(nvmlUnit_t unit, nvmlPSUInfo_t *psu); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetTemperature))(nvmlUnit_t unit, unsigned int type, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetFanSpeedInfo))(nvmlUnit_t unit, nvmlUnitFanSpeeds_t *fanSpeeds); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitGetDevices))(nvmlUnit_t unit, unsigned int *deviceCount, nvmlDevice_t *devices); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetHicVersion))(unsigned int *hwbcCount, nvmlHwbcEntry_t *hwbcEntries); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCount))(unsigned int *deviceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAttributes))(nvmlDevice_t device, nvmlDeviceAttributes_t *attributes); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByIndex))(unsigned int index, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleBySerial))(const char *serial, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByUUID))(const char *uuid, nvmlDevice_t *device); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHandleByPciBusId))(const char *pciBusId, nvmlDevice_t *device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetName))(nvmlDevice_t device, char *name, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBrand))(nvmlDevice_t device, nvmlBrandType_t *type); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetIndex))(nvmlDevice_t device, unsigned int *index); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSerial))(nvmlDevice_t device, char *serial, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryAffinity))(nvmlDevice_t device, unsigned int nodeSetSize, unsigned long *nodeSet, nvmlAffinityScope_t scope); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCpuAffinityWithinScope))(nvmlDevice_t device, unsigned int cpuSetSize, unsigned long *cpuSet, nvmlAffinityScope_t scope); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCpuAffinity))(nvmlDevice_t device, unsigned int cpuSetSize, unsigned long *cpuSet); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetCpuAffinity))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearCpuAffinity))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTopologyCommonAncestor))(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuTopologyLevel_t *pathInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTopologyNearestGpus))(nvmlDevice_t device, nvmlGpuTopologyLevel_t level, unsigned int *count, nvmlDevice_t *deviceArray); +//typedef nvmlReturn_t (*FUNC_T(nvmlSystemGetTopologyGpuSet))(unsigned int cpuNumber, unsigned int *count, nvmlDevice_t *deviceArray); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetP2PStatus))(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuP2PCapsIndex_t p2pIndex,nvmlGpuP2PStatus_t *p2pStatus); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetUUID))(nvmlDevice_t device, char *uuid, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetMdevUUID))(nvmlVgpuInstance_t vgpuInstance, char *mdevUuid, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMinorNumber))(nvmlDevice_t device, unsigned int *minorNumber); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBoardPartNumber))(nvmlDevice_t device, char* partNumber, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomVersion))(nvmlDevice_t device, nvmlInforomObject_t object, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomImageVersion))(nvmlDevice_t device, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetInforomConfigurationChecksum))(nvmlDevice_t device, unsigned int *checksum); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceValidateInforom))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDisplayMode))(nvmlDevice_t device, nvmlEnableState_t *display); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDisplayActive))(nvmlDevice_t device, nvmlEnableState_t *isActive); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPersistenceMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPciInfo))(nvmlDevice_t device, nvmlPciInfo_t *pci); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxPcieLinkGeneration))(nvmlDevice_t device, unsigned int *maxLinkGen); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxPcieLinkWidth))(nvmlDevice_t device, unsigned int *maxLinkWidth); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrPcieLinkGeneration))(nvmlDevice_t device, unsigned int *currLinkGen); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrPcieLinkWidth))(nvmlDevice_t device, unsigned int *currLinkWidth); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPcieThroughput))(nvmlDevice_t device, nvmlPcieUtilCounter_t counter, unsigned int *value); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPcieReplayCounter))(nvmlDevice_t device, unsigned int *value); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetClockInfo))(nvmlDevice_t device, nvmlClockType_t type, unsigned int *clock); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxClockInfo))(nvmlDevice_t device, nvmlClockType_t type, unsigned int *clock); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetApplicationsClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDefaultApplicationsClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetApplicationsClocks))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetClock))(nvmlDevice_t device, nvmlClockType_t clockType, nvmlClockId_t clockId, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxCustomerBoostClock))(nvmlDevice_t device, nvmlClockType_t clockType, unsigned int *clockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedMemoryClocks))(nvmlDevice_t device, unsigned int *count, unsigned int *clocksMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedGraphicsClocks))(nvmlDevice_t device, unsigned int memoryClockMHz, unsigned int *count, unsigned int *clocksMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t *isEnabled, nvmlEnableState_t *defaultIsEnabled); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t enabled); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetDefaultAutoBoostedClocksEnabled))(nvmlDevice_t device, nvmlEnableState_t enabled, unsigned int flags); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFanSpeed))(nvmlDevice_t device, unsigned int *speed); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFanSpeed_v2))(nvmlDevice_t device, unsigned int fan, unsigned int * speed); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTemperature))(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTemperatureThreshold))(nvmlDevice_t device, nvmlTemperatureThresholds_t thresholdType, unsigned int *temp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPerformanceState))(nvmlDevice_t device, nvmlPstates_t *pState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCurrentClocksThrottleReasons))(nvmlDevice_t device, unsigned long long *clocksThrottleReasons); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedClocksThrottleReasons))(nvmlDevice_t device, unsigned long long *supportedClocksThrottleReasons); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerState))(nvmlDevice_t device, nvmlPstates_t *pState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementLimit))(nvmlDevice_t device, unsigned int *limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementLimitConstraints))(nvmlDevice_t device, unsigned int *minLimit, unsigned int *maxLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerManagementDefaultLimit))(nvmlDevice_t device, unsigned int *defaultLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPowerUsage))(nvmlDevice_t device, unsigned int *power); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTotalEnergyConsumption))(nvmlDevice_t device, unsigned long long *energy); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEnforcedPowerLimit))(nvmlDevice_t device, unsigned int *limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuOperationMode))(nvmlDevice_t device, nvmlGpuOperationMode_t *current, nvmlGpuOperationMode_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryInfo))(nvmlDevice_t device, nvmlMemory_t *memory); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeMode))(nvmlDevice_t device, nvmlComputeMode_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCudaComputeCapability))(nvmlDevice_t device, int *major, int *minor); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEccMode))(nvmlDevice_t device, nvmlEnableState_t *current, nvmlEnableState_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBoardId))(nvmlDevice_t device, unsigned int *boardId); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMultiGpuBoard))(nvmlDevice_t device, unsigned int *multiGpuBool); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetTotalEccErrors))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, unsigned long long *eccCounts); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDetailedEccErrors))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, nvmlEccErrorCounts_t *eccCounts); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMemoryErrorCounter))(nvmlDevice_t device, nvmlMemoryErrorType_t errorType, nvmlEccCounterType_t counterType, nvmlMemoryLocation_t locationType, unsigned long long *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetUtilizationRates))(nvmlDevice_t device, nvmlUtilization_t *utilization); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderUtilization))(nvmlDevice_t device, unsigned int *utilization, unsigned int *samplingPeriodUs); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderCapacity))(nvmlDevice_t device, nvmlEncoderType_t encoderQueryType, unsigned int *encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderStats))(nvmlDevice_t device, unsigned int *sessionCount, unsigned int *averageFps, unsigned int *averageLatency); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetEncoderSessions))(nvmlDevice_t device, unsigned int *sessionCount, nvmlEncoderSessionInfo_t *sessionInfos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDecoderUtilization))(nvmlDevice_t device, unsigned int *utilization, unsigned int *samplingPeriodUs); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFBCStats))(nvmlDevice_t device, nvmlFBCStats_t *fbcStats); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFBCSessions))(nvmlDevice_t device, unsigned int *sessionCount, nvmlFBCSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDriverModel))(nvmlDevice_t device, nvmlDriverModel_t *current, nvmlDriverModel_t *pending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVbiosVersion))(nvmlDevice_t device, char *version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBridgeChipInfo))(nvmlDevice_t device, nvmlBridgeChipHierarchy_t *bridgeHierarchy); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeRunningProcesses))(nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGraphicsRunningProcesses))(nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceOnSameBoard))(nvmlDevice_t device1, nvmlDevice_t device2, int *onSameBoard); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAPIRestriction))(nvmlDevice_t device, nvmlRestrictedAPI_t apiType, nvmlEnableState_t *isRestricted); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSamples))(nvmlDevice_t device, nvmlSamplingType_t type, unsigned long long lastSeenTimeStamp, nvmlValueType_t *sampleValType, unsigned int *sampleCount, nvmlSample_t *samples); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetBAR1MemoryInfo))(nvmlDevice_t device, nvmlBAR1Memory_t *bar1Memory); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetViolationStatus))(nvmlDevice_t device, nvmlPerfPolicyType_t perfPolicyType, nvmlViolationTime_t *violTime); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingMode))(nvmlDevice_t device, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingStats))(nvmlDevice_t device, unsigned int pid, nvmlAccountingStats_t *stats); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingPids))(nvmlDevice_t device, unsigned int *count, unsigned int *pids); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetAccountingBufferSize))(nvmlDevice_t device, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPages))(nvmlDevice_t device, nvmlPageRetirementCause_t cause, unsigned int *pageCount, unsigned long long *addresses); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPages_v2))(nvmlDevice_t device, nvmlPageRetirementCause_t cause, unsigned int *pageCount, unsigned long long *addresses, unsigned long long *timestamps); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRetiredPagesPendingStatus))(nvmlDevice_t device, nvmlEnableState_t *isPending); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetRemappedRows))(nvmlDevice_t device, unsigned int *corrRows, unsigned int *uncRows, unsigned int *isPending, unsigned int *failureOccurred); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetArchitecture))(nvmlDevice_t device, nvmlDeviceArchitecture_t *arch); +//typedef nvmlReturn_t (*FUNC_T(nvmlUnitSetLedState))(nvmlUnit_t unit, nvmlLedColor_t color); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetPersistenceMode))(nvmlDevice_t device, nvmlEnableState_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetComputeMode))(nvmlDevice_t device, nvmlComputeMode_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetEccMode))(nvmlDevice_t device, nvmlEnableState_t ecc); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearEccErrorCounts))(nvmlDevice_t device, nvmlEccCounterType_t counterType); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetDriverModel))(nvmlDevice_t device, nvmlDriverModel_t driverModel, unsigned int flags); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetGpuLockedClocks))(nvmlDevice_t device, unsigned int minGpuClockMHz, unsigned int maxGpuClockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetGpuLockedClocks))(nvmlDevice_t device); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetApplicationsClocks))(nvmlDevice_t device, unsigned int memClockMHz, unsigned int graphicsClockMHz); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetPowerManagementLimit))(nvmlDevice_t device, unsigned int limit); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetGpuOperationMode))(nvmlDevice_t device, nvmlGpuOperationMode_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAPIRestriction))(nvmlDevice_t device, nvmlRestrictedAPI_t apiType, nvmlEnableState_t isRestricted); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetAccountingMode))(nvmlDevice_t device, nvmlEnableState_t mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceClearAccountingPids))(nvmlDevice_t device); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkState))(nvmlDevice_t device, unsigned int link, nvmlEnableState_t *isActive); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkVersion))(nvmlDevice_t device, unsigned int link, unsigned int *version); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkCapability))(nvmlDevice_t device, unsigned int link, nvmlNvLinkCapability_t capability, unsigned int *capResult); +typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkRemotePciInfo))(nvmlDevice_t device, unsigned int link, nvmlPciInfo_t *pci); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkErrorCounter))(nvmlDevice_t device, unsigned int link, nvmlNvLinkErrorCounter_t counter, unsigned long long *counterValue); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetNvLinkErrorCounters))(nvmlDevice_t device, unsigned int link); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetNvLinkUtilizationControl))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlNvLinkUtilizationControl_t *control, unsigned int reset); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkUtilizationControl))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlNvLinkUtilizationControl_t *control); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter, unsigned long long *rxcounter, unsigned long long *txcounter); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceFreezeNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter, nvmlEnableState_t freeze); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceResetNvLinkUtilizationCounter))(nvmlDevice_t device, unsigned int link, unsigned int counter); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetCreate))(nvmlEventSet_t *set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceRegisterEvents))(nvmlDevice_t device, unsigned long long eventTypes, nvmlEventSet_t set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedEventTypes))(nvmlDevice_t device, unsigned long long *eventTypes); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetWait))(nvmlEventSet_t set, nvmlEventData_t * data, unsigned int timeoutms); +//typedef nvmlReturn_t (*FUNC_T(nvmlEventSetFree))(nvmlEventSet_t set); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceModifyDrainState))(nvmlPciInfo_t *pciInfo, nvmlEnableState_t newState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceQueryDrainState))(nvmlPciInfo_t *pciInfo, nvmlEnableState_t *currentState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceRemoveGpu))(nvmlPciInfo_t *pciInfo, nvmlDetachGpuState_t gpuState, nvmlPcieLinkState_t linkState); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceDiscoverGpus))(nvmlPciInfo_t *pciInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetFieldValues))(nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVirtualizationMode))(nvmlDevice_t device, nvmlGpuVirtualizationMode_t *pVirtualMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetHostVgpuMode))(nvmlDevice_t device, nvmlHostVgpuMode_t *pHostVgpuMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetVirtualizationMode))(nvmlDevice_t device, nvmlGpuVirtualizationMode_t virtualMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGridLicensableFeatures))(nvmlDevice_t device, nvmlGridLicensableFeatures_t *pGridLicensableFeatures); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetProcessUtilization))(nvmlDevice_t device, nvmlProcessUtilizationSample_t *utilization, unsigned int *processSamplesCount, unsigned long long lastSeenTimeStamp); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetSupportedVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetCreatableVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetClass))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeClass, unsigned int *size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetName))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeName, unsigned int *size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetDeviceID))(nvmlVgpuTypeId_t vgpuTypeId, unsigned long long *deviceID, unsigned long long *subsystemID); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetFramebufferSize))(nvmlVgpuTypeId_t vgpuTypeId, unsigned long long *fbSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetNumDisplayHeads))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *numDisplayHeads); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetResolution))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int displayIndex, unsigned int *xdim, unsigned int *ydim); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetLicense))(nvmlVgpuTypeId_t vgpuTypeId, char *vgpuTypeLicenseString, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetFrameRateLimit))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *frameRateLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetMaxInstances))(nvmlDevice_t device, nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuTypeGetMaxInstancesPerVm))(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCountPerVm); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetActiveVgpus))(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuInstance_t *vgpuInstances); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetVmID))(nvmlVgpuInstance_t vgpuInstance, char *vmId, unsigned int size, nvmlVgpuVmIdType_t *vmIdType); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetUUID))(nvmlVgpuInstance_t vgpuInstance, char *uuid, unsigned int size); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetVmDriverVersion))(nvmlVgpuInstance_t vgpuInstance, char* version, unsigned int length); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFbUsage))(nvmlVgpuInstance_t vgpuInstance, unsigned long long *fbUsage); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetLicenseStatus))(nvmlVgpuInstance_t vgpuInstance, unsigned int *licensed); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetType))(nvmlVgpuInstance_t vgpuInstance, nvmlVgpuTypeId_t *vgpuTypeId); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFrameRateLimit))(nvmlVgpuInstance_t vgpuInstance, unsigned int *frameRateLimit); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEccMode))(nvmlVgpuInstance_t vgpuInstance, nvmlEnableState_t *eccMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderCapacity))(nvmlVgpuInstance_t vgpuInstance, unsigned int *encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceSetEncoderCapacity))(nvmlVgpuInstance_t vgpuInstance, unsigned int encoderCapacity); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderStats))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, unsigned int *averageFps, unsigned int *averageLatency); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetEncoderSessions))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, nvmlEncoderSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFBCStats))(nvmlVgpuInstance_t vgpuInstance, nvmlFBCStats_t *fbcStats); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetFBCSessions))(nvmlVgpuInstance_t vgpuInstance, unsigned int *sessionCount, nvmlFBCSessionInfo_t *sessionInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetMetadata))(nvmlVgpuInstance_t vgpuInstance, nvmlVgpuMetadata_t *vgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuMetadata))(nvmlDevice_t device, nvmlVgpuPgpuMetadata_t *pgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetVgpuCompatibility))(nvmlVgpuMetadata_t *vgpuMetadata, nvmlVgpuPgpuMetadata_t *pgpuMetadata, nvmlVgpuPgpuCompatibility_t *compatibilityInfo); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetPgpuMetadataString))(nvmlDevice_t device, char *pgpuMetadata, unsigned int *bufferSize); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetVgpuVersion))(nvmlVgpuVersion_t *supported, nvmlVgpuVersion_t *current); +//typedef nvmlReturn_t (*FUNC_T(nvmlSetVgpuVersion))(nvmlVgpuVersion_t *vgpuVersion); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuUtilization))(nvmlDevice_t device, unsigned long long lastSeenTimeStamp, nvmlValueType_t *sampleValType, unsigned int *vgpuInstanceSamplesCount, nvmlVgpuInstanceUtilizationSample_t *utilizationSamples); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetVgpuProcessUtilization))(nvmlDevice_t device, unsigned long long lastSeenTimeStamp, unsigned int *vgpuProcessSamplesCount, nvmlVgpuProcessUtilizationSample_t *utilizationSamples); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingMode))(nvmlVgpuInstance_t vgpuInstance, nvmlEnableState_t *mode); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingPids))(nvmlVgpuInstance_t vgpuInstance, unsigned int *count, unsigned int *pids); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceGetAccountingStats))(nvmlVgpuInstance_t vgpuInstance, unsigned int pid, nvmlAccountingStats_t *stats); +//typedef nvmlReturn_t (*FUNC_T(nvmlVgpuInstanceClearAccountingPids))(nvmlVgpuInstance_t vgpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetBlacklistDeviceCount))(unsigned int *deviceCount); +//typedef nvmlReturn_t (*FUNC_T(nvmlGetBlacklistDeviceInfoByIndex))(unsigned int index, nvmlBlacklistDeviceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceSetMigMode))(nvmlDevice_t device, unsigned int mode, nvmlReturn_t *activationStatus); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMigMode))(nvmlDevice_t device, unsigned int *currentMode, unsigned int *pendingMode); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceProfileInfo))(nvmlDevice_t device, unsigned int profile, nvmlGpuInstanceProfileInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstancePossiblePlacements))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstancePlacement_t *placements, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceRemainingCapacity))(nvmlDevice_t device, unsigned int profileId, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceCreateGpuInstance))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstance_t *gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceDestroy))(nvmlGpuInstance_t gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstances))(nvmlDevice_t device, unsigned int profileId, nvmlGpuInstance_t *gpuInstances, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceById))(nvmlDevice_t device, unsigned int id, nvmlGpuInstance_t *gpuInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetInfo))(nvmlGpuInstance_t gpuInstance, nvmlGpuInstanceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceProfileInfo))(nvmlGpuInstance_t gpuInstance, unsigned int profile, unsigned int engProfile, nvmlComputeInstanceProfileInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceRemainingCapacity))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceCreateComputeInstance))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, nvmlComputeInstance_t *computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlComputeInstanceDestroy))(nvmlComputeInstance_t computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstances))(nvmlGpuInstance_t gpuInstance, unsigned int profileId, nvmlComputeInstance_t *computeInstances, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlGpuInstanceGetComputeInstanceById))(nvmlGpuInstance_t gpuInstance, unsigned int id, nvmlComputeInstance_t *computeInstance); +//typedef nvmlReturn_t (*FUNC_T(nvmlComputeInstanceGetInfo))(nvmlComputeInstance_t computeInstance, nvmlComputeInstanceInfo_t *info); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceIsMigDeviceHandle))(nvmlDevice_t device, unsigned int *isMigDevice); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetGpuInstanceId))(nvmlDevice_t device, unsigned int *id); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetComputeInstanceId))(nvmlDevice_t device, unsigned int *id); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMaxMigDeviceCount))(nvmlDevice_t device, unsigned int *count); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetMigDeviceHandleByIndex))(nvmlDevice_t device, unsigned int index, nvmlDevice_t *migDevice); +//typedef nvmlReturn_t (*FUNC_T(nvmlDeviceGetDeviceHandleFromMigDeviceHandle))(nvmlDevice_t migDevice, nvmlDevice_t *device); + + + +struct NVMLFunctionTable +{ + FUNC_P(nvmlInit); + //FUNC_P(nvmlInitWithFlags); + FUNC_P(nvmlShutdown); + //FUNC_P(nvmlErrorString); + //FUNC_P(nvmlSystemGetDriverVersion); + //FUNC_P(nvmlSystemGetNVMLVersion); + //FUNC_P(nvmlSystemGetCudaDriverVersion); + //FUNC_P(nvmlSystemGetCudaDriverVersion_v2); + //FUNC_P(nvmlSystemGetProcessName); + //FUNC_P(nvmlUnitGetCount); + //FUNC_P(nvmlUnitGetHandleByIndex); + //FUNC_P(nvmlUnitGetUnitInfo); + //FUNC_P(nvmlUnitGetLedState); + //FUNC_P(nvmlUnitGetPsuInfo); + //FUNC_P(nvmlUnitGetTemperature); + //FUNC_P(nvmlUnitGetFanSpeedInfo); + //FUNC_P(nvmlUnitGetDevices); + //FUNC_P(nvmlSystemGetHicVersion); + //FUNC_P(nvmlDeviceGetCount); + //FUNC_P(nvmlDeviceGetAttributes); + //FUNC_P(nvmlDeviceGetHandleByIndex); + //FUNC_P(nvmlDeviceGetHandleBySerial); + //FUNC_P(nvmlDeviceGetHandleByUUID); + FUNC_P(nvmlDeviceGetHandleByPciBusId); + //FUNC_P(nvmlDeviceGetName); + //FUNC_P(nvmlDeviceGetBrand); + //FUNC_P(nvmlDeviceGetIndex); + //FUNC_P(nvmlDeviceGetSerial); + //FUNC_P(nvmlDeviceGetMemoryAffinity); + //FUNC_P(nvmlDeviceGetCpuAffinityWithinScope); + //FUNC_P(nvmlDeviceGetCpuAffinity); + //FUNC_P(nvmlDeviceSetCpuAffinity); + //FUNC_P(nvmlDeviceClearCpuAffinity); + //FUNC_P(nvmlDeviceGetTopologyCommonAncestor); + //FUNC_P(nvmlDeviceGetTopologyNearestGpus); + //FUNC_P(nvmlSystemGetTopologyGpuSet); + //FUNC_P(nvmlDeviceGetP2PStatus); + //FUNC_P(nvmlDeviceGetUUID); + //FUNC_P(nvmlVgpuInstanceGetMdevUUID); + //FUNC_P(nvmlDeviceGetMinorNumber); + //FUNC_P(nvmlDeviceGetBoardPartNumber); + //FUNC_P(nvmlDeviceGetInforomVersion); + //FUNC_P(nvmlDeviceGetInforomImageVersion); + //FUNC_P(nvmlDeviceGetInforomConfigurationChecksum); + //FUNC_P(nvmlDeviceValidateInforom); + //FUNC_P(nvmlDeviceGetDisplayMode); + //FUNC_P(nvmlDeviceGetDisplayActive); + //FUNC_P(nvmlDeviceGetPersistenceMode); + //FUNC_P(nvmlDeviceGetPciInfo); + //FUNC_P(nvmlDeviceGetMaxPcieLinkGeneration); + //FUNC_P(nvmlDeviceGetMaxPcieLinkWidth); + //FUNC_P(nvmlDeviceGetCurrPcieLinkGeneration); + //FUNC_P(nvmlDeviceGetCurrPcieLinkWidth); + //FUNC_P(nvmlDeviceGetPcieThroughput); + //FUNC_P(nvmlDeviceGetPcieReplayCounter); + //FUNC_P(nvmlDeviceGetClockInfo); + //FUNC_P(nvmlDeviceGetMaxClockInfo); + //FUNC_P(nvmlDeviceGetApplicationsClock); + //FUNC_P(nvmlDeviceGetDefaultApplicationsClock); + //FUNC_P(nvmlDeviceResetApplicationsClocks); + //FUNC_P(nvmlDeviceGetClock); + //FUNC_P(nvmlDeviceGetMaxCustomerBoostClock); + //FUNC_P(nvmlDeviceGetSupportedMemoryClocks); + //FUNC_P(nvmlDeviceGetSupportedGraphicsClocks); + //FUNC_P(nvmlDeviceGetAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceSetAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceSetDefaultAutoBoostedClocksEnabled); + //FUNC_P(nvmlDeviceGetFanSpeed); + //FUNC_P(nvmlDeviceGetFanSpeed_v2); + //FUNC_P(nvmlDeviceGetTemperature); + //FUNC_P(nvmlDeviceGetTemperatureThreshold); + //FUNC_P(nvmlDeviceGetPerformanceState); + //FUNC_P(nvmlDeviceGetCurrentClocksThrottleReasons); + //FUNC_P(nvmlDeviceGetSupportedClocksThrottleReasons); + //FUNC_P(nvmlDeviceGetPowerState); + //FUNC_P(nvmlDeviceGetPowerManagementMode); + //FUNC_P(nvmlDeviceGetPowerManagementLimit); + //FUNC_P(nvmlDeviceGetPowerManagementLimitConstraints); + //FUNC_P(nvmlDeviceGetPowerManagementDefaultLimit); + //FUNC_P(nvmlDeviceGetPowerUsage); + //FUNC_P(nvmlDeviceGetTotalEnergyConsumption); + //FUNC_P(nvmlDeviceGetEnforcedPowerLimit); + //FUNC_P(nvmlDeviceGetGpuOperationMode); + //FUNC_P(nvmlDeviceGetMemoryInfo); + //FUNC_P(nvmlDeviceGetComputeMode); + //FUNC_P(nvmlDeviceGetCudaComputeCapability); + //FUNC_P(nvmlDeviceGetEccMode); + //FUNC_P(nvmlDeviceGetBoardId); + //FUNC_P(nvmlDeviceGetMultiGpuBoard); + //FUNC_P(nvmlDeviceGetTotalEccErrors); + //FUNC_P(nvmlDeviceGetDetailedEccErrors); + //FUNC_P(nvmlDeviceGetMemoryErrorCounter); + //FUNC_P(nvmlDeviceGetUtilizationRates); + //FUNC_P(nvmlDeviceGetEncoderUtilization); + //FUNC_P(nvmlDeviceGetEncoderCapacity); + //FUNC_P(nvmlDeviceGetEncoderStats); + //FUNC_P(nvmlDeviceGetEncoderSessions); + //FUNC_P(nvmlDeviceGetDecoderUtilization); + //FUNC_P(nvmlDeviceGetFBCStats); + //FUNC_P(nvmlDeviceGetFBCSessions); + //FUNC_P(nvmlDeviceGetDriverModel); + //FUNC_P(nvmlDeviceGetVbiosVersion); + //FUNC_P(nvmlDeviceGetBridgeChipInfo); + //FUNC_P(nvmlDeviceGetComputeRunningProcesses); + //FUNC_P(nvmlDeviceGetGraphicsRunningProcesses); + //FUNC_P(nvmlDeviceOnSameBoard); + //FUNC_P(nvmlDeviceGetAPIRestriction); + //FUNC_P(nvmlDeviceGetSamples); + //FUNC_P(nvmlDeviceGetBAR1MemoryInfo); + //FUNC_P(nvmlDeviceGetViolationStatus); + //FUNC_P(nvmlDeviceGetAccountingMode); + //FUNC_P(nvmlDeviceGetAccountingStats); + //FUNC_P(nvmlDeviceGetAccountingPids); + //FUNC_P(nvmlDeviceGetAccountingBufferSize); + //FUNC_P(nvmlDeviceGetRetiredPages); + //FUNC_P(nvmlDeviceGetRetiredPages_v2); + //FUNC_P(nvmlDeviceGetRetiredPagesPendingStatus); + //FUNC_P(nvmlDeviceGetRemappedRows); + //FUNC_P(nvmlDeviceGetArchitecture); + //FUNC_P(nvmlUnitSetLedState); + //FUNC_P(nvmlDeviceSetPersistenceMode); + //FUNC_P(nvmlDeviceSetComputeMode); + //FUNC_P(nvmlDeviceSetEccMode); + //FUNC_P(nvmlDeviceClearEccErrorCounts); + //FUNC_P(nvmlDeviceSetDriverModel); + //FUNC_P(nvmlDeviceSetGpuLockedClocks); + //FUNC_P(nvmlDeviceResetGpuLockedClocks); + //FUNC_P(nvmlDeviceSetApplicationsClocks); + //FUNC_P(nvmlDeviceSetPowerManagementLimit); + //FUNC_P(nvmlDeviceSetGpuOperationMode); + //FUNC_P(nvmlDeviceSetAPIRestriction); + //FUNC_P(nvmlDeviceSetAccountingMode); + //FUNC_P(nvmlDeviceClearAccountingPids); + FUNC_P(nvmlDeviceGetNvLinkState); + //FUNC_P(nvmlDeviceGetNvLinkVersion); + FUNC_P(nvmlDeviceGetNvLinkCapability); + FUNC_P(nvmlDeviceGetNvLinkRemotePciInfo); + //FUNC_P(nvmlDeviceGetNvLinkErrorCounter); + //FUNC_P(nvmlDeviceResetNvLinkErrorCounters); + //FUNC_P(nvmlDeviceSetNvLinkUtilizationControl); + //FUNC_P(nvmlDeviceGetNvLinkUtilizationControl); + //FUNC_P(nvmlDeviceGetNvLinkUtilizationCounter); + //FUNC_P(nvmlDeviceFreezeNvLinkUtilizationCounter); + //FUNC_P(nvmlDeviceResetNvLinkUtilizationCounter); + //FUNC_P(nvmlEventSetCreate); + //FUNC_P(nvmlDeviceRegisterEvents); + //FUNC_P(nvmlDeviceGetSupportedEventTypes); + //FUNC_P(nvmlEventSetWait); + //FUNC_P(nvmlEventSetFree); + //FUNC_P(nvmlDeviceModifyDrainState); + //FUNC_P(nvmlDeviceQueryDrainState); + //FUNC_P(nvmlDeviceRemoveGpu); + //FUNC_P(nvmlDeviceDiscoverGpus); + //FUNC_P(nvmlDeviceGetFieldValues); + //FUNC_P(nvmlDeviceGetVirtualizationMode); + //FUNC_P(nvmlDeviceGetHostVgpuMode); + //FUNC_P(nvmlDeviceSetVirtualizationMode); + //FUNC_P(nvmlDeviceGetGridLicensableFeatures); + //FUNC_P(nvmlDeviceGetProcessUtilization); + //FUNC_P(nvmlDeviceGetSupportedVgpus); + //FUNC_P(nvmlDeviceGetCreatableVgpus); + //FUNC_P(nvmlVgpuTypeGetClass); + //FUNC_P(nvmlVgpuTypeGetName); + //FUNC_P(nvmlVgpuTypeGetDeviceID); + //FUNC_P(nvmlVgpuTypeGetFramebufferSize); + //FUNC_P(nvmlVgpuTypeGetNumDisplayHeads); + //FUNC_P(nvmlVgpuTypeGetResolution); + //FUNC_P(nvmlVgpuTypeGetLicense); + //FUNC_P(nvmlVgpuTypeGetFrameRateLimit); + //FUNC_P(nvmlVgpuTypeGetMaxInstances); + //FUNC_P(nvmlVgpuTypeGetMaxInstancesPerVm); + //FUNC_P(nvmlDeviceGetActiveVgpus); + //FUNC_P(nvmlVgpuInstanceGetVmID); + //FUNC_P(nvmlVgpuInstanceGetUUID); + //FUNC_P(nvmlVgpuInstanceGetVmDriverVersion); + //FUNC_P(nvmlVgpuInstanceGetFbUsage); + //FUNC_P(nvmlVgpuInstanceGetLicenseStatus); + //FUNC_P(nvmlVgpuInstanceGetType); + //FUNC_P(nvmlVgpuInstanceGetFrameRateLimit); + //FUNC_P(nvmlVgpuInstanceGetEccMode); + //FUNC_P(nvmlVgpuInstanceGetEncoderCapacity); + //FUNC_P(nvmlVgpuInstanceSetEncoderCapacity); + //FUNC_P(nvmlVgpuInstanceGetEncoderStats); + //FUNC_P(nvmlVgpuInstanceGetEncoderSessions); + //FUNC_P(nvmlVgpuInstanceGetFBCStats); + //FUNC_P(nvmlVgpuInstanceGetFBCSessions); + //FUNC_P(nvmlVgpuInstanceGetMetadata); + //FUNC_P(nvmlDeviceGetVgpuMetadata); + //FUNC_P(nvmlGetVgpuCompatibility); + //FUNC_P(nvmlDeviceGetPgpuMetadataString); + //FUNC_P(nvmlGetVgpuVersion); + //FUNC_P(nvmlSetVgpuVersion); + //FUNC_P(nvmlDeviceGetVgpuUtilization); + //FUNC_P(nvmlDeviceGetVgpuProcessUtilization); + //FUNC_P(nvmlVgpuInstanceGetAccountingMode); + //FUNC_P(nvmlVgpuInstanceGetAccountingPids); + //FUNC_P(nvmlVgpuInstanceGetAccountingStats); + //FUNC_P(nvmlVgpuInstanceClearAccountingPids); + //FUNC_P(nvmlGetBlacklistDeviceCount); + //FUNC_P(nvmlGetBlacklistDeviceInfoByIndex); + //FUNC_P(nvmlDeviceSetMigMode); + //FUNC_P(nvmlDeviceGetMigMode); + //FUNC_P(nvmlDeviceGetGpuInstanceProfileInfo); + //FUNC_P(nvmlDeviceGetGpuInstancePossiblePlacements); + //FUNC_P(nvmlDeviceGetGpuInstanceRemainingCapacity); + //FUNC_P(nvmlDeviceCreateGpuInstance); + //FUNC_P(nvmlGpuInstanceDestroy); + //FUNC_P(nvmlDeviceGetGpuInstances); + //FUNC_P(nvmlDeviceGetGpuInstanceById); + //FUNC_P(nvmlGpuInstanceGetInfo); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceProfileInfo); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceRemainingCapacity); + //FUNC_P(nvmlGpuInstanceCreateComputeInstance); + //FUNC_P(nvmlComputeInstanceDestroy); + //FUNC_P(nvmlGpuInstanceGetComputeInstances); + //FUNC_P(nvmlGpuInstanceGetComputeInstanceById); + //FUNC_P(nvmlComputeInstanceGetInfo); + //FUNC_P(nvmlDeviceIsMigDeviceHandle); + //FUNC_P(nvmlDeviceGetGpuInstanceId); + //FUNC_P(nvmlDeviceGetComputeInstanceId); + //FUNC_P(nvmlDeviceGetMaxMigDeviceCount); + //FUNC_P(nvmlDeviceGetMigDeviceHandleByIndex); + //FUNC_P(nvmlDeviceGetDeviceHandleFromMigDeviceHandle); +}; + +#undef FUNC_T_V +#undef FUNC_T + +#undef FUNC_P_V +#undef FUNC_P + +class NVMLImpl +{ +public: + NVMLImpl(); + //~NVMLImpl(); + + bool initFunctionTable(); + +public: + NVMLFunctionTable m_api; + +private: + void* m_handle; // nullptr when the library could not be found +}; + + +#endif // NVML_IMPL_H + diff --git a/apps/bench_shared_offscreen/inc/Options.h b/apps/bench_shared_offscreen/inc/Options.h new file mode 100644 index 00000000..4541f95f --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Options.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include + +class Options +{ +public: + Options(); + //~Options(); + + bool parseCommandLine(int argc, char *argv[]); + + int getWidth() const; + int getHeight() const; + int getMode() const; + bool getOptimize() const; + std::string getSystem() const; + std::string getScene() const; + +private: + void printUsage(const std::string& argv); + +private: + int m_width; + int m_height; + int m_mode; + bool m_optimize; + bool m_peerPCIE; + std::string m_filenameSystem; + std::string m_filenameScene; +}; + +#endif // OPTIONS_H diff --git a/apps/bench_shared_offscreen/inc/Parser.h b/apps/bench_shared_offscreen/inc/Parser.h new file mode 100644 index 00000000..89dda384 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Parser.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef PARSER_H +#define PARSER_H + +#include + +enum ParserTokenType +{ + PTT_UNKNOWN, // Unknown, normally indicates an error. + PTT_ID, // Keywords and identifiers (not a number). + PTT_VAL, // Immediate floating point value. + PTT_STRING, // Filenames and any other identifier in quotation marks. + PTT_EOL, // End of line. + PTT_EOF // End of file. +}; + + +// System and scene file parsing information. +class Parser +{ +public: + Parser(); + //~Parser(); + + bool load(const std::string& filename); + + ParserTokenType getNextToken(std::string& token); + + size_t getSize() const; + std::string::size_type getIndex() const; + unsigned int getLine() const; + +private: + std::string m_source; // System or scene description file contents. + std::string::size_type m_index; // Parser's current character index into m_source. + unsigned int m_line; // Current source code line, one-based for error messages. +}; + +#endif // PARSER_H diff --git a/apps/bench_shared_offscreen/inc/Picture.h b/apps/bench_shared_offscreen/inc/Picture.h new file mode 100644 index 00000000..88336cfa --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Picture.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// Code in these classes is based on the ILTexLoader.h/.cpp routines inside the NVIDIA nvpro-pipeline ILTexLoader plugin: +// https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp + +#pragma once + +#ifndef PICTURE_H +#define PICTURE_H + +#include + +#include +#include + +// Bits for the image handling and texture creation flags. +#define IMAGE_FLAG_1D 0x00000001 +#define IMAGE_FLAG_2D 0x00000002 +#define IMAGE_FLAG_3D 0x00000004 +#define IMAGE_FLAG_CUBE 0x00000008 +// Modifier bits on the above types. +// Layered image (not applicable to 3D) +#define IMAGE_FLAG_LAYER 0x00000010 +// Mipmapped image (ignored when there are no mipmaps provided or for environment map). +#define IMAGE_FLAG_MIPMAP 0x00000020 +// Special case for a 2D spherical environment map. +#define IMAGE_FLAG_ENV 0x00000040 + +struct Image +{ + Image(unsigned int width, unsigned int height, unsigned int depth, int format, int type); + ~Image(); + + unsigned int m_width; + unsigned int m_height; + unsigned int m_depth; + + int m_format; // DevIL image format. + int m_type; // DevIL image component type. + + // Derived values. + unsigned int m_bpp; // bytes per pixel + unsigned int m_bpl; // bytes per scanline + unsigned int m_bps; // bytes per slice (plane) + unsigned int m_nob; // number of bytes (complete image) + + unsigned char* m_pixels; // The pixel data of one image. +}; + + +class Picture +{ +public: + Picture(); + ~Picture(); + + bool load(const std::string& filename, const unsigned int flags); + void clear(); + + // Add an empty new vector of images. Each vector can hold one mipmap chain. Returns the new image index. + unsigned int addImages(); + + // Add a mipmap chain as new vector of images. Returns the new image index. + // pixels and extents are for the LOD 0. mipmaps can be empty. + unsigned int addImages(const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type, + const std::vector& mipmaps, const unsigned int flags); + + // Append a new image LOD to the existing vector of images building a mipmap chain. (No mipmap consistency check here.) + unsigned int addLevel(const unsigned int index, + const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type); + + unsigned int getFlags() const; + unsigned int getNumberOfImages() const; + unsigned int getNumberOfLevels(unsigned int indexImage) const; + const Image* getImageLevel(unsigned int indexImage, unsigned int indexLevel) const; + bool isCubemap() const; + + // This is needed when generating cubemaps without loading them via DevIL. + void setIsCubemap(const bool isCube); + + // DEBUG Function to generate all 14 texture targets with RGBA8 images. + void generateRGBA8(unsigned int width, unsigned int height, unsigned int depth, const unsigned int flags); + +private: + void mirrorX(unsigned int index); + void mirrorY(unsigned int index); + + void clearImages(); // Delete all pixel data, all Image pointers and clear the m_images vector. + +private: + unsigned int m_flags; // The image flags which which this Picture has been loaded. + bool m_isCube; // Track if the picture is a cube map. + std::vector< std::vector > m_images; +}; + +#endif // PICTURE_H diff --git a/apps/bench_shared_offscreen/inc/Rasterizer.h b/apps/bench_shared_offscreen/inc/Rasterizer.h new file mode 100644 index 00000000..3bbe8e9f --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Rasterizer.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RASTERIZER_H +#define RASTERIZER_H + +#include +#if defined( _WIN32 ) +#include +#endif + +#include "inc/Timer.h" +#include "inc/TonemapperGUI.h" + +#include + +typedef struct +{ + float u; // u-coordinate in range [0.0, 1.0] + float c[3]; // color, components in range [0.0, 1.0] +} ColorRampElement; + + +class Rasterizer +{ +public: + Rasterizer(const int w, const int h, const int interop); + ~Rasterizer(); + + void reshape(const int w, const int h); + void display(); + + const int getNumDevices() const; + + const unsigned char* getUUID(const unsigned int index) const; + const unsigned char* getLUID() const; + int getNodeMask() const; + + unsigned int getTextureObject() const; + unsigned int getPixelBufferObject() const; + + void setResolution(const int w, const int h); + void setTonemapper(const TonemapperGUI& tm); + +private: + void checkInfoLog(const char *msg, GLuint object); + void initGLSL(); + void updateProjectionMatrix(); + void updateVertexAttributes(); + +private: + int m_width; + int m_height; + int m_interop; + + int m_widthResolution; + int m_heightResolution; + + GLint m_numDevices; // Number of OpenGL devices. Normally 1, unless multicast is enabled. + GLubyte m_deviceUUID[24][GL_UUID_SIZE_EXT]; // Max. 24 devices expected. 16 bytes identifier. + //GLubyte m_driverUUID[GL_UUID_SIZE_EXT]; // 16 bytes identifier, unused. + + GLubyte m_deviceLUID[GL_LUID_SIZE_EXT]; // 8 bytes identifier. + GLint m_nodeMask; // Node mask used together with the LUID to identify OpenGL device uniquely. + + GLuint m_hdrTexture; + GLuint m_pbo; + + GLuint m_colorRampTexture; + + GLuint m_glslProgram; + + GLuint m_vboAttributes; + GLuint m_vboIndices; + + GLint m_locAttrPosition; + GLint m_locAttrTexCoord; + GLint m_locProjection; + + GLint m_locSamplerHDR; + GLint m_locSamplerColorRamp; + + // Rasterizer side of the TonemapperGUI data + GLint m_locInvGamma; + GLint m_locColorBalance; + GLint m_locInvWhitePoint; + GLint m_locBurnHighlights; + GLint m_locCrushBlacks; + GLint m_locSaturation; + + Timer m_timer; +}; + +#endif // RASTERIZER_H diff --git a/apps/bench_shared_offscreen/inc/Raytracer.h b/apps/bench_shared_offscreen/inc/Raytracer.h new file mode 100644 index 00000000..5502326c --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Raytracer.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RAYTRACER_H +#define RAYTRACER_H + +#include "inc/Device.h" +#include "inc/MaterialGUI.h" +#include "inc/Picture.h" +#include "inc/SceneGraph.h" +#include "inc/Texture.h" +#include "inc/NVMLImpl.h" + +#include "shaders/system_data.h" + +#include +#include +#include + +// Bitfield encoding for m_peerToPeer: +#define P2P_PCI 1 +#define P2P_TEX 2 +#define P2P_GAS 4 +#define P2P_ENV 8 + +class Raytracer +{ +public: + Raytracer(const int maskDevices, + const int miss, + //const int interop, + //const unsigned int tex, + //const unsigned int pbo, + const size_t sizeArena, + const int p2p); + ~Raytracer(); + + //int matchUUID(const char* uuid); + //int matchLUID(const char* luid, const unsigned int nodeMask); + + bool enablePeerAccess(); // Calculates peer-to-peer access bit matrix in m_peerConnections and sets the m_islands. + void disablePeerAccess(); // Clear the peer-to-peer islands. Afterwards each device is its own island. + + void synchronize(); // Needed for the benchmark to wait for all asynchronous rendering to have finished. + + void initTextures(const std::map& mapOfPictures); + void initCameras(const std::vector& cameras); + void initLights(const std::vector& lights); + void initMaterials(const std::vector& materialsGUI); + void initScene(std::shared_ptr root, const unsigned int numGeometries); + void initState(const DeviceState& state); + + // Update functions should be replaced with NOP functions in a derived batch renderer because the device functions are fully asynchronous then. + void updateCamera(const int idCamera, const CameraDefinition& camera); + void updateLight(const int idLight, const LightDefinition& light); + void updateMaterial(const int idMaterial, const MaterialGUI& src); + void updateState(const DeviceState& state); + + // Abstract functions must be implemented by each derived Raytracer per strategy individually. + unsigned int render(); + //void updateDisplayTexture(); + const void* getOutputBufferHost(); + +private: + void selectDevices(); + int getDeviceHome(const std::vector& island) const; + void traverseNode(std::shared_ptr node, InstanceData instanceData, float matrix[12]); + bool activeNVLINK(const int home, const int peer) const; + int findActiveDevice(const unsigned int domain, const unsigned int bus, const unsigned int device) const; + +public: + // Constructor arguments + unsigned int m_maskDevices; // The bitmask with the devices the user selected. + int m_miss; // The miss program selection (0 = black, 1 = white, 2 = env map) + int m_interop; // Which CUDA-OpenGL interop to use. + unsigned int m_tex; // The OpenGL texture object used for display. + unsigned int m_pbo; // The pixel buffer object when using INTEROP_MODE_PBO. + size_t m_sizeArena; // The default Arena allocation size in mega-bytes, just routed through to Device class. + int m_peerToPeer; // Bitfield encoding for which resources CUDA P2P sharing is allowed: + // Bit 0: Allow sharing via PCI-E, ignores the NVLINK topology, just checks cuDeviceCanAccessPeer(). + // Bit 1: Share material textures (not the HDR environment). + // Bit 2: Share GAS and vertex attribute data. + // Bit 3: Share (optional) HDR environment and its CDF data. + + bool m_isValid; + + int m_numDevicesVisible; // The number of visible CUDA devices. (What you can control via the CUDA_VISIBLE_DEVICES environment variable.) + //int m_indexDeviceOGL; // The first device which matches with the OpenGL LUID and node mask. -1 when there was no match. + unsigned int m_maskDevicesActive; // The bitmask marking the actually enabled devices. + std::vector m_devicesActive; + + unsigned int m_iterationIndex; // Tracks which frame is currently raytraced. + unsigned int m_samplesPerPixel; // This is samplesSqrt squared. Rendering end-condition is: m_iterationIndex == m_samplesPerPixel. + + std::vector m_peerConnections; // Bitfield indicating peer-to-peer access between devices. Indexing is m_peerConnections[home] & (1 << peer) + std::vector< std::vector > m_islands; // Vector with vector of device indices (not ordinals) building a peer-to-peer island. + + std::vector m_geometryData; + + NVMLImpl m_nvml; +}; + +#endif // RAYTRACER_H diff --git a/apps/bench_shared_offscreen/inc/SceneGraph.h b/apps/bench_shared_offscreen/inc/SceneGraph.h new file mode 100644 index 00000000..486b9c2d --- /dev/null +++ b/apps/bench_shared_offscreen/inc/SceneGraph.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SCENEGRAPH_H +#define SCENEGRAPH_H + +// For the vector types. +#include + +#include "shaders/vertex_attributes.h" +#include "shaders/vector_math.h" + +#include +#include + +namespace sg +{ + + enum NodeType + { + NT_GROUP, + NT_INSTANCE, + NT_TRIANGLES + }; + + class Node + { + public: + Node(const unsigned int id); + //~Node(); + + virtual sg::NodeType getType() const = 0; + + unsigned int getId() const + { + return m_id; + } + + private: + unsigned int m_id; + }; + + + class Triangles : public Node + { + public: + Triangles(const unsigned int id); + //~Triangles(); + + sg::NodeType getType() const; + + void createBox(); + void createPlane(const unsigned int tessU, const unsigned int tessV, const unsigned int upAxis); + void createSphere(const unsigned int tessU, const unsigned int tessV, const float radius, const float maxTheta); + void createTorus(const unsigned int tessU, const unsigned int tessV, const float innerRadius, const float outerRadius); + void createParallelogram(const float3& position, const float3& vecU, const float3& vecV, const float3& normal); + + void setAttributes(const std::vector& attributes); + const std::vector& getAttributes() const; + + void setIndices(const std::vector& indices); + const std::vector& getIndices() const; + + private: + std::vector m_attributes; + std::vector m_indices; // If m_indices.size() == 0, m_attributes are independent primitives. // Not actually supported in this renderer implementation. + }; + + class Instance : public Node + { + public: + Instance(const unsigned int id); + //~Instance(); + + sg::NodeType getType() const; + + void setTransform(const float m[12]); + const float* getTransform() const; + + void setChild(std::shared_ptr node); + std::shared_ptr getChild(); + + void setMaterial(const int index); + int getMaterial() const; + + void setLight(const int index); + int getLight() const; + + private: + int m_material; + int m_light; + float m_matrix[12]; + std::shared_ptr m_child; // An Instance can either hold a Group or Triangles as child. + }; + + class Group : public Node + { + public: + Group(const unsigned int id); + //~Group(); + + sg::NodeType getType() const; + + void addChild(std::shared_ptr instance); // Groups can only hold Instances. + + size_t getNumChildren() const; + std::shared_ptr getChild(size_t index); + + private: + std::vector< std::shared_ptr > m_children; + }; + +} // namespace sg + +#endif // SCENEGRAPH_H diff --git a/apps/bench_shared_offscreen/inc/Texture.h b/apps/bench_shared_offscreen/inc/Texture.h new file mode 100644 index 00000000..608b7f96 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Texture.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TEXTURE_H +#define TEXTURE_H + +// Always include this before any OptiX headers. +#include +#include + +#include "inc/Picture.h" + +#include +#include + +// Bitfield encoding of the texture channels. +// These are used to remap user format and user data to the internal format. +// Each four bits hold the channel index of red, green, blue, alpha, and luminance. +// (encoding >> ENC_*_SHIFT) & ENC_MASK gives the channel index if the result is less than 4. +// That encoding allows to automatically swap red and blue, map luminance to RGB (not the other way round though!), +// fill in alpha with input data or force it to one if required. +// 49 remapper functions take care to convert the data types including fixed-point adjustments. + +#define ENC_MASK 0xF + +#define ENC_RED_SHIFT 0 +#define ENC_RED_0 ( 0u << ENC_RED_SHIFT) +#define ENC_RED_1 ( 1u << ENC_RED_SHIFT) +#define ENC_RED_2 ( 2u << ENC_RED_SHIFT) +#define ENC_RED_3 ( 3u << ENC_RED_SHIFT) +#define ENC_RED_NONE (15u << ENC_RED_SHIFT) + +#define ENC_GREEN_SHIFT 4 +#define ENC_GREEN_0 ( 0u << ENC_GREEN_SHIFT) +#define ENC_GREEN_1 ( 1u << ENC_GREEN_SHIFT) +#define ENC_GREEN_2 ( 2u << ENC_GREEN_SHIFT) +#define ENC_GREEN_3 ( 3u << ENC_GREEN_SHIFT) +#define ENC_GREEN_NONE (15u << ENC_GREEN_SHIFT) + +#define ENC_BLUE_SHIFT 8 +#define ENC_BLUE_0 ( 0u << ENC_BLUE_SHIFT) +#define ENC_BLUE_1 ( 1u << ENC_BLUE_SHIFT) +#define ENC_BLUE_2 ( 2u << ENC_BLUE_SHIFT) +#define ENC_BLUE_3 ( 3u << ENC_BLUE_SHIFT) +#define ENC_BLUE_NONE (15u << ENC_BLUE_SHIFT) + +#define ENC_ALPHA_SHIFT 12 +#define ENC_ALPHA_0 ( 0u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_1 ( 1u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_2 ( 2u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_3 ( 3u << ENC_ALPHA_SHIFT) +#define ENC_ALPHA_NONE (15u << ENC_ALPHA_SHIFT) + +#define ENC_LUM_SHIFT 16 +#define ENC_LUM_0 ( 0u << ENC_LUM_SHIFT) +#define ENC_LUM_1 ( 1u << ENC_LUM_SHIFT) +#define ENC_LUM_2 ( 2u << ENC_LUM_SHIFT) +#define ENC_LUM_3 ( 3u << ENC_LUM_SHIFT) +#define ENC_LUM_NONE (15u << ENC_LUM_SHIFT) + +#define ENC_CHANNELS_SHIFT 20 +#define ENC_CHANNELS_1 (1u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_2 (2u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_3 (3u << ENC_CHANNELS_SHIFT) +#define ENC_CHANNELS_4 (4u << ENC_CHANNELS_SHIFT) + +#define ENC_TYPE_SHIFT 24 +// These are indices into the remapper table. +#define ENC_TYPE_CHAR ( 0u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_CHAR ( 1u << ENC_TYPE_SHIFT) +#define ENC_TYPE_SHORT ( 2u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_SHORT ( 3u << ENC_TYPE_SHIFT) +#define ENC_TYPE_INT ( 4u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNSIGNED_INT ( 5u << ENC_TYPE_SHIFT) +#define ENC_TYPE_FLOAT ( 6u << ENC_TYPE_SHIFT) +#define ENC_TYPE_UNDEFINED (15u << ENC_TYPE_SHIFT) + +// Flags to indicate that special handling is required. +#define ENC_MISC_SHIFT 28 +#define ENC_FIXED_POINT (1u << ENC_MISC_SHIFT) +#define ENC_ALPHA_ONE (2u << ENC_MISC_SHIFT) +// Highest bit set means invalid encoding. +#define ENC_INVALID (8u << ENC_MISC_SHIFT) + + +class Device; + +class Texture +{ +public: + Texture(Device* device); + ~Texture(); + + size_t destroy(Device* device); + + void setTextureDescription(const CUDA_TEXTURE_DESC& descr); + + void setAddressMode(CUaddress_mode s, CUaddress_mode t, CUaddress_mode r); + void setFilterMode(CUfilter_mode filter, CUfilter_mode filterMipmap); + void setReadMode(bool asInteger); + void setSRGB(bool srgb); + void setBorderColor(float r, float g, float b, float a); + void setNormalizedCoords(bool normalized); + void setMaxAnisotropy(unsigned int aniso); + void setMipmapLevelBiasMinMax(float bias, float minimum, float maximum); + + bool create(const Picture* picture, const unsigned int flags); + bool create(const Texture* shared); + + bool update(const Picture* picture); + + Device* getOwner() const; + + unsigned int getWidth() const; + unsigned int getHeight() const; + unsigned int getDepth() const; + + cudaTextureObject_t getTextureObject() const; + + size_t getSizeBytes() const; // Texture memory tracking of CUarrays and CUmipmappedArrays in bytes sent to the cuMemCpy3D(). + + // Specific to spherical environment map. // DAR FIXME Move into a derived class instead. + // Create cumulative distribution function for importance sampling of spherical environment lights. Call last. + void calculateSphericalCDF(const float* rgba); + CUdeviceptr getCDF_U() const; + CUdeviceptr getCDF_V() const; + float getIntegral() const; + +private: + bool create1D(const Picture* picture); + bool create2D(const Picture* picture); + bool create3D(const Picture* picture); + bool createCube(const Picture* picture); + bool createEnv(const Picture* picture); + + bool update1D(const Picture* picture); + bool update2D(const Picture* picture); + bool update3D(const Picture* picture); + bool updateCube(const Picture* picture); + bool updateEnv(const Picture* picture); + +private: + Device* m_owner; // The device which created this Texture. Needed for peer-to-peer sharing, resp. for proper destruction. + + unsigned int m_width; + unsigned int m_height; + unsigned int m_depth; + + unsigned int m_flags; + + unsigned int m_encodingHost; + unsigned int m_encodingDevice; + + CUDA_ARRAY3D_DESCRIPTOR m_descArray3D; + size_t m_sizeBytesPerElement; + + CUDA_RESOURCE_DESC m_resourceDescription; // For the final texture object creation. + + CUDA_TEXTURE_DESC m_textureDescription; // This contains all texture parameters which can be set individually or as a whole. + + // Note that the CUarray or CUmipmappedArray are shared among peer devices, not the texture object! + // This needs to be created per device, which happens in the two create() functions. + CUtexObject m_textureObject; + + // Only one of these is ever used per texture. + CUarray m_d_array; + CUmipmappedArray m_d_mipmappedArray; + + // How much memory the CUarray or CUmipmappedArray required in bytes input to cuMemcpy3d() + // (without m_deviceAttribute.textureAlignment or potential row padding on the device). + size_t m_sizeBytesArray; + + // Specific to spherical environment map. + CUdeviceptr m_d_envCDF_U; + CUdeviceptr m_d_envCDF_V; + float m_integral; +}; + +#endif // TEXTURE_H diff --git a/apps/bench_shared_offscreen/inc/Timer.h b/apps/bench_shared_offscreen/inc/Timer.h new file mode 100644 index 00000000..2c13625d --- /dev/null +++ b/apps/bench_shared_offscreen/inc/Timer.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// This code is part of the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + +#pragma once + +#ifndef TIMER_H +#define TIMER_H + +#if defined(_WIN32) + #include +#else + #include +#endif + + +/*! \brief A simple timer class. + * This timer class can be used on Windows and Linux systems to + * measure time intervals in seconds. + * The timer can be started and stopped several times and accumulates + * time elapsed between the start() and stop() calls. */ +class Timer +{ +public: + //! Default constructor. Constructs a Timer, but does not start it yet. + Timer(); + + //! Default destructor. + ~Timer(); + + //! Starts the timer. + void start(); + + //! Stops the timer. + void stop(); + + //! Resets the timer. + void reset(); + + //! Resets the timer and starts it. + void restart(); + + //! Returns the current time in seconds. + double getTime() const; + + //! Return whether the timer is still running. + bool isRunning() const { return m_running; } + +private: +#if defined(_WIN32) + typedef LARGE_INTEGER Time; +#else + typedef timeval Time; +#endif + +private: + double calcDuration(Time begin, Time end) const; + +private: +#if defined(_WIN32) + LARGE_INTEGER m_freq; +#endif + Time m_begin; + bool m_running; + double m_seconds; +}; + +#endif // TIMER_H diff --git a/apps/bench_shared_offscreen/inc/TonemapperGUI.h b/apps/bench_shared_offscreen/inc/TonemapperGUI.h new file mode 100644 index 00000000..843248e4 --- /dev/null +++ b/apps/bench_shared_offscreen/inc/TonemapperGUI.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef TONEMAPPER_GUI_H +#define TONEMAPPER_GUI_H + +struct TonemapperGUI +{ + float gamma; + float whitePoint; + float colorBalance[3]; + float burnHighlights; + float crushBlacks; + float saturation; + float brightness; +}; + +#endif // TONEMAPPER_GUI_H diff --git a/apps/bench_shared_offscreen/shaders/anyhit.cu b/apps/bench_shared_offscreen/shaders/anyhit.cu new file mode 100644 index 00000000..b51dc47d --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/anyhit.cu @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +//#include "vertex_attributes.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +// One anyhit program for the radiance ray for all materials with cutout opacity! +extern "C" __global__ void __anyhit__radiance_cutout() +{ + GeometryInstanceData* theData = reinterpret_cast(optixGetSbtDataPointer()); + + const MaterialDefinition& material = sysData.materialDefinitions[theData->idMaterial]; + + if (material.textureCutout != 0) + { + // Cast the CUdeviceptr to the actual format for Triangles geometry. + const uint3* indices = reinterpret_cast(theData->indices); + const TriangleAttributes* attributes = reinterpret_cast(theData->attributes); + + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const uint3 tri = indices[thePrimitiveIndex]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + const float3 texcoord = attributes[tri.x].texcoord * alpha + + attributes[tri.y].texcoord * theBarycentrics.x + + attributes[tri.z].texcoord * theBarycentrics.y; + + const float opacity = intensity(make_float3(tex2D(material.textureCutout, texcoord.x, texcoord.y))); + + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Stochastic alpha test to get an alpha blend effect. + if (opacity < 1.0f && opacity <= rng(thePrd->seed)) // No need to calculate an expensive random number if the test is going to fail anyway. + { + optixIgnoreIntersection(); + } + } +} + + +// The shadow ray program for all materials with no cutout opacity. +extern "C" __global__ void __anyhit__shadow() +{ + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->flags |= FLAG_SHADOW; // Visbility check failed. + + optixTerminateRay(); +} + + +extern "C" __global__ void __anyhit__shadow_cutout() // For the radiance ray type. +{ + GeometryInstanceData* theData = reinterpret_cast(optixGetSbtDataPointer()); + + const MaterialDefinition& material = sysData.materialDefinitions[theData->idMaterial]; + + float opacity = 1.0f; + + if (material.textureCutout != 0) + { + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + const uint3* indices = reinterpret_cast(theData->indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData->attributes); + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + const float3 texcoord = attributes[tri.x].texcoord * alpha + + attributes[tri.y].texcoord * theBarycentrics.x + + attributes[tri.z].texcoord * theBarycentrics.y; + + opacity = intensity(make_float3(tex2D(material.textureCutout, texcoord.x, texcoord.y))); + } + + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + // Stochastic alpha test to get an alpha blend effect. + if (opacity < 1.0f && opacity <= rng(thePrd->seed)) // No need to calculate an expensive random number if the test is going to fail anyway. + { + optixIgnoreIntersection(); + } + else + { + thePrd->flags |= FLAG_SHADOW; + optixTerminateRay(); + } +} diff --git a/apps/bench_shared_offscreen/shaders/bxdf_diffuse.cu b/apps/bench_shared_offscreen/shaders/bxdf_diffuse.cu new file mode 100644 index 00000000..d3350730 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/bxdf_diffuse.cu @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +__forceinline__ __device__ void alignVector(const float3& axis, float3& w) +{ + // Align w with axis. + const float s = copysignf(1.0f, axis.z); + w.z *= s; + const float3 h = make_float3(axis.x, axis.y, axis.z + s); + const float k = dot(w, h) / (1.0f + fabsf(axis.z)); + w = k * h - w; +} + +__forceinline__ __device__ void unitSquareToCosineHemisphere(const float2 sample, const float3& axis, float3& w, float& pdf) +{ + // Choose a point on the local hemisphere coordinates about +z. + const float theta = 2.0f * M_PIf * sample.x; + const float r = sqrtf(sample.y); + w.x = r * cosf(theta); + w.y = r * sinf(theta); + w.z = 1.0f - w.x * w.x - w.y * w.y; + w.z = (0.0f < w.z) ? sqrtf(w.z) : 0.0f; + + pdf = w.z * M_1_PIf; + + // Align with axis. + alignVector(axis, w); +} + +// BRDF Diffuse (Lambert) + +extern "C" __device__ void __direct_callable__sample_brdf_diffuse(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Cosine weighted hemisphere sampling for Lambert material. + unitSquareToCosineHemisphere(rng2(prd->seed), state.normal, prd->wi, prd->pdf); + + if (prd->pdf <= 0.0f || dot(prd->wi, state.normalGeo) <= 0.0f) + { + prd->flags |= FLAG_TERMINATE; + return; + } + + // This would be the universal implementation for an arbitrary sampling of a diffuse surface. + // prd->f_over_pdf = state.albedo * (M_1_PIf * fabsf(dot(prd->wi, state.normal)) / prd->pdf); + + // PERF Since the cosine-weighted hemisphere distribution is a perfect importance-sampling of the Lambert material, + // the whole term ((M_1_PIf * fabsf(dot(prd->wi, state.normal)) / prd->pdf) is always 1.0f here! + prd->f_over_pdf = state.albedo; + + prd->flags |= FLAG_DIFFUSE; // Direct lighting will be done with multiple importance sampling. +} + +// The parameter wiL is the lightSample.direction (direct lighting), not the next ray segment's direction prd.wi (indirect lighting). +extern "C" __device__ float4 __direct_callable__eval_brdf_diffuse(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +{ + const float3 f = state.albedo * M_1_PIf; + const float pdf = fmaxf(0.0f, dot(wiL, state.normal) * M_1_PIf); + + return make_float4(f, pdf); +} + diff --git a/apps/bench_shared_offscreen/shaders/bxdf_ggx_smith.cu b/apps/bench_shared_offscreen/shaders/bxdf_ggx_smith.cu new file mode 100644 index 00000000..a34a9f21 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/bxdf_ggx_smith.cu @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + +// "Microfacet Models for Refraction through Rough Surfaces" - Walter, Marschner, Li, Torrance. 2007 +// "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" - Eric Heitz + +// This function evaluates a Fresnel dielectric function when the transmitting cosine ("cost") +// is unknown and the incident index of refraction is assumed to be 1.0f. +// \param et The transmitted index of refraction. +// \param costIn The cosine of the angle between the incident direction and normal direction. +__forceinline__ __device__ float evaluateFresnelDielectric(const float et, const float cosIn) +{ + const float cosi = fabsf(cosIn); + + float sint = 1.0f - cosi * cosi; + sint = (0.0f < sint) ? sqrtf(sint) / et : 0.0f; + + // Handle total internal reflection. + if (1.0f < sint) + { + return 1.0f; + } + + float cost = 1.0f - sint * sint; + cost = (0.0f < cost) ? sqrtf(cost) : 0.0f; + + const float et_cosi = et * cosi; + const float et_cost = et * cost; + + const float rPerpendicular = (cosi - et_cost) / (cosi + et_cost); + const float rParallel = (et_cosi - cost) / (et_cosi + cost); + + const float result = (rParallel * rParallel + rPerpendicular * rPerpendicular) * 0.5f; + + return (result <= 1.0f) ? result : 1.0f; +} + + +// Optimized version to calculate D and PDF reusing shared calculations. +__forceinline__ __device__ float2 distribution_d_pdf(const float ax, const float ay, const float3& wm) +{ + if (DENOMINATOR_EPSILON < wm.z) // Heaviside function: X_plus(wm * wg). (wm is in tangent space.) + { + const float cosThetaSqr = wm.z * wm.z; + const float tanThetaSqr = (1.0f - cosThetaSqr) / cosThetaSqr; + + const float phiM = atan2f(wm.y, wm.x); + const float cosPhiM = cosf(phiM); + const float sinPhiM = sinf(phiM); + + const float term = 1.0f + tanThetaSqr * ((cosPhiM * cosPhiM) / (ax * ax) + (sinPhiM * sinPhiM) / (ay * ay)); + + const float d = 1.0f / (M_PIf * ax * ay * cosThetaSqr * cosThetaSqr * term * term); // Heitz, Formula (85) + const float pdf = d * wm.z; // PDF with respect to the half-direction. + + return make_float2(d, pdf); + } + return make_float2(0.0f); +} + +// Return a sample direction in local tangent space coordinates. +__forceinline__ __device__ float3 distribution_sample(const float ax, const float ay, const float u1, const float u2) +{ + // Made isotropic to ay. Output vector scales .x accordingly. + const float theta = atanf(ay * sqrtf(u1) / sqrtf(1.0f - u1)); // Walter, Formula (35). + const float phi = 2.0f * M_PIf * u2; // Walter, Formula (36). + const float sinTheta = sinf(theta); + return normalize(make_float3(cosf(phi) * sinTheta * ax / ay, // Heitz, Formula (77) + sinf(phi) * sinTheta, + cosf(theta))); +} + +// "Microfacet Models for Refraction through Rough Surfaces" - Walter, Marschner, Li, Torrance. +// PERF Using this because it's faster than the approximation below. +__forceinline__ __device__ float smith_G1(const float alpha, const float3& w, const float3& wm) +{ + const float w_wm = dot(w, wm); + if (w_wm * w.z <= 0.0f) // X_plus(v * m / v * n) from Walter, Formula (34). // PERF Checking the sign with a multiplication here. + { + return 0.0f; + } + const float cosThetaSqr = w.z * w.z; + const float sinThetaSqr = 1.0f - cosThetaSqr; + //const float tanTheta = (0.0f < sinThetaSqr) ? sqrtf(sinThetaSqr) / w.z : 0.0f; // PERF Remove the sqrtf() by calculating tanThetaSqr here + //const float invA = alpha * tanTheta; // because this is squared below: invASqr = alpha * alpha * tanThetaSqr; + //const float lambda = (-1.0f + sqrtf(1.0f + invA * invA)) * 0.5f; // Heitz, Formula (86) + //return 1.0f / (1.0f + lambda); // Heitz, below Formula (69) + const float tanThetaSqr = (0.0f < sinThetaSqr) ? sinThetaSqr / cosThetaSqr : 0.0f; + const float invASqr = alpha * alpha * tanThetaSqr; + return 2.0f / (1.0f + sqrtf(1.0f + invASqr)); // Optimized version is Walter, Formula (34) +} + +// Approximation from "Microfacet Models for Refraction through Rough Surfaces" - Walter, Marschner, Li, Torrance. +//__forceinline__ __device__ float smith_G1(const float alpha, const float3& w, const float3& wm) +//{ +// const float w_wm = optix::dot(w, wm); +// if (w_wm * w.z <= 0.0f) // X_plus(v * m / v * n) from Walter, Formula (34). // PERF Checking the sign with a multiplication here. +// { +// return 0.0f; +// } +// const float t = 1.0f - w.z * w.z; +// const float tanTheta = (0.0f < t) ? sqrtf(t) / w.z : 0.0f; +// if (tanTheta == 0.0f) +// { +// return 1.0f; +// } +// const float a = 1.0f / (tanTheta * alpha); +// if (1.6f <= a) +// { +// return 1.0f; +// } +// const float aSqr = a * a; +// return (3.535f * a + 2.181f * aSqr) / (1.0f + 2.276f * a + 2.577f * aSqr); // Walter, Formula (27) used for Heitz, Formula (83) +//} + +__forceinline__ __device__ float distribution_G(const float ax, const float ay, const float3& wo, const float3& wi, const float3& wm) +{ + float phi = atan2f(wo.y, wo.x); + float c = cosf(phi); + float s = sinf(phi); + float alpha = sqrtf(c * c * ax * ax + s * s * ay * ay); // Heitz, Formula (80) for wo + + const float g = smith_G1(alpha, wo, wm); + + phi = atan2f(wi.y, wi.x); + c = cosf(phi); + s = sinf(phi); + alpha = sqrtf(c * c * ax * ax + s * s * ay * ay); // Heitz, Formula (80) for wi. + + return g * smith_G1(alpha, wi, wm); +} + +// ########## BRDF GGX with Smith shadowing + +extern "C" __device__ void __direct_callable__sample_brdf_ggx_smith(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Sample a microfacet normal in local space, which effectively is a tangent space coordinate. + const float2 sample = rng2(prd->seed); + + const float3 wm = distribution_sample(material.roughness.x, + material.roughness.y, + sample.x, + sample.y); + + const TBN tangentSpace(state.tangent, state.normal); // Tangent space transformation, handles anisotropic rotation. + + const float3 wh = tangentSpace.transformToWorld(wm); // wh is the microfacet normal in world space coordinates! + + prd->wi = reflect(-prd->wo, wh); + + if (dot(prd->wi, state.normalGeo) <= 0.0f) // Do not sample opaque materials below the geometric surface. + { + prd->flags |= FLAG_TERMINATE; + return; + } + + const float3 wo = tangentSpace.transformToLocal(prd->wo); + const float3 wi = tangentSpace.transformToLocal(prd->wi); + + const float wi_wh = dot(prd->wi, wh); + + if (wo.z <= 0.0f || wi.z <= 0.0f || wi_wh <= 0.0f) + { + prd->flags |= FLAG_TERMINATE; + return; + } + + const float2 D_PDF = distribution_d_pdf(material.roughness.x, + material.roughness.y, + wm); + if (D_PDF.y <= 0.0f) + { + prd->flags |= FLAG_TERMINATE; + return; + } + + const float G = distribution_G(material.roughness.x, + material.roughness.y, + wo, wi, wm); + + // Watch out: PBRT2 puts the factor 1.0f / (4.0f * cosThetaH) into the pdf() functions. + // This is the density function with respect to the light vector. + prd->pdf = D_PDF.y / (4.0f * wi_wh); + //prd->f_over_pdf = state.albedo * (fabsf(dot(prd->wi, state->normal)) * D_PDF.x * G / (4.0f * wo.z * wi.z * prd->pdf)); + prd->f_over_pdf = state.albedo * (G * D_PDF.x * wi_wh / (D_PDF.y * wo.z)); // Optimized version with all factors canceled out. + + prd->flags |= FLAG_DIFFUSE; // Can handle direct lighting. +} + +// When reaching this function, the roughness values are clamped to a minimal working value already, +// so that anisotropic roughness can simply be calculated without additional checks! +extern "C" __device__ float4 __direct_callable__eval_brdf_ggx_smith(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +{ + const TBN tangentSpace(state.tangent, state.normal); // Tangent space transformation, handles anisotropic rotation. + + const float3 wo = tangentSpace.transformToLocal(prd->wo); + const float3 wi = tangentSpace.transformToLocal(wiL); + + if (wo.z <= 0.0f || wi.z <= 0.0f) // Either vector on the other side of the node.normal hemisphere? + { + return make_float4(0.0f); + } + + float3 wm = wo + wi; // The half-vector is the microfacet normal, in tangent space + if (isNull(wm)) // Collinear in opposing directions? + { + return make_float4(0.0f); + } + + wm = normalize(wm); + + const float2 D_PDF = distribution_d_pdf(material.roughness.x, + material.roughness.y, + wm); + + const float G = distribution_G(material.roughness.x, + material.roughness.y, + wo, wi, wm); + + const float3 f = state.albedo * (D_PDF.x * G / (4.0f * wo.z * wi.z)); + + // Watch out: PBRT2 puts the factor 1.0f / (4.0f * cosThetaH) into the pdf() functions. + // This is the density function with respect to the light vector. + const float pdf = D_PDF.y / (4.0f * dot(wi, wm)); + + return make_float4(f, pdf); +} + +// ########## BSDF GGX with Smith shadowing + +extern "C" __device__ void __direct_callable__sample_bsdf_ggx_smith(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Return the current material's absorption coefficient and ior to the integrator to be able to support nested materials. + prd->absorption_ior = make_float4(material.absorption, material.ior); + + // Need to figure out here which index of refraction to use if the ray is already inside some refractive medium. + // This needs to happen with the original FLAG_FRONTFACE condition to find out from which side of the geometry we're looking! + // ior.xy are the current volume's IOR and the surrounding volume's IOR. + // Thin-walled materials have no volume, always use the frontface eta for them! + const float eta = (prd->flags & (FLAG_FRONTFACE | FLAG_THINWALLED)) + ? prd->absorption_ior.w / prd->ior.x + : prd->ior.y / prd->absorption_ior.w; + + // Sample a microfacet normal in local space, which effectively is a tangent space coordinate. + const float2 sample = rng2(prd->seed); + + const float3 wm = distribution_sample(material.roughness.x, + material.roughness.y, + sample.x, + sample.y); + + const TBN tangentSpace(state.tangent, state.normal); // Tangent space transformation, handles anisotropic rotation. + + const float3 wh = tangentSpace.transformToWorld(wm); // wh is the microfacet normal in world space coordinates! + + + const float3 R = reflect(-prd->wo, wh); + + float reflective = 1.0f; + if (refract(prd->wi, -prd->wo, wh, eta)) + { + if (prd->flags & FLAG_THINWALLED) + { + // DAR FIXME The resulting vector isn't necessarily on the other side of the geometric normal, but should be! + prd->wi = reflect(R, state.normal); // Flip the vector to the other side of the normal. + } + // Note, not using fabs() on the cosine to get the refract side correct. + // Total internal reflection will leave this reflection probability at 1.0f. + reflective = evaluateFresnelDielectric(eta, dot(prd->wo, wh)); + } + + const float pseudo = rng(prd->seed); + if (pseudo < reflective) + { + prd->wi = R; // Fresnel reflection or total internal reflection. + } + else if (!(prd->flags & FLAG_THINWALLED)) // Only non-thinwalled materials have a volume and transmission events. + { + prd->flags |= FLAG_TRANSMISSION; + } + + // No Fresnel factor here. The probability to pick one or the other side took care of that. + prd->f_over_pdf = state.albedo; + prd->pdf = 1.0f; // Not 0.0f to make sure the path is not terminated. Otherwise unused for specular events. +} + +//extern "C" __device__ float4 __direct_callable__eval_bsdf_ggx_smith(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +//{ +// // The implementation handles this as specular continuation. Can reuse the eval_brdf_specular() implementation. +// return make_float4(0.0f); +//} diff --git a/apps/bench_shared_offscreen/shaders/bxdf_specular.cu b/apps/bench_shared_offscreen/shaders/bxdf_specular.cu new file mode 100644 index 00000000..f325eeec --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/bxdf_specular.cu @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "material_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + +// This function evaluates a Fresnel dielectric function when the transmitting cosine ("cost") +// is unknown and the incident index of refraction is assumed to be 1.0f. +// \param et The transmitted index of refraction. +// \param costIn The cosine of the angle between the incident direction and normal direction. +__forceinline__ __device__ float evaluateFresnelDielectric(const float et, const float cosIn) +{ + const float cosi = fabsf(cosIn); + + float sint = 1.0f - cosi * cosi; + sint = (0.0f < sint) ? sqrtf(sint) / et : 0.0f; + + // Handle total internal reflection. + if (1.0f < sint) + { + return 1.0f; + } + + float cost = 1.0f - sint * sint; + cost = (0.0f < cost) ? sqrtf(cost) : 0.0f; + + const float et_cosi = et * cosi; + const float et_cost = et * cost; + + const float rPerpendicular = (cosi - et_cost) / (cosi + et_cost); + const float rParallel = (et_cosi - cost) / (et_cosi + cost); + + const float result = (rParallel * rParallel + rPerpendicular * rPerpendicular) * 0.5f; + + return (result <= 1.0f) ? result : 1.0f; +} + +// ########## BRDF Specular (tinted mirror) + +extern "C" __device__ void __direct_callable__sample_brdf_specular(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + prd->wi = reflect(-prd->wo, state.normal); + + if (dot(prd->wi, state.normalGeo) <= 0.0f) // Do not sample opaque materials below the geometric surface. + { + prd->flags |= FLAG_TERMINATE; + return; + } + + prd->f_over_pdf = state.albedo; + prd->pdf = 1.0f; // Not 0.0f to make sure the path is not terminated. Otherwise unused for specular events. +} + +// This function will be used for all specular materials. +// This is actually never reached in this simply material system, because the FLAG_DIFFUSE flag is not set when a specular BSDF is has been sampled. +extern "C" __device__ float4 __direct_callable__eval_brdf_specular(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +{ + return make_float4(0.0f); +} + +// ########## BSDF Specular (glass etc.) + +extern "C" __device__ void __direct_callable__sample_bsdf_specular(const MaterialDefinition& material, const State& state, PerRayData* prd) +{ + // Return the current material's absorption coefficient and ior to the integrator to be able to support nested materials. + prd->absorption_ior = make_float4(material.absorption, material.ior); + + // Need to figure out here which index of refraction to use if the ray is already inside some refractive medium. + // This needs to happen with the original FLAG_FRONTFACE condition to find out from which side of the geometry we're looking! + // ior.xy are the current volume's IOR and the surrounding volume's IOR. + // Thin-walled materials have no volume, always use the frontface eta for them! + const float eta = (prd->flags & (FLAG_FRONTFACE | FLAG_THINWALLED)) + ? prd->absorption_ior.w / prd->ior.x + : prd->ior.y / prd->absorption_ior.w; + + const float3 R = reflect(-prd->wo, state.normal); + + float reflective = 1.0f; + + if (refract(prd->wi, -prd->wo, state.normal, eta)) + { + if (prd->flags & FLAG_THINWALLED) + { + prd->wi = -prd->wo; // Straight through, no volume. + } + // Total internal reflection will leave this reflection probability at 1.0f. + reflective = evaluateFresnelDielectric(eta, dot(prd->wo, state.normal)); + } + + const float pseudo = rng(prd->seed); + if (pseudo < reflective) + { + prd->wi = R; // Fresnel reflection or total internal reflection. + } + else if (!(prd->flags & FLAG_THINWALLED)) // Only non-thinwalled materials have a volume and transmission events. + { + prd->flags |= FLAG_TRANSMISSION; + } + + // No Fresnel factor here. The probability to pick one or the other side took care of that. + prd->f_over_pdf = state.albedo; + prd->pdf = 1.0f; // Not 0.0f to make sure the path is not terminated. Otherwise unused for specular events. +} + +// PERF Same as every specular material. +//extern "C" __device__ float4 __direct_callable__eval_bsdf_specular(const MaterialDefinition& material, const State& state, const PerRayData* const prd, const float3 wiL) +//{ +// return make_float4(0.0f); +//} + diff --git a/apps/bench_shared_offscreen/shaders/camera_definition.h b/apps/bench_shared_offscreen/shaders/camera_definition.h new file mode 100644 index 00000000..e0adf352 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/camera_definition.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef CAMERA_DEFINITION_H +#define CAMERA_DEFINITION_H + +struct CameraDefinition +{ + float3 P; + float3 U; + float3 V; + float3 W; +}; + +#endif // CAMERA_DEFINITION_H diff --git a/apps/bench_shared_offscreen/shaders/closesthit.cu b/apps/bench_shared_offscreen/shaders/closesthit.cu new file mode 100644 index 00000000..e56518cb --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/closesthit.cu @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +#include "vertex_attributes.h" +#include "function_indices.h" +#include "material_definition.h" +#include "light_definition.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +// Get the 3x4 object to world transform and its inverse from a two-level hierarchy. +__forceinline__ __device__ void getTransforms(float4* mW, float4* mO) +{ + OptixTraversableHandle handle = optixGetTransformListHandle(0); + + const float4* tW = optixGetInstanceTransformFromHandle(handle); + const float4* tO = optixGetInstanceInverseTransformFromHandle(handle); + + mW[0] = tW[0]; + mW[1] = tW[1]; + mW[2] = tW[2]; + + mO[0] = tO[0]; + mO[1] = tO[1]; + mO[2] = tO[2]; +} + +// Functions to get the individual transforms in case only one of them is needed. + +__forceinline__ __device__ void getTransformObjectToWorld(float4* mW) +{ + OptixTraversableHandle handle = optixGetTransformListHandle(0); + + const float4* tW = optixGetInstanceTransformFromHandle(handle); + + mW[0] = tW[0]; + mW[1] = tW[1]; + mW[2] = tW[2]; +} + +__forceinline__ __device__ void getTransformWorldToObject(float4* mO) +{ + OptixTraversableHandle handle = optixGetTransformListHandle(0); + + const float4* tO = optixGetInstanceInverseTransformFromHandle(handle); + + mO[0] = tO[0]; + mO[1] = tO[1]; + mO[2] = tO[2]; +} + + +// Matrix3x4 * point. v.w == 1.0f +__forceinline__ __device__ float3 transformPoint(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z + m[0].w; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z + m[1].w; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z + m[2].w; + + return r; +} + +// Matrix3x4 * vector. v.w == 0.0f +__forceinline__ __device__ float3 transformVector(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z; + r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z; + r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z; + + return r; +} + +// InverseMatrix3x4^T * normal. v.w == 0.0f +// Get the inverse matrix as input and applies it as inverse transpose. +__forceinline__ __device__ float3 transformNormal(const float4* m, const float3& v) +{ + float3 r; + + r.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + r.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + r.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + + return r; +} + + +extern "C" __global__ void __closesthit__radiance() +{ + GeometryInstanceData* theData = reinterpret_cast(optixGetSbtDataPointer()); + + // Cast the CUdeviceptr to the actual format for Triangles geometry. + const unsigned int thePrimitiveIndex = optixGetPrimitiveIndex(); + + const uint3* indices = reinterpret_cast(theData->indices); + const uint3 tri = indices[thePrimitiveIndex]; + + const TriangleAttributes* attributes = reinterpret_cast(theData->attributes); + + const TriangleAttributes& attr0 = attributes[tri.x]; + const TriangleAttributes& attr1 = attributes[tri.y]; + const TriangleAttributes& attr2 = attributes[tri.z]; + + const float2 theBarycentrics = optixGetTriangleBarycentrics(); // beta and gamma + const float alpha = 1.0f - theBarycentrics.x - theBarycentrics.y; + + const float3 ng = cross(attr1.vertex - attr0.vertex, attr2.vertex - attr0.vertex); + const float3 tg = attr0.tangent * alpha + attr1.tangent * theBarycentrics.x + attr2.tangent * theBarycentrics.y; + const float3 ns = attr0.normal * alpha + attr1.normal * theBarycentrics.x + attr2.normal * theBarycentrics.y; + + // DAR PERF This State lies in memory. It's more efficient to hold the data in registers. + // Problem is that more advanced material systems need the State all the time. + State state; // All in world space coordinates! + + state.texcoord = attr0.texcoord * alpha + attr1.texcoord * theBarycentrics.x + attr2.texcoord * theBarycentrics.y; + + float4 objectToWorld[3]; + float4 worldToObject[3]; + + getTransforms(objectToWorld, worldToObject); + + state.normalGeo = normalize(transformNormal(worldToObject, ng)); + state.tangent = normalize(transformVector(objectToWorld, tg)); + state.normal = normalize(transformNormal(worldToObject, ns)); + + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->distance = optixGetRayTmax(); // Return the current path segment distance, needed for absorption calculations in the integrator. + + //thePrd->pos = optixGetWorldRayOrigin() + optixGetWorldRayDirection() * optixGetRayTmax(); + thePrd->pos += thePrd->wi * thePrd->distance; // DEBUG Check which version is more efficient. + + // Explicitly include edge-on cases as frontface condition! + // Keeps the material stack from overflowing at silhouettes. + // Prevents that silhouettes of thin-walled materials use the backface material. + // Using the true geometry normal attribute as originally defined on the frontface! + thePrd->flags |= (0.0f <= dot(thePrd->wo, state.normalGeo)) ? FLAG_FRONTFACE : 0; + + if ((thePrd->flags & FLAG_FRONTFACE) == 0) // Looking at the backface? + { + // Means geometric normal and shading normal are always defined on the side currently looked at. + // This gives the backfaces of opaque BSDFs a defined result. + state.normalGeo = -state.normalGeo; + state.tangent = -state.tangent; + state.normal = -state.normal; + // Explicitly DO NOT recalculate the frontface condition! + } + + thePrd->radiance = make_float3(0.0f); + + // When hitting a geometric light, evaluate the emission first, because this needs the previous diffuse hit's pdf. + const int idLight = theData->idLight; + + if (0 <= idLight && (thePrd->flags & FLAG_FRONTFACE)) // This material is emissive and we're looking at the front face. + { + const float cosTheta = dot(thePrd->wo, state.normalGeo); + if (DENOMINATOR_EPSILON < cosTheta) + { + const LightDefinition& light = sysData.lightDefinitions[idLight]; + + float3 emission = light.emission; + +#if USE_NEXT_EVENT_ESTIMATION + const float lightPdf = (thePrd->distance * thePrd->distance) / (light.area * cosTheta); // This assumes the light.area is greater than zero. + + // If it's an implicit light hit from a diffuse scattering event and the light emission was not returning a zero pdf (e.g. backface or edge on). + if ((thePrd->flags & FLAG_DIFFUSE) && DENOMINATOR_EPSILON < lightPdf) + { + // Scale the emission with the power heuristic between the initial BSDF sample pdf and this implicit light sample pdf. + emission *= powerHeuristic(thePrd->pdf, lightPdf); + } +#endif // USE_NEXT_EVENT_ESTIMATION + + thePrd->radiance = emission; + + // PERF End the path when hitting a light. Emissive materials with a non-black BSDF would normally just continue. + thePrd->flags |= FLAG_TERMINATE; + return; + } + } + + // Start fresh with the next BSDF sample. (Either of these values remaining zero is an end-of-path condition.) + // The pdf of the previous evene was needed for the emission calculation above. + thePrd->f_over_pdf = make_float3(0.0f); + thePrd->pdf = 0.0f; + + const MaterialDefinition& material = sysData.materialDefinitions[theData->idMaterial]; + + state.albedo = material.albedo; + + if (material.textureAlbedo != 0) + { + const float3 texColor = make_float3(tex2D(material.textureAlbedo, state.texcoord.x, state.texcoord.y)); + + // Modulate the incoming color with the texture. + state.albedo *= texColor; // linear color, resp. if the texture has been uint8 and readmode set to use sRGB, then sRGB. + //state.albedo *= powf(texColor, 2.2f); // sRGB gamma correction done manually. + } + + // Only the last diffuse hit is tracked for multiple importance sampling of implicit light hits. + thePrd->flags = (thePrd->flags & ~FLAG_DIFFUSE) | FLAG_HIT | material.flags; // FLAG_THINWALLED can be set directly from the material. + + // Sample a new path direction. + const int indexBSDF = NUM_LENS_SHADERS + NUM_LIGHT_TYPES + material.indexBSDF * 2; + + optixDirectCall(indexBSDF, material, state, thePrd); + +#if USE_NEXT_EVENT_ESTIMATION + // Direct lighting if the sampled BSDF was diffuse and any light is in the scene. + const int numLights = sysData.numLights; + if ((thePrd->flags & FLAG_DIFFUSE) && 0 < numLights) + { + // Sample one of many lights. + const float2 sample = rng2(thePrd->seed); // Use lower dimension samples for the position. (Irrelevant for the LCG). + + // The caller picks the light to sample. Make sure the index stays in the bounds of the sysData.lightDefinitions array. + const int indexLight = (1 < numLights) ? clamp(static_cast(floorf(rng(thePrd->seed) * numLights)), 0, numLights - 1) : 0; + + const LightDefinition& light = sysData.lightDefinitions[indexLight]; + + const int indexCallable = NUM_LENS_SHADERS + light.type; + + LightSample lightSample = optixDirectCall(indexCallable, light, thePrd->pos, sample); + + if (0.0f < lightSample.pdf) // Useful light sample? + { + // Evaluate the BSDF in the light sample direction. Normally cheaper than shooting rays. + // Returns BSDF f in .xyz and the BSDF pdf in .w + // BSDF eval function is one index after the sample fucntion. + const float4 bsdf_pdf = optixDirectCall(indexBSDF + 1, material, state, thePrd, lightSample.direction); + + if (0.0f < bsdf_pdf.w && isNotNull(make_float3(bsdf_pdf))) + { + // Pass the current payload registers through to the shadow ray. + unsigned int p0 = optixGetPayload_0(); + unsigned int p1 = optixGetPayload_1(); + + // Note that the sysData.sceneEpsilon is applied on both sides of the shadow ray [t_min, t_max] interval + // to prevent self-intersections with the actual light geometry in the scene. + optixTrace(sysData.topObject, + thePrd->pos, lightSample.direction, // origin, direction + sysData.sceneEpsilon, lightSample.distance - sysData.sceneEpsilon, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT, // The shadow ray type only uses anyhit programs. + RAYTYPE_SHADOW, NUM_RAYTYPES, RAYTYPE_SHADOW, + p0, p1); // Pass through thePrd to the shadow ray. It needs the seed and sets flags. + + if ((thePrd->flags & FLAG_SHADOW) == 0) // Shadow flag not set? + { + if (thePrd->flags & FLAG_VOLUME) // Supporting nested materials includes having lights inside a volume. + { + // Calculate the transmittance along the light sample's distance in case it's inside a volume. + // The light must be in the same volume or it would have been shadowed. + lightSample.emission *= expf(-lightSample.distance * thePrd->sigma_t); + } + + const float weightMis = powerHeuristic(lightSample.pdf, bsdf_pdf.w); + + thePrd->radiance += make_float3(bsdf_pdf) * lightSample.emission * (weightMis * dot(lightSample.direction, state.normal) / lightSample.pdf); + } + } + } + } +#endif // USE_NEXT_EVENT_ESTIMATION +} diff --git a/apps/bench_shared_offscreen/shaders/compositor.cu b/apps/bench_shared_offscreen/shaders/compositor.cu new file mode 100644 index 00000000..257ff4f3 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/compositor.cu @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "config.h" + +#include "compositor_data.h" +#include "vector_math.h" + +// Compositor kernel to copy the tiles in the texelBuffer into the final outputBuffer location. +extern "C" __global__ void compositor(CompositorData* args) +{ + const unsigned int xLaunch = blockDim.x * blockIdx.x + threadIdx.x; + const unsigned int yLaunch = blockDim.y * blockIdx.y + threadIdx.y; + + if (yLaunch < args->resolution.y) + { + // First calculate block coordinates of this launch index. + // That is the launch index divided by the tile dimensions. (No operator>>() on vectors?) + const unsigned int xBlock = xLaunch >> args->tileShift.x; + const unsigned int yBlock = yLaunch >> args->tileShift.y; + + // Each device needs to start at a different column and each row should start with a different device. + const unsigned int xTile = xBlock * args->deviceCount + ((args->deviceIndex + yBlock) % args->deviceCount); + + // The horizontal pixel coordinate is: tile coordinate * tile width + launch index % tile width. + const unsigned int xPixel = xTile * args->tileSize.x + (xLaunch & (args->tileSize.x - 1)); // tileSize needs to be power-of-two for this modulo operation. + + if (xPixel < args->resolution.x) + { + const float4 *src = reinterpret_cast(args->tileBuffer); + float4 *dst = reinterpret_cast(args->outputBuffer); + + // The src location needs to be calculated with the original launch width, because gridDim.x * blockDim.x might be different. + dst[yLaunch * args->resolution.x + xPixel] = src[yLaunch * args->launchWidth + xLaunch]; // Copy one float4 per launch index. + } + } +} diff --git a/apps/bench_shared_offscreen/shaders/compositor_data.h b/apps/bench_shared_offscreen/shaders/compositor_data.h new file mode 100644 index 00000000..ec67ac34 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/compositor_data.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef COMPOSITOR_DATA_H +#define COMPOSITOR_DATA_H + +#include + +struct CompositorData +{ + // 8 byte alignment + CUdeviceptr outputBuffer; + CUdeviceptr tileBuffer; + + int2 resolution; // The actual rendering resolution. Independent from the launch dimensions for some rendering strategies. + int2 tileSize; // Example: make_int2(8, 4) for 8x4 tiles. Must be a power of two to make the division a right-shift. + int2 tileShift; // Example: make_int2(3, 2) for the integer division by tile size. That actually makes the tileSize redundant. + + // 4 byte alignment + int launchWidth; // The orignal launch width. Needed to calculate the source data index. The compositor launch gridDim.x * blockDim.x might be different! + int deviceCount; // Number of devices doing the rendering. + int deviceIndex; // Device index to be able to distinguish the individual devices in a multi-GPU environment. +}; + +#endif // COMPOSITOR_DATA_H diff --git a/apps/bench_shared_offscreen/shaders/config.h b/apps/bench_shared_offscreen/shaders/config.h new file mode 100644 index 00000000..d99cbd9f --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/config.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// This header with defines is included in all shaders +// to be able to switch different code paths at a central location. +// Changing any setting here will rebuild the whole solution. + +#pragma once + +#ifndef CONFIG_H +#define CONFIG_H + +// Used to shoot rays without distance limit. +#define RT_DEFAULT_MAX 1.e27f + +// Scales the m_sceneEpsilonFactor to give the effective SystemData::sceneEpsilon. +#define SCENE_EPSILON_SCALE 1.0e-7f + +// Prevent that division by very small floating point values results in huge values, for example dividing by pdf. +#define DENOMINATOR_EPSILON 1.0e-6f + +// If both anisotropic roughness values fall below this threshold, the BSDF switches to specular. +#define MICROFACET_MIN_ROUGHNESS 0.0014142f + +// 0 == Brute force path tracing without next event estimation (direct lighting). // Debug setting to compare lighting results. +// 1 == Next event estimation per path vertex (direct lighting) and using MIS with power heuristic. // Default. +#define USE_NEXT_EVENT_ESTIMATION 1 + +// 0 == All debug features disabled. Code optimization level on maximum. (Benchmark only in this mode!) +// 1 == All debug features enabled. Code generated with full debug info. (Really only for debugging, big performance hit!) +#define USE_DEBUG_EXCEPTIONS 0 + +// 0 == Disable clock() usage and time view display. +// 1 == Enable clock() usage and time view display. +#define USE_TIME_VIEW 0 + +// The m_clockFactor GUI value is scaled by this. +// With a default of m_clockFactor = 1000 this means a million clocks will be value 1.0 in the alpha channel +// which is used as 1D texture coordinate inside the GLSL display shader and results in white in the temperature ramp texture. +#define CLOCK_FACTOR_SCALE 1.0e-9f + +// These defines are used in Application, Rasterizer, Raytracer and Device. This is the only header included by all. +#define INTEROP_MODE_OFF 0 +#define INTEROP_MODE_TEX 1 +#define INTEROP_MODE_PBO 2 + +#endif // CONFIG_H diff --git a/apps/bench_shared_offscreen/shaders/exception.cu b/apps/bench_shared_offscreen/shaders/exception.cu new file mode 100644 index 00000000..7eb65c12 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/exception.cu @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" + +extern "C" __constant__ SystemData sysData; + +extern "C" __global__ void __exception__all() +{ + //const uint3 theLaunchDim = optixGetLaunchDimensions(); + const uint3 theLaunchIndex = optixGetLaunchIndex(); + const int theExceptionCode = optixGetExceptionCode(); + + printf("Exception %d at (%u, %u)\n", theExceptionCode, theLaunchIndex.x, theLaunchIndex.y); + + // FIXME This only works for render strategies where the launch dimension matches the outputBuffer resolution. + //float4* buffer = reinterpret_cast(sysData.outputBuffer); + //const unsigned int index = theLaunchIndex.y * theLaunchDim.x + theLaunchIndex.x; + + //buffer[index] = make_float4(1000000.0f, 0.0f, 1000000.0f, 1.0f); // super magenta +} diff --git a/apps/bench_shared_offscreen/shaders/function_indices.h b/apps/bench_shared_offscreen/shaders/function_indices.h new file mode 100644 index 00000000..2bef321a --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/function_indices.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef FUNCTION_INDICES_H +#define FUNCTION_INDICES_H + +enum RayType +{ + RAYTYPE_RADIANCE = 0, + RAYTYPE_SHADOW = 1, + + NUM_RAYTYPES = 2 +}; + +enum LensShader +{ + LENS_SHADER_PINHOLE = 0, + LENS_SHADER_FISHEYE = 1, + LENS_SHADER_SPHERE = 2, + + NUM_LENS_SHADERS = 3 +}; + +enum FunctionIndex +{ + INDEX_BRDF_DIFFUSE = 0, + INDEX_BRDF_SPECULAR = 1, + INDEX_BSDF_SPECULAR = 2, + INDEX_BRDF_GGX_SMITH = 3, + INDEX_BSDF_GGX_SMITH = 4, + + NUM_BSDF_INDICES = 5 +}; + +#endif // FUNCTION_INDICES_H diff --git a/apps/bench_shared_offscreen/shaders/lens_shader.cu b/apps/bench_shared_offscreen/shaders/lens_shader.cu new file mode 100644 index 00000000..f499103d --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/lens_shader.cu @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "shader_common.h" + +extern "C" __constant__ SystemData sysData; + +// Note that all these lens shaders return the primary ray origin and direction in world space! + +extern "C" __device__ void __direct_callable__pinhole(const float2 screen, const float2 pixel, const float2 sample, + float3& origin, float3& direction) +{ + const float2 fragment = pixel + sample; // Jitter the sub-pixel location + const float2 ndc = (fragment / screen) * 2.0f - 1.0f; // Normalized device coordinates in range [-1, 1]. + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + origin = camera.P; + direction = normalize(camera.U * ndc.x + + camera.V * ndc.y + + camera.W); +} + + +extern "C" __device__ void __direct_callable__fisheye(const float2 screen, const float2 pixel, const float2 sample, + float3& origin, float3& direction) +{ + const float2 fragment = pixel + sample; // x, y + + // Implement a fisheye projection with 180 degrees angle across the image diagonal (=> all pixels rendered, not a circular fisheye). + const float2 center = screen * 0.5f; + const float2 uv = (fragment - center) / length(center); // uv components are in the range [0, 1]. Both 1 in the corners of the image! + const float z = cosf(length(uv) * 0.7071067812f * 0.5f * M_PIf); // Scale by 1.0f / sqrtf(2.0f) to get length into the range [0, 1] + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + const float3 U = normalize(camera.U); + const float3 V = normalize(camera.V); + const float3 W = normalize(camera.W); + + origin = camera.P; + direction = normalize(uv.x * U + uv.y * V + z * W); +} + + +extern "C" __device__ void __direct_callable__sphere(const float2 screen, const float2 pixel, const float2 sample, + float3& origin, float3& direction) +{ + const float2 uv = (pixel + sample) / screen; // "texture coordinates" + + // Convert the 2D index into a direction. + const float phi = uv.x * 2.0f * M_PIf; + const float theta = uv.y * M_PIf; + + const float sinTheta = sinf(theta); + + const float3 v = make_float3(-sinf(phi) * sinTheta, + -cosf(theta), + -cosf(phi) * sinTheta); + + const CameraDefinition camera = sysData.cameraDefinitions[0]; + + const float3 U = normalize(camera.U); + const float3 V = normalize(camera.V); + const float3 W = normalize(camera.W); + + origin = camera.P; + direction = normalize(v.x * U + v.y * V + v.z * W); +} diff --git a/apps/bench_shared_offscreen/shaders/light_definition.h b/apps/bench_shared_offscreen/shaders/light_definition.h new file mode 100644 index 00000000..15267742 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/light_definition.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef LIGHT_DEFINITION_H +#define LIGHT_DEFINITION_H + +enum LightType +{ + LIGHT_ENVIRONMENT = 0, // constant color or spherical environment map. + LIGHT_PARALLELOGRAM = 1, // Parallelogram area light. + + NUM_LIGHT_TYPES = 2 +}; + +struct LightDefinition +{ + LightType type; // Constant or spherical environment, rectangle (parallelogram). + + // Rectangle lights are defined in world coordinates as footpoint and two vectors spanning a parallelogram. + // All in world coordinates with no scaling. + float3 position; + float3 vecU; + float3 vecV; + float3 normal; + float area; + float3 emission; + + // Manual padding to float4 alignment goes here. + float unused0; + float unused1; + float unused2; +}; + +struct LightSample +{ + float3 position; + float distance; + float3 direction; + float3 emission; + float pdf; +}; + +#endif // LIGHT_DEFINITION_H diff --git a/apps/bench_shared_offscreen/shaders/light_sample.cu b/apps/bench_shared_offscreen/shaders/light_sample.cu new file mode 100644 index 00000000..1ec877e9 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/light_sample.cu @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" + +#include "shader_common.h" + +extern "C" __constant__ SystemData sysData; + + +__forceinline__ __device__ void unitSquareToSphere(const float u, const float v, float3& p, float& pdf) +{ + p.z = 1.0f - 2.0f * u; + float r = 1.0f - p.z * p.z; + r = (0.0f < r) ? sqrtf(r) : 0.0f; + + const float phi = v * 2.0f * M_PIf; + p.x = r * cosf(phi); + p.y = r * sinf(phi); + + pdf = 0.25f * M_1_PIf; // == 1.0f / (4.0f * M_PIf) +} + +// Note that all light sampling routines return lightSample.direction and lightSample.distance in world space! + +extern "C" __device__ LightSample __direct_callable__light_env_constant(const LightDefinition& light, const float3 point, const float2 sample) +{ + LightSample lightSample; + + unitSquareToSphere(sample.x, sample.y, lightSample.direction, lightSample.pdf); + + // Environment lights do not set the light sample position! + lightSample.distance = RT_DEFAULT_MAX; // Environment light. + + // Explicit light sample. White scaled by inverse probabilty to hit this light. + // FIXME Could use the sysData.lightDefinitions[0].emission for different colors. + lightSample.emission = make_float3(sysData.numLights); + + return lightSample; +} + +extern "C" __device__ LightSample __direct_callable__light_env_sphere(const LightDefinition& light, const float3 point, const float2 sample) +{ + LightSample lightSample; + + // Importance-sample the spherical environment light direction. + + // Note that the marginal CDF is one bigger than the texture height. As index this is the 1.0f at the end of the CDF. + const unsigned int sizeV = sysData.envHeight; + + unsigned int ilo = 0; // Use this for full spherical lighting. (This matches the result of indirect environment lighting.) + unsigned int ihi = sizeV; // Index on the last entry containing 1.0f. Can never be reached with the sample in the range [0.0f, 1.0f). + + const float* cdfV = sysData.envCDF_V; + + // Binary search the row index to look up. + while (ilo != ihi - 1) // When a pair of limits have been found, the lower index indicates the cell to use. + { + const unsigned int i = (ilo + ihi) >> 1; + if (sample.y < cdfV[i]) // If the cdf is greater than the sample, use that as new higher limit. + { + ihi = i; + } + else // If the sample is greater than or equal to the CDF value, use that as new lower limit. + { + ilo = i; + } + } + + const unsigned int vIdx = ilo; // This is the row we found. + + // Note that the horizontal CDF is one bigger than the texture width. As index this is the 1.0f at the end of the CDF. + const unsigned int sizeU = sysData.envWidth; // Note that the horizontal CDFs are one bigger than the texture width. + + // Binary search the column index to look up. + ilo = 0; + ihi = sizeU; // Index on the last entry containing 1.0f. Can never be reached with the sample in the range [0.0f, 1.0f). + + // Pointer to the indexY row! + const float* cdfU = &sysData.envCDF_U[vIdx * (sizeU + 1)]; // Horizontal CDF is one bigger then the texture width! + + while (ilo != ihi - 1) // When a pair of limits have been found, the lower index indicates the cell to use. + { + const unsigned int i = (ilo + ihi) >> 1; + if (sample.x < cdfU[i]) // If the CDF value is greater than the sample, use that as new higher limit. + { + ihi = i; + } + else // If the sample is greater than or equal to the CDF value, use that as new lower limit. + { + ilo = i; + } + } + + const unsigned int uIdx = ilo; // The column result. + + // Continuous sampling of the CDF. + const float cdfLowerU = cdfU[uIdx]; + const float cdfUpperU = cdfU[uIdx + 1]; + const float du = (sample.x - cdfLowerU) / (cdfUpperU - cdfLowerU); + + const float cdfLowerV = cdfV[vIdx]; + const float cdfUpperV = cdfV[vIdx + 1]; + const float dv = (sample.y - cdfLowerV) / (cdfUpperV - cdfLowerV); + + // Texture lookup coordinates. + const float u = (float(uIdx) + du) / float(sizeU); + const float v = (float(vIdx) + dv) / float(sizeV); + + // Light sample direction vector polar coordinates. This is where the environment rotation happens! + // DAR FIXME Use a light.matrix to rotate the resulting vector instead. + const float phi = (u - sysData.envRotation) * 2.0f * M_PIf; + const float theta = v * M_PIf; // theta == 0.0f is south pole, theta == M_PIf is north pole. + + const float sinTheta = sinf(theta); + // The miss program places the 1->0 seam at the positive z-axis and looks from the inside. + lightSample.direction = make_float3(-sinf(phi) * sinTheta, // Starting on positive z-axis going around clockwise (to negative x-axis). + -cosf(theta), // From south pole to north pole. + cosf(phi) * sinTheta); // Starting on positive z-axis. + + // Note that environment lights do not set the light sample position! + lightSample.distance = RT_DEFAULT_MAX; // Environment light. + + const float3 emission = make_float3(tex2D(sysData.envTexture, u, v)); + // Explicit light sample. The returned emission must be scaled by the inverse probability to select this light. + lightSample.emission = emission * sysData.numLights; + // For simplicity we pretend that we perfectly importance-sampled the actual texture-filtered environment map + // and not the Gaussian-smoothed one used to actually generate the CDFs and uniform sampling in the texel. + lightSample.pdf = intensity(emission) / sysData.envIntegral; + + return lightSample; +} + +extern "C" __device__ LightSample __direct_callable__light_parallelogram(const LightDefinition& light, const float3 point, const float2 sample) +{ + LightSample lightSample; + + lightSample.pdf = 0.0f; // Default return, invalid light sample (backface, edge on, or too near to the surface) + + lightSample.position = light.position + light.vecU * sample.x + light.vecV * sample.y; // The light sample position in world coordinates. + lightSample.direction = lightSample.position - point; // Sample direction from surface point to light sample position. + lightSample.distance = length(lightSample.direction); + if (DENOMINATOR_EPSILON < lightSample.distance) + { + lightSample.direction /= lightSample.distance; // Normalized direction to light. + + const float cosTheta = dot(-lightSample.direction, light.normal); + if (DENOMINATOR_EPSILON < cosTheta) // Only emit light on the front side. + { + // Explicit light sample, must scale the emission by inverse probabilty to hit this light. + lightSample.emission = light.emission * float(sysData.numLights); + lightSample.pdf = (lightSample.distance * lightSample.distance) / (light.area * cosTheta); // Solid angle pdf. Assumes light.area != 0.0f. + } + } + + return lightSample; +} diff --git a/apps/bench_shared_offscreen/shaders/material_definition.h b/apps/bench_shared_offscreen/shaders/material_definition.h new file mode 100644 index 00000000..35eaf53f --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/material_definition.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef MATERIAL_DEFINITION_H +#define MATERIAL_DEFINITION_H + +#include "function_indices.h" + +// Just some hardcoded material parameter system which allows to show a few fundamental BSDFs. +struct MaterialDefinition +{ + // 8 byte alignment. + cudaTextureObject_t textureAlbedo; // Modulates albedo when valid. + cudaTextureObject_t textureCutout; // RGB intensity defines surface cutout when valid, normally used with thin-walled. + + float2 roughness; // Anisotropic roughness values. + + // 4 byte alignment. + FunctionIndex indexBSDF; // BSDF index to use in the closest hit program. + + float3 albedo; // Albedo, tint, throughput change for specular surfaces. Pick your meaning. + float3 absorption; // Absorption coefficient. + float ior; // Index of refraction. + unsigned int flags; // Thin-walled on/off + + // Manual padding to 16-byte alignment goes here. + int pad0; + //int pad1; + //int pad3; + //int pad4; +}; + +#endif // MATERIAL_DEFINITION_H diff --git a/apps/bench_shared_offscreen/shaders/miss.cu b/apps/bench_shared_offscreen/shaders/miss.cu new file mode 100644 index 00000000..ec912e49 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/miss.cu @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "per_ray_data.h" +#include "light_definition.h" +#include "shader_common.h" +#include "system_data.h" + +extern "C" __constant__ SystemData sysData; + +// Not actually a light. Never appears inside the sysLightDefinitions. +extern "C" __global__ void __miss__env_null() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + thePrd->radiance = make_float3(0.0f); + thePrd->flags |= FLAG_TERMINATE; +} + + +extern "C" __global__ void __miss__env_constant() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + +#if USE_NEXT_EVENT_ESTIMATION + // If the last surface intersection was a diffuse which was directly lit with multiple importance sampling, + // then calculate light emission with multiple importance sampling as well. + const float weightMIS = (thePrd->flags & FLAG_DIFFUSE) ? powerHeuristic(thePrd->pdf, 0.25f * M_1_PIf) : 1.0f; + thePrd->radiance = make_float3(weightMIS); // Constant white emission multiplied by MIS weight. +#else + thePrd->radiance = make_float3(1.0f); // Constant white emission. +#endif + + thePrd->flags |= FLAG_TERMINATE; +} + + +extern "C" __global__ void __miss__env_sphere() +{ + // Get the current rtPayload pointer from the unsigned int payload registers p0 and p1. + PerRayData* thePrd = mergePointer(optixGetPayload_0(), optixGetPayload_1()); + + const float3 R = thePrd->wi; // theRay.direction; + // The seam u == 0.0 == 1.0 is in positive z-axis direction. + // Compensate for the environment rotation done inside the direct lighting. + const float u = (atan2f(R.x, -R.z) + M_PIf) * 0.5f * M_1_PIf + sysData.envRotation; // DAR FIXME Use a light.matrix to rotate the environment. + const float theta = acosf(-R.y); // theta == 0.0f is south pole, theta == M_PIf is north pole. + const float v = theta * M_1_PIf; // Texture is with origin at lower left, v == 0.0f is south pole. + + const float3 emission = make_float3(tex2D(sysData.envTexture, u, v)); + +#if USE_NEXT_EVENT_ESTIMATION + float weightMIS = 1.0f; + // If the last surface intersection was a diffuse event which was directly lit with multiple importance sampling, + // then calculate light emission with multiple importance sampling for this implicit light hit as well. + if (thePrd->flags & FLAG_DIFFUSE) + { + // For simplicity we pretend that we perfectly importance-sampled the actual texture-filtered environment map + // and not the Gaussian smoothed one used to actually generate the CDFs. + const float pdfLight = intensity(emission) / sysData.envIntegral; + weightMIS = powerHeuristic(thePrd->pdf, pdfLight); + } + thePrd->radiance = emission * weightMIS; +#else + thePrd->radiance = emission; +#endif + + thePrd->flags |= FLAG_TERMINATE; +} diff --git a/apps/bench_shared_offscreen/shaders/per_ray_data.h b/apps/bench_shared_offscreen/shaders/per_ray_data.h new file mode 100644 index 00000000..57402982 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/per_ray_data.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef PER_RAY_DATA_H +#define PER_RAY_DATA_H + +#include "config.h" + +#include + + +#define MATERIAL_STACK_EMPTY -1 +#define MATERIAL_STACK_FIRST 0 +#define MATERIAL_STACK_LAST 3 +#define MATERIAL_STACK_SIZE 4 + +// Set when reaching a closesthit program. Unused in this demo +#define FLAG_HIT 0x00000001 +// Set when reaching the __anyhit__shadow program. Indicates visibility test failed. +#define FLAG_SHADOW 0x00000002 + +// Set by BSDFs which support direct lighting. Not set means specular interaction. Cleared in the closesthit program. +// Used to decide when to do direct lighting and multuiple importance sampling on implicit light hits. +#define FLAG_DIFFUSE 0x00000004 + +// Set if (0.0f <= wo_dot_ng), means looking onto the front face. (Edge-on is explicitly handled as frontface for the material stack.) +#define FLAG_FRONTFACE 0x00000010 +// Pass down material.flags through to the BSDFs. +#define FLAG_THINWALLED 0x00000020 + +// FLAG_TRANSMISSION is set if there is a transmission. (Can't happen when FLAG_THINWALLED is set.) +#define FLAG_TRANSMISSION 0x00000100 + +// Set if the material stack is not empty. +#define FLAG_VOLUME 0x00001000 + +// Highest bit set means terminate path. +#define FLAG_TERMINATE 0x80000000 + +// Keep flags active in a path segment which need to be tracked along the path. +// In this case only the last surface interaction is kept. +// It's needed to track the last bounce's diffuse state in case a ray hits a light implicitly for multiple importance sampling. +// FLAG_DIFFUSE is reset in the closesthit program. +#define FLAG_CLEAR_MASK FLAG_DIFFUSE + +// Currently only containing some vertex attributes in world coordinates. +struct State +{ + float3 normalGeo; + float3 tangent; + float3 normal; + float3 texcoord; + float3 albedo; // PERF Added albedo to the state to allow modulation with an optional texture once before BSDF sampling and evaluation. +}; + +// Note that the fields are ordered by CUDA alignment restrictions. +struct PerRayData +{ + // 16-byte alignment + float4 absorption_ior; // The absorption coefficient and IOR of the currently hit material. + + // 8-byte alignment + float2 ior; // .x = IOR the ray currently is inside, .y = the IOR of the surrounding volume. The IOR of the current material is in absorption_ior.w! + + // 4-byte alignment + float3 pos; // Current surface hit point or volume sample point, in world space + float distance; // Distance from the ray origin to the current position, in world space. Needed for absorption of nested materials. + + float3 wo; // Outgoing direction, to observer, in world space. + float3 wi; // Incoming direction, to light, in world space. + + float3 radiance; // Radiance along the current path segment. + + unsigned int flags; // Bitfield with flags. See FLAG_* defines above for its contents. + + float3 f_over_pdf; // BSDF sample throughput, pre-multiplied f_over_pdf = bsdf.f * fabsf(dot(wi, ns) / bsdf.pdf; + float pdf; // The last BSDF sample's pdf, tracked for multiple importance sampling. + + float3 sigma_t; // The current volume's extinction coefficient. (Only absorption in this implementation.) + float opacity; // Cutout opacity result. + + unsigned int seed; // Random number generator input. +}; + + +// Alias the PerRayData pointer and an uint2 for the payload split and merge operations. This generates only move instructions. +typedef union +{ + PerRayData* ptr; + uint2 dat; +} Payload; + +__forceinline__ __device__ uint2 splitPointer(PerRayData* ptr) +{ + Payload payload; + + payload.ptr = ptr; + + return payload.dat; +} + +__forceinline__ __device__ PerRayData* mergePointer(unsigned int p0, unsigned int p1) +{ + Payload payload; + + payload.dat.x = p0; + payload.dat.y = p1; + + return payload.ptr; +} + +#endif // PER_RAY_DATA_H diff --git a/apps/bench_shared_offscreen/shaders/random_number_generators.h b/apps/bench_shared_offscreen/shaders/random_number_generators.h new file mode 100644 index 00000000..0670ff67 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/random_number_generators.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef RANDOM_NUMBER_GENERATORS_H +#define RANDOM_NUMBER_GENERATORS_H + +#include "config.h" + + +// Tiny Encryption Algorithm (TEA) to calculate a the seed per launch index and iteration. +// This results in a ton of integer instructions! Use the smallest N necessary. +template +__forceinline__ __device__ unsigned int tea(const unsigned int val0, const unsigned int val1) +{ + unsigned int v0 = val0; + unsigned int v1 = val1; + unsigned int s0 = 0; + + for (unsigned int n = 0; n < N; ++n) + { + s0 += 0x9e3779b9; + v0 += ((v1 << 4) + 0xA341316C) ^ (v1 + s0) ^ ((v1 >> 5) + 0xC8013EA4); + v1 += ((v0 << 4) + 0xAD90777D) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7E95761E); + } + return v0; +} + +// Just do one LCG step. The new random unsigned int number is in the referenced argument. +__forceinline__ __device__ void lcg(unsigned int& previous) +{ + previous = previous * 1664525u + 1013904223u; +} + + +// Return a random sample in the range [0, 1) with a simple Linear Congruential Generator. +__forceinline__ __device__ float rng(unsigned int& previous) +{ + previous = previous * 1664525u + 1013904223u; + + return float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + // return float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits +} + +// Convenience function to generate a 2D unit square sample. +__forceinline__ __device__ float2 rng2(unsigned int& previous) +{ + float2 s; + + previous = previous * 1664525u + 1013904223u; + s.x = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + //s.x = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + previous = previous * 1664525u + 1013904223u; + s.y = float(previous & 0x00FFFFFF) / float(0x01000000u); // Use the lower 24 bits. + //s.y = float(previous >> 8) / float(0x01000000u); // Use the upper 24 bits + + return s; +} + +#endif // RANDOM_NUMBER_GENERATORS_H diff --git a/apps/bench_shared_offscreen/shaders/raygeneration.cu b/apps/bench_shared_offscreen/shaders/raygeneration.cu new file mode 100644 index 00000000..6ebca83e --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/raygeneration.cu @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "config.h" + +#include + +#include "system_data.h" +#include "per_ray_data.h" +#include "shader_common.h" +#include "random_number_generators.h" + + +extern "C" __constant__ SystemData sysData; + + +__forceinline__ __device__ float3 integrator(PerRayData& prd) +{ + // This renderer supports nested volumes. Four levels is plenty enough for most cases. + // The absorption coefficient and IOR of the volume the ray is currently inside. + float4 absorptionStack[MATERIAL_STACK_SIZE]; // .xyz == absorptionCoefficient (sigma_a), .w == index of refraction + + int stackIdx = MATERIAL_STACK_EMPTY; // Start with empty nested materials stack. + + // Russian Roulette path termination after a specified number of bounces needs the current depth. + int depth = 0; // Path segment index. Primary ray is 0. + + float3 radiance = make_float3(0.0f); // Start with black. + float3 throughput = make_float3(1.0f); // The throughput for the next radiance, starts with 1.0f. + + // Assumes that the primary ray starts in vacuum. + prd.absorption_ior = make_float4(0.0f, 0.0f, 0.0f, 1.0f); // No absorption and IOR == 1.0f + prd.sigma_t = make_float3(0.0f); // No extinction. + prd.flags = 0; + + while (depth < sysData.pathLengths.y) + { + prd.wo = -prd.wi; // Direction to observer. + prd.ior = make_float2(1.0f); // Reset the volume IORs. + prd.distance = RT_DEFAULT_MAX; // Shoot the next ray with maximum length. + prd.flags &= FLAG_CLEAR_MASK; // Clear all non-persistent flags. In this demo only the last diffuse surface interaction stays. + + // Handle volume absorption of nested materials. + if (MATERIAL_STACK_FIRST <= stackIdx) // Inside a volume? + { + prd.flags |= FLAG_VOLUME; // Indicate that we're inside a volume. => At least absorption calculation needs to happen. + prd.sigma_t = make_float3(absorptionStack[stackIdx]); // There is only volume absorption in this demo, no volume scattering. + prd.ior.x = absorptionStack[stackIdx].w; // The IOR of the volume we're inside. Needed for eta calculations in transparent materials. + if (MATERIAL_STACK_FIRST <= stackIdx - 1) + { + prd.ior.y = absorptionStack[stackIdx - 1].w; // The IOR of the surrounding volume. Needed when potentially leaving a volume to calculate eta in transparent materials. + } + } + + // Put payload pointer into two unsigned integers. Actually const, but that's not what optixTrace() expects. + uint2 payload = splitPointer(&prd); + + // Note that the primary rays (or volume scattering miss cases) wouldn't normally offset the ray t_min by sysSceneEpsilon. Keep it simple here. + optixTrace(sysData.topObject, + prd.pos, prd.wi, // origin, direction + sysData.sceneEpsilon, prd.distance, 0.0f, // tmin, tmax, time + OptixVisibilityMask(0xFF), OPTIX_RAY_FLAG_NONE, + RAYTYPE_RADIANCE, NUM_RAYTYPES, RAYTYPE_RADIANCE, + payload.x, payload.y); + + // This renderer supports nested volumes. + if (prd.flags & FLAG_VOLUME) // We're inside a volume? + { + // The transmittance along the current path segment inside a volume needs to attenuate + // the ray throughput with the extinction before it modulates the radiance of the hitpoint. + throughput *= expf(-prd.distance * prd.sigma_t); + } + + radiance += throughput * prd.radiance; + + // Path termination by miss shader or sample() routines. + // If terminate is true, f_over_pdf and pdf might be undefined. + if ((prd.flags & FLAG_TERMINATE) || prd.pdf <= 0.0f || isNull(prd.f_over_pdf)) + { + break; + } + + // PERF f_over_pdf already contains the proper throughput adjustment for diffuse materials: f * (fabsf(dot(prd.wi, state.normal)) / prd.pdf); + throughput *= prd.f_over_pdf; + + // Unbiased Russian Roulette path termination. + if (sysData.pathLengths.x <= depth) // Start termination after a minimum number of bounces. + { + const float probability = fmaxf(throughput); // DEBUG Other options: // intensity(throughput); // fminf(0.5f, intensity(throughput)); + if (probability < rng(prd.seed)) // Paths with lower probability to continue are terminated earlier. + { + break; + } + throughput /= probability; // Path isn't terminated. Adjust the throughput so that the average is right again. + } + + // Adjust the material volume stack if the geometry is not thin-walled but a border between two volumes and + // the outgoing ray direction was a transmission. + if ((prd.flags & (FLAG_THINWALLED | FLAG_TRANSMISSION)) == FLAG_TRANSMISSION) + { + // Transmission. + if (prd.flags & FLAG_FRONTFACE) // Entered a new volume? + { + // Push the entered material's volume properties onto the volume stack. + //rtAssert((stackIdx < MATERIAL_STACK_LAST), 1); // Overflow? + stackIdx = min(stackIdx + 1, MATERIAL_STACK_LAST); + absorptionStack[stackIdx] = prd.absorption_ior; + } + else // Exited the current volume? + { + // Pop the top of stack material volume. + // This assert fires and is intended because I tuned the frontface checks so that there are more exits than enters at silhouettes. + //rtAssert((MATERIAL_STACK_EMPTY < stackIdx), 0); // Underflow? + stackIdx = max(stackIdx - 1, MATERIAL_STACK_EMPTY); + } + } + + ++depth; // Next path segment. + } + + return radiance; +} + + +__forceinline__ __device__ unsigned int distribute(const uint2 launchIndex) +{ + // First calculate block coordinates of this launch index. + // That is the launch index divided by the tile dimensions. (No operator>>() on vectors?) + const unsigned int xBlock = launchIndex.x >> sysData.tileShift.x; + const unsigned int yBlock = launchIndex.y >> sysData.tileShift.y; + + // Each device needs to start at a different column and each row should start with a different device. + const unsigned int xTile = xBlock * sysData.deviceCount + ((sysData.deviceIndex + yBlock) % sysData.deviceCount); + + // The horizontal pixel coordinate is: tile coordinate * tile width + launch index % tile width. + return xTile * sysData.tileSize.x + (launchIndex.x & (sysData.tileSize.x - 1)); // tileSize needs to be power-of-two for this modulo operation. +} + +// The sample position is calculated via a fixed rotated grid. +// This will result in perfectly stratified camera sample positions when all samples per pixel are rendered. +__forceinline__ __device__ float2 sampleRotatedGrid(const unsigned int iteration) +{ + const unsigned int samples = sysData.samplesSqrt; + if (samples == 1) + { + return make_float2(0.5f); + } + + const float invSamples = 1.0f / float(samples); + const float invSamplesMinusOne = 1.0f / float(samples - 1); // This is why samples == 1 needs a special case. + + // Convert the linear iteration index into a 2D grid. + const float x = float(iteration % samples); + const float y = float(iteration / samples); // % samples; // The modulo here is not necessary because iterationIndex < samplesSqrt^2. + + // Calculate the sub-pixel cell offset. Used to interpolate the rotated grid sample positions. + // Maximum shift away from the center is half a cell, minus half a cell divided by the number of samples in each dimension. + float delta = 0.5f * invSamples; + delta -= delta * invSamples; + + // Cell center + per row offset. + // Horizontal sample positions start to shift to the left in the bottom row. + // Odd sizes will have one sample exactly in the pixel center (0.5f, 0.5f). + const float u = (x + 0.5f) * invSamples + lerp(-delta, delta, y * invSamplesMinusOne); + + // Cell center + per column offset. + // Note that the shift is to the opposite direction here to get a rotated grid,. + // Vertical sample positions start to shift to the top in left column. + const float v = (y + 0.5f) * invSamples + lerp(delta, -delta, x * invSamplesMinusOne); + + return make_float2(u, v); +} + + +extern "C" __global__ void __raygen__path_tracer_local_copy() +{ +#if USE_TIME_VIEW + clock_t clockBegin = clock(); +#endif + + const uint2 theLaunchIndex = make_uint2(optixGetLaunchIndex()); + + unsigned int launchColumn = theLaunchIndex.x; + + if (1 < sysData.deviceCount) // Multi-GPU distribution required? + { + launchColumn = distribute(theLaunchIndex); // Calculate mapping from launch index to pixel index. + if (sysData.resolution.x <= launchColumn) // Check if the launchColumn is outside the resolution. + { + return; + } + } + + PerRayData prd; + + const uint2 theLaunchDim = make_uint2(optixGetLaunchDimensions()); // For multi-GPU tiling this is (resolution + deviceCount - 1) / deviceCount. + + // Initialize the random number generator seed from the linear pixel index and the iteration index. + prd.seed = tea<4>(theLaunchIndex.y * theLaunchDim.x + launchColumn, sysData.iterationIndex); // PERF This template really generates a lot of instructions. + + // Decoupling the pixel coordinates from the screen size will allow for partial rendering algorithms. + // Resolution is the actual full rendering resolution and for the single GPU strategy, theLaunchDim == resolution. + const float2 screen = make_float2(sysData.resolution); // == theLaunchDim for rendering strategy RS_SINGLE_GPU. + const float2 pixel = make_float2(launchColumn, theLaunchIndex.y); + const float2 sample = sampleRotatedGrid(sysData.iterationIndex); // or rng2(prd.seed); + + // Lens shaders + optixDirectCall(sysData.lensShader, screen, pixel, sample, prd.pos, prd.wi); + + float3 radiance = integrator(prd); + +#if USE_DEBUG_EXCEPTIONS + // DAR DEBUG Highlight numerical errors. + if (isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z)) + { + radiance = make_float3(1000000.0f, 0.0f, 0.0f); // super red + } + else if (isinf(radiance.x) || isinf(radiance.y) || isinf(radiance.z)) + { + radiance = make_float3(0.0f, 1000000.0f, 0.0f); // super green + } + else if (radiance.x < 0.0f || radiance.y < 0.0f || radiance.z < 0.0f) + { + radiance = make_float3(0.0f, 0.0f, 1000000.0f); // super blue + } +#else + // NaN values will never go away. Filter them out before they can arrive in the output buffer. + // This only has an effect if the debug coloring above is off! + if (!(isnan(radiance.x) || isnan(radiance.y) || isnan(radiance.z))) +#endif + { + // The texelBuffer is a CUdeviceptr to allow different formats. + float4* buffer = reinterpret_cast(sysData.texelBuffer); // This is a per device launch sized buffer in this renderer strategy. + + // This renderer write the results into individual launch sized local buffers and composites them in a separate native CUDA kernel. + const unsigned int index = theLaunchIndex.y * theLaunchDim.x + theLaunchIndex.x; + +#if USE_TIME_VIEW + clock_t clockEnd = clock(); + const float alpha = (clockEnd - clockBegin) * sysData.clockScale; + + float4 result = make_float4(radiance, alpha); + + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + result = lerp(dst, result, 1.0f / float(sysData.iterationIndex + 1)); // Accumulate the alpha as well. + } + buffer[index] = result; +#else + if (0 < sysData.iterationIndex) + { + const float4 dst = buffer[index]; // RGBA32F + radiance = lerp(make_float3(dst), radiance, 1.0f / float(sysData.iterationIndex + 1)); // Only accumulate the radiance, alpha stays 1.0f. + } + buffer[index] = make_float4(radiance, 1.0f); +#endif + } +} + diff --git a/apps/bench_shared_offscreen/shaders/shader_common.h b/apps/bench_shared_offscreen/shaders/shader_common.h new file mode 100644 index 00000000..96f607cd --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/shader_common.h @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SHADER_COMMON_H +#define SHADER_COMMON_H + +#include "config.h" + +#include "vector_math.h" + + +/** +* Calculates refraction direction +* r : refraction vector +* i : incident vector +* n : surface normal +* ior : index of refraction ( n2 / n1 ) +* returns false in case of total internal reflection, in that case r is initialized to (0,0,0). +*/ +__forceinline__ __host__ __device__ bool refract(float3& r, const float3& i, const float3& n, const float ior) +{ + float3 nn = n; + float negNdotV = dot(i, nn); + float eta; + + if (negNdotV > 0.0f) + { + eta = ior; + nn = -n; + negNdotV = -negNdotV; + } + else + { + eta = 1.f / ior; + } + + const float k = 1.f - eta * eta * (1.f - negNdotV * negNdotV); + + if (k < 0.0f) + { + // Initialize this value, so that r always leaves this function initialized. + r = make_float3(0.f); + return false; + } + else + { + r = normalize(eta * i - (eta * negNdotV + sqrtf(k)) * nn); + return true; + } +} + + + +// Tangent-Bitangent-Normal orthonormal space. +struct TBN +{ + // Default constructor to be able to include it into other structures when needed. + __forceinline__ __host__ __device__ TBN() + { + } + + __forceinline__ __host__ __device__ TBN(const float3& n) + : normal(n) + { + if (fabsf(normal.z) < fabsf(normal.x)) + { + tangent.x = normal.z; + tangent.y = 0.0f; + tangent.z = -normal.x; + } + else + { + tangent.x = 0.0f; + tangent.y = normal.z; + tangent.z = -normal.y; + } + tangent = normalize(tangent); + bitangent = cross(normal, tangent); + } + + // Constructor for cases where tangent, bitangent, and normal are given as ortho-normal basis. + __forceinline__ __host__ __device__ TBN(const float3& t, const float3& b, const float3& n) + : tangent(t) + , bitangent(b) + , normal(n) + { + } + + // Normal is kept, tangent and bitangent are calculated. + // Normal must be normalized. + // Must not be used with degenerated vectors! + __forceinline__ __host__ __device__ TBN(const float3& tangent_reference, const float3& n) + : normal(n) + { + bitangent = normalize(cross(normal, tangent_reference)); + tangent = cross(bitangent, normal); + } + + __forceinline__ __host__ __device__ void negate() + { + tangent = -tangent; + bitangent = -bitangent; + normal = -normal; + } + + __forceinline__ __host__ __device__ float3 transformToLocal(const float3& p) const + { + return make_float3(dot(p, tangent), + dot(p, bitangent), + dot(p, normal)); + } + + __forceinline__ __host__ __device__ float3 transformToWorld(const float3& p) const + { + return p.x * tangent + p.y * bitangent + p.z * normal; + } + + float3 tangent; + float3 bitangent; + float3 normal; +}; + +// FBN (Fiber, Bitangent, Normal) +// Special version of TBN (Tangent, Bitangent, Normal) ortho-normal basis generation for fiber geometry. +// The difference is that the TBN keeps the normal intact where the FBN keeps the tangent intact, which is the fiber orientation! +struct FBN +{ + // Default constructor to be able to include it into State. + __forceinline__ __host__ __device__ FBN() + { + } + + // Do not use these single argument constructors for anisotropic materials! + // There will be discontinuities on round objects when the FBN orientation flips. + // Tangent is kept, bitangent and normal are calculated. + // Tangent t must be normalized. + __forceinline__ __host__ __device__ FBN(const float3& t) // t == fiber orientation + : tangent(t) + { + if (fabsf(tangent.z) < fabsf(tangent.x)) + { + bitangent.x = -tangent.y; + bitangent.y = tangent.x; + bitangent.z = 0.0f; + } + else + { + bitangent.x = 0.0f; + bitangent.y = -tangent.z; + bitangent.z = tangent.y; + } + + bitangent = normalize(bitangent); + normal = cross(tangent, bitangent); + } + + // Constructor for cases where tangent, bitangent, and normal are given as ortho-normal basis. + __forceinline__ __host__ __device__ FBN(const float3& t, const float3& b, const float3& n) // t == fiber orientation + : tangent(t) + , bitangent(b) + , normal(n) + { + } + + // Tangent is kept, bitangent and normal are calculated. + // Tangent t must be normalized. + // Must not be used with degenerated vectors! + __forceinline__ __host__ __device__ FBN(const float3& t, const float3& n) + : tangent(t) + { + bitangent = normalize(cross(n, tangent)); + normal = cross(tangent, bitangent); + } + + __forceinline__ __host__ __device__ void negate() + { + tangent = -tangent; + bitangent = -bitangent; + normal = -normal; + } + + __forceinline__ __host__ __device__ float3 transformToLocal(const float3& p) const + { + return make_float3(dot(p, tangent), + dot(p, bitangent), + dot(p, normal)); + } + + __forceinline__ __host__ __device__ float3 transformToWorld(const float3& p) const + { + return p.x * tangent + p.y * bitangent + p.z * normal; + } + + float3 tangent; + float3 bitangent; + float3 normal; +}; + + + +__forceinline__ __host__ __device__ float luminance(const float3& rgb) +{ + const float3 ntsc_luminance = { 0.30f, 0.59f, 0.11f }; + return dot(rgb, ntsc_luminance); +} + +__forceinline__ __host__ __device__ float intensity(const float3& rgb) +{ + return (rgb.x + rgb.y + rgb.z) * 0.3333333333f; +} + +__forceinline__ __host__ __device__ float cube(const float x) +{ + return x * x * x; +} + +__forceinline__ __host__ __device__ bool isNull(const float3& v) +{ + return (v.x == 0.0f && v.y == 0.0f && v.z == 0.0f); +} + +__forceinline__ __host__ __device__ bool isNotNull(const float3& v) +{ + return (v.x != 0.0f || v.y != 0.0f || v.z != 0.0f); +} + +// Used for Multiple Importance Sampling. +__forceinline__ __host__ __device__ float powerHeuristic(const float a, const float b) +{ + const float t = a * a; + return t / (t + b * b); +} + +__forceinline__ __host__ __device__ float balanceHeuristic(const float a, const float b) +{ + return a / (a + b); +} + + +#endif // SHADER_COMMON_H diff --git a/apps/bench_shared_offscreen/shaders/system_data.h b/apps/bench_shared_offscreen/shaders/system_data.h new file mode 100644 index 00000000..91b47a3d --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/system_data.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef SYSTEM_DATA_H +#define SYSTEM_DATA_H + +#include "config.h" + +#include "camera_definition.h" +#include "light_definition.h" +#include "material_definition.h" +#include "vertex_attributes.h" + + +struct SystemData +{ + // 16 byte alignment + //int4 rect; // DAR Unused, not implementing a tile renderer. + + // 8 byte alignment + OptixTraversableHandle topObject; + + // The accumulated linear color space output buffer. + // This is always sized to the resolution, not always matching the launch dimension. + // Using a CUdeviceptr here to allow for different buffer formats without too many casts. + CUdeviceptr outputBuffer; + // These buffers are used differently among the rendering strategies. + CUdeviceptr tileBuffer; + CUdeviceptr texelBuffer; + + CameraDefinition* cameraDefinitions; // Currently only one camera in the array. (Allows camera motion blur in the future.) + LightDefinition* lightDefinitions; + MaterialDefinition* materialDefinitions; + + cudaTextureObject_t envTexture; // DAR FIXME Move these into the LightDefinition. + + float* envCDF_U; // 2D, size (envWidth + 1) * envHeight + float* envCDF_V; // 1D, size (envHeight + 1) + + int2 resolution; // The actual rendering resolution. Independent from the launch dimensions for some rendering strategies. + int2 tileSize; // Example: make_int2(8, 4) for 8x4 tiles. Must be a power of two to make the division a right-shift. + int2 tileShift; // Example: make_int2(3, 2) for the integer division by tile size. That actually makes the tileSize redundant. + int2 pathLengths; // .x = min path length before Russian Roulette kicks in, .y = maximum path length + + // 4 byte alignment + int deviceCount; // Number of devices doing the rendering. + int deviceIndex; // Device index to be able to distinguish the individual devices in a multi-GPU environment. + //int distribution; // Indicate if the tile distribution inside the ray generation program should be used. // FIXME Put booleans into bitfield if there are more. Redundant, the deviceCount is what really matters. + int iterationIndex; + int samplesSqrt; + + float sceneEpsilon; + float clockScale; // Only used with USE_TIME_VIEW. + + int lensShader; // Camera type. + + int numCameras; + int numMaterials; + int numLights; + + unsigned int envWidth; // The original size of the environment texture. + unsigned int envHeight; + float envIntegral; + float envRotation; +}; + + +// SBT Record data for the hit group. This is used on the device to calculate attributes! +struct GeometryInstanceData +{ + // Using CUdeviceptr here to be able to handle different attribute and index formats for Triangles, Hair, and Curves. + CUdeviceptr attributes; + CUdeviceptr indices; + int idMaterial; + int idLight; // Negative means not a light. +}; + +#endif // SYSTEM_DATA_H diff --git a/apps/bench_shared_offscreen/shaders/vector_math.h b/apps/bench_shared_offscreen/shaders/vector_math.h new file mode 100644 index 00000000..3ea0b339 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/vector_math.h @@ -0,0 +1,2958 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef VECTOR_MATH_H +#define VECTOR_MATH_H + +#include "config.h" + +#include + + +#if defined(__CUDACC__) || defined(__CUDABE__) +#define VECTOR_MATH_API __forceinline__ __host__ __device__ +#else +#include +#define VECTOR_MATH_API inline +#endif + + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288419716939937510 +#endif +#ifndef M_Ef +#define M_Ef 2.71828182845904523536f +#endif +#ifndef M_LOG2Ef +#define M_LOG2Ef 1.44269504088896340736f +#endif +#ifndef M_LOG10Ef +#define M_LOG10Ef 0.434294481903251827651f +#endif +#ifndef M_LN2f +#define M_LN2f 0.693147180559945309417f +#endif +#ifndef M_LN10f +#define M_LN10f 2.30258509299404568402f +#endif +#ifndef M_PIf +#define M_PIf 3.14159265358979323846f +#endif +#ifndef M_PI_2f +#define M_PI_2f 1.57079632679489661923f +#endif +#ifndef M_PI_4f +#define M_PI_4f 0.785398163397448309616f +#endif +#ifndef M_1_PIf +#define M_1_PIf 0.318309886183790671538f +#endif +#ifndef M_2_PIf +#define M_2_PIf 0.636619772367581343076f +#endif +#ifndef M_2_SQRTPIf +#define M_2_SQRTPIf 1.12837916709551257390f +#endif +#ifndef M_SQRT2f +#define M_SQRT2f 1.41421356237309504880f +#endif +#ifndef M_SQRT1_2f +#define M_SQRT1_2f 0.707106781186547524401f +#endif + +#if !defined(__CUDACC__) + +VECTOR_MATH_API int max(int a, int b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API int min(int a, int b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API long long max(long long a, long long b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API long long min(long long a, long long b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API unsigned int max(unsigned int a, unsigned int b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API unsigned int min(unsigned int a, unsigned int b) +{ + return (a < b) ? a : b; +} + +VECTOR_MATH_API unsigned long long max(unsigned long long a, unsigned long long b) +{ + return (a > b) ? a : b; +} + +VECTOR_MATH_API unsigned long long min(unsigned long long a, unsigned long long b) +{ + return (a < b) ? a : b; +} + +#endif + +/** lerp */ +VECTOR_MATH_API float lerp(const float a, const float b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float bilerp(const float x00, const float x10, const float x01, const float x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp */ +VECTOR_MATH_API float clamp(const float f, const float a, const float b) +{ + return fmaxf(a, fminf(f, b)); +} + + +/* float2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float2 make_float2(const float s) +{ + return make_float2(s, s); +} +VECTOR_MATH_API float2 make_float2(const int2& a) +{ + return make_float2(float(a.x), float(a.y)); +} +VECTOR_MATH_API float2 make_float2(const uint2& a) +{ + return make_float2(float(a.x), float(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float2 operator-(const float2& a) +{ + return make_float2(-a.x, -a.y); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float2 fminf(const float2& a, const float2& b) +{ + return make_float2(fminf(a.x, b.x), fminf(a.y, b.y)); +} +VECTOR_MATH_API float fminf(const float2& a) +{ + return fminf(a.x, a.y); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float2 fmaxf(const float2& a, const float2& b) +{ + return make_float2(fmaxf(a.x, b.x), fmaxf(a.y, b.y)); +} +VECTOR_MATH_API float fmaxf(const float2& a) +{ + return fmaxf(a.x, a.y); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float2 operator+(const float2& a, const float2& b) +{ + return make_float2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API float2 operator+(const float2& a, const float b) +{ + return make_float2(a.x + b, a.y + b); +} +VECTOR_MATH_API float2 operator+(const float a, const float2& b) +{ + return make_float2(a + b.x, a + b.y); +} +VECTOR_MATH_API void operator+=(float2& a, const float2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float2 operator-(const float2& a, const float2& b) +{ + return make_float2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API float2 operator-(const float2& a, const float b) +{ + return make_float2(a.x - b, a.y - b); +} +VECTOR_MATH_API float2 operator-(const float a, const float2& b) +{ + return make_float2(a - b.x, a - b.y); +} +VECTOR_MATH_API void operator-=(float2& a, const float2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float2 operator*(const float2& a, const float2& b) +{ + return make_float2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API float2 operator*(const float2& a, const float s) +{ + return make_float2(a.x * s, a.y * s); +} +VECTOR_MATH_API float2 operator*(const float s, const float2& a) +{ + return make_float2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(float2& a, const float2& s) +{ + a.x *= s.x; + a.y *= s.y; +} +VECTOR_MATH_API void operator*=(float2& a, const float s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float2 operator/(const float2& a, const float2& b) +{ + return make_float2(a.x / b.x, a.y / b.y); +} +VECTOR_MATH_API float2 operator/(const float2& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float2 operator/(const float s, const float2& a) +{ + return make_float2(s / a.x, s / a.y); +} +VECTOR_MATH_API void operator/=(float2& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float2 lerp(const float2& a, const float2& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float2 bilerp(const float2& x00, const float2& x10, const float2& x01, const float2& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float2 clamp(const float2& v, const float a, const float b) +{ + return make_float2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API float2 clamp(const float2& v, const float2& a, const float2& b) +{ + return make_float2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float2& a, const float2& b) +{ + return a.x * b.x + a.y * b.y; +} + +/** length */ +VECTOR_MATH_API float length(const float2& v) +{ + return sqrtf(dot(v, v)); +} + +/** normalize */ +VECTOR_MATH_API float2 normalize(const float2& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float2 floor(const float2& v) +{ + return make_float2(::floorf(v.x), ::floorf(v.y)); +} + +/** reflect */ +VECTOR_MATH_API float2 reflect(const float2& i, const float2& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL */ +VECTOR_MATH_API float2 faceforward(const float2& n, const float2& i, const float2& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float2 expf(const float2& v) +{ + return make_float2(::expf(v.x), ::expf(v.y)); +} + +/** pow **/ +VECTOR_MATH_API float2 powf(const float2& v, const float e) +{ + return make_float2(::powf(v.x, e), ::powf(v.y, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float2 sqrtf(const float2& v) +{ + return make_float2(::sqrtf(v.x), ::sqrtf(v.y)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float2& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float2& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + + +/* float3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float3 make_float3(const float s) +{ + return make_float3(s, s, s); +} +VECTOR_MATH_API float3 make_float3(const float2& a) +{ + return make_float3(a.x, a.y, 0.0f); +} +VECTOR_MATH_API float3 make_float3(const int3& a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +VECTOR_MATH_API float3 make_float3(const uint3& a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float3 operator-(const float3& a) +{ + return make_float3(-a.x, -a.y, -a.z); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float3 fminf(const float3& a, const float3& b) +{ + return make_float3(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z)); +} +VECTOR_MATH_API float fminf(const float3& a) +{ + return fminf(fminf(a.x, a.y), a.z); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float3 fmaxf(const float3& a, const float3& b) +{ + return make_float3(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z)); +} +VECTOR_MATH_API float fmaxf(const float3& a) +{ + return fmaxf(fmaxf(a.x, a.y), a.z); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float3 operator+(const float3& a, const float3& b) +{ + return make_float3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API float3 operator+(const float3& a, const float b) +{ + return make_float3(a.x + b, a.y + b, a.z + b); +} +VECTOR_MATH_API float3 operator+(const float a, const float3& b) +{ + return make_float3(a + b.x, a + b.y, a + b.z); +} +VECTOR_MATH_API void operator+=(float3& a, const float3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float3 operator-(const float3& a, const float3& b) +{ + return make_float3(a.x - b.x, a.y - b.y, a.z - b.z); +} +VECTOR_MATH_API float3 operator-(const float3& a, const float b) +{ + return make_float3(a.x - b, a.y - b, a.z - b); +} +VECTOR_MATH_API float3 operator-(const float a, const float3& b) +{ + return make_float3(a - b.x, a - b.y, a - b.z); +} +VECTOR_MATH_API void operator-=(float3& a, const float3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float3 operator*(const float3& a, const float3& b) +{ + return make_float3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API float3 operator*(const float3& a, const float s) +{ + return make_float3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API float3 operator*(const float s, const float3& a) +{ + return make_float3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(float3& a, const float3& s) +{ + a.x *= s.x; + a.y *= s.y; + a.z *= s.z; +} +VECTOR_MATH_API void operator*=(float3& a, const float s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float3 operator/(const float3& a, const float3& b) +{ + return make_float3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API float3 operator/(const float3& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float3 operator/(const float s, const float3& a) +{ + return make_float3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(float3& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float3 lerp(const float3& a, const float3& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float3 bilerp(const float3& x00, const float3& x10, const float3& x01, const float3& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float3 clamp(const float3& v, const float a, const float b) +{ + return make_float3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API float3 clamp(const float3& v, const float3& a, const float3& b) +{ + return make_float3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float3& a, const float3& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +/** cross product */ +VECTOR_MATH_API float3 cross(const float3& a, const float3& b) +{ + return make_float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); +} + +/** length */ +VECTOR_MATH_API float length(const float3& v) +{ + return sqrtf(dot(v, v)); +} + +/** normalize */ +VECTOR_MATH_API float3 normalize(const float3& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float3 floor(const float3& v) +{ + return make_float3(::floorf(v.x), ::floorf(v.y), ::floorf(v.z)); +} + +/** reflect */ +VECTOR_MATH_API float3 reflect(const float3& i, const float3& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL */ +VECTOR_MATH_API float3 faceforward(const float3& n, const float3& i, const float3& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float3 expf(const float3& v) +{ + return make_float3(::expf(v.x), ::expf(v.y), ::expf(v.z)); +} + +/** pow **/ +VECTOR_MATH_API float3 powf(const float3& v, const float e) +{ + return make_float3(::powf(v.x, e), ::powf(v.y, e), ::powf(v.z, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float3 sqrtf(const float3& v) +{ + return make_float3(::sqrtf(v.x), ::sqrtf(v.y), ::sqrtf(v.z)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float3& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float3& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + +/* float4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API float4 make_float4(const float s) +{ + return make_float4(s, s, s, s); +} +VECTOR_MATH_API float4 make_float4(const float3& a) +{ + return make_float4(a.x, a.y, a.z, 0.0f); +} +VECTOR_MATH_API float4 make_float4(const int4& a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +VECTOR_MATH_API float4 make_float4(const uint4& a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API float4 operator-(const float4& a) +{ + return make_float4(-a.x, -a.y, -a.z, -a.w); +} + +/** min +* @{ +*/ +VECTOR_MATH_API float4 fminf(const float4& a, const float4& b) +{ + return make_float4(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z), fminf(a.w, b.w)); +} +VECTOR_MATH_API float fminf(const float4& a) +{ + return fminf(fminf(a.x, a.y), fminf(a.z, a.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API float4 fmaxf(const float4& a, const float4& b) +{ + return make_float4(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z), fmaxf(a.w, b.w)); +} +VECTOR_MATH_API float fmaxf(const float4& a) +{ + return fmaxf(fmaxf(a.x, a.y), fmaxf(a.z, a.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API float4 operator+(const float4& a, const float4& b) +{ + return make_float4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API float4 operator+(const float4& a, const float b) +{ + return make_float4(a.x + b, a.y + b, a.z + b, a.w + b); +} +VECTOR_MATH_API float4 operator+(const float a, const float4& b) +{ + return make_float4(a + b.x, a + b.y, a + b.z, a + b.w); +} +VECTOR_MATH_API void operator+=(float4& a, const float4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API float4 operator-(const float4& a, const float4& b) +{ + return make_float4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} +VECTOR_MATH_API float4 operator-(const float4& a, const float b) +{ + return make_float4(a.x - b, a.y - b, a.z - b, a.w - b); +} +VECTOR_MATH_API float4 operator-(const float a, const float4& b) +{ + return make_float4(a - b.x, a - b.y, a - b.z, a - b.w); +} +VECTOR_MATH_API void operator-=(float4& a, const float4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API float4 operator*(const float4& a, const float4& s) +{ + return make_float4(a.x * s.x, a.y * s.y, a.z * s.z, a.w * s.w); +} +VECTOR_MATH_API float4 operator*(const float4& a, const float s) +{ + return make_float4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API float4 operator*(const float s, const float4& a) +{ + return make_float4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(float4& a, const float4& s) +{ + a.x *= s.x; a.y *= s.y; a.z *= s.z; a.w *= s.w; +} +VECTOR_MATH_API void operator*=(float4& a, const float s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API float4 operator/(const float4& a, const float4& b) +{ + return make_float4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API float4 operator/(const float4& a, const float s) +{ + float inv = 1.0f / s; + return a * inv; +} +VECTOR_MATH_API float4 operator/(const float s, const float4& a) +{ + return make_float4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(float4& a, const float s) +{ + float inv = 1.0f / s; + a *= inv; +} +/** @} */ + +/** lerp */ +VECTOR_MATH_API float4 lerp(const float4& a, const float4& b, const float t) +{ + return a + t * (b - a); +} + +/** bilerp */ +VECTOR_MATH_API float4 bilerp(const float4& x00, const float4& x10, const float4& x01, const float4& x11, + const float u, const float v) +{ + return lerp(lerp(x00, x10, u), lerp(x01, x11, u), v); +} + +/** clamp +* @{ +*/ +VECTOR_MATH_API float4 clamp(const float4& v, const float a, const float b) +{ + return make_float4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API float4 clamp(const float4& v, const float4& a, const float4& b) +{ + return make_float4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** dot product */ +VECTOR_MATH_API float dot(const float4& a, const float4& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +/** length */ +VECTOR_MATH_API float length(const float4& r) +{ + return sqrtf(dot(r, r)); +} + +/** normalize */ +VECTOR_MATH_API float4 normalize(const float4& v) +{ + float invLen = 1.0f / sqrtf(dot(v, v)); + return v * invLen; +} + +/** floor */ +VECTOR_MATH_API float4 floor(const float4& v) +{ + return make_float4(::floorf(v.x), ::floorf(v.y), ::floorf(v.z), ::floorf(v.w)); +} + +/** reflect */ +VECTOR_MATH_API float4 reflect(const float4& i, const float4& n) +{ + return i - 2.0f * n * dot(n, i); +} + +/** +* Faceforward +* Returns N if dot(i, nref) > 0; else -N; +* Typical usage is N = faceforward(N, -ray.dir, N); +* Note that this is opposite of what faceforward does in Cg and GLSL +*/ +VECTOR_MATH_API float4 faceforward(const float4& n, const float4& i, const float4& nref) +{ + return n * copysignf(1.0f, dot(i, nref)); +} + +/** exp */ +VECTOR_MATH_API float4 expf(const float4& v) +{ + return make_float4(::expf(v.x), ::expf(v.y), ::expf(v.z), ::expf(v.w)); +} + +/** pow */ +VECTOR_MATH_API float4 powf(const float4& v, const float e) +{ + return make_float4(::powf(v.x, e), ::powf(v.y, e), ::powf(v.z, e), ::powf(v.w, e)); +} + +/** sqrtf */ +VECTOR_MATH_API float4 sqrtf(const float4& v) +{ + return make_float4(::sqrtf(v.x), ::sqrtf(v.y), ::sqrtf(v.z), ::sqrtf(v.w)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API float getByIndex(const float4& v, int i) +{ + return ((float*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(float4& v, int i, float x) +{ + ((float*) (&v))[i] = x; +} + + +/* int functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API int clamp(const int f, const int a, const int b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int1& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int1& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int2 make_int2(const int s) +{ + return make_int2(s, s); +} +VECTOR_MATH_API int2 make_int2(const float2& a) +{ + return make_int2(int(a.x), int(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int2 operator-(const int2& a) +{ + return make_int2(-a.x, -a.y); +} + +/** min */ +VECTOR_MATH_API int2 min(const int2& a, const int2& b) +{ + return make_int2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API int2 max(const int2& a, const int2& b) +{ + return make_int2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int2 operator+(const int2& a, const int2& b) +{ + return make_int2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(int2& a, const int2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int2 operator-(const int2& a, const int2& b) +{ + return make_int2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API int2 operator-(const int2& a, const int b) +{ + return make_int2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(int2& a, const int2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int2 operator*(const int2& a, const int2& b) +{ + return make_int2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API int2 operator*(const int2& a, const int s) +{ + return make_int2(a.x * s, a.y * s); +} +VECTOR_MATH_API int2 operator*(const int s, const int2& a) +{ + return make_int2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(int2& a, const int s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int2 clamp(const int2& v, const int a, const int b) +{ + return make_int2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API int2 clamp(const int2& v, const int2& a, const int2& b) +{ + return make_int2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int2& a, const int2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const int2& a, const int2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int2& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int2& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int3 make_int3(const int s) +{ + return make_int3(s, s, s); +} +VECTOR_MATH_API int3 make_int3(const float3& a) +{ + return make_int3(int(a.x), int(a.y), int(a.z)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int3 operator-(const int3& a) +{ + return make_int3(-a.x, -a.y, -a.z); +} + +/** min */ +VECTOR_MATH_API int3 min(const int3& a, const int3& b) +{ + return make_int3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API int3 max(const int3& a, const int3& b) +{ + return make_int3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int3 operator+(const int3& a, const int3& b) +{ + return make_int3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(int3& a, const int3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int3 operator-(const int3& a, const int3& b) +{ + return make_int3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(int3& a, const int3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int3 operator*(const int3& a, const int3& b) +{ + return make_int3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API int3 operator*(const int3& a, const int s) +{ + return make_int3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API int3 operator*(const int s, const int3& a) +{ + return make_int3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(int3& a, const int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API int3 operator/(const int3& a, const int3& b) +{ + return make_int3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API int3 operator/(const int3& a, const int s) +{ + return make_int3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API int3 operator/(const int s, const int3& a) +{ + return make_int3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(int3& a, const int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int3 clamp(const int3& v, const int a, const int b) +{ + return make_int3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API int3 clamp(const int3& v, const int3& a, const int3& b) +{ + return make_int3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int3& a, const int3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const int3& a, const int3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int3& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int3& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* int4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API int4 make_int4(const int s) +{ + return make_int4(s, s, s, s); +} +VECTOR_MATH_API int4 make_int4(const float4& a) +{ + return make_int4((int) a.x, (int) a.y, (int) a.z, (int) a.w); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API int4 operator-(const int4& a) +{ + return make_int4(-a.x, -a.y, -a.z, -a.w); +} + +/** min */ +VECTOR_MATH_API int4 min(const int4& a, const int4& b) +{ + return make_int4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} + +/** max */ +VECTOR_MATH_API int4 max(const int4& a, const int4& b) +{ + return make_int4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API int4 operator+(const int4& a, const int4& b) +{ + return make_int4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(int4& a, const int4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API int4 operator-(const int4& a, const int4& b) +{ + return make_int4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(int4& a, const int4& b) +{ + a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API int4 operator*(const int4& a, const int4& b) +{ + return make_int4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API int4 operator*(const int4& a, const int s) +{ + return make_int4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API int4 operator*(const int s, const int4& a) +{ + return make_int4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(int4& a, const int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API int4 operator/(const int4& a, const int4& b) +{ + return make_int4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API int4 operator/(const int4& a, const int s) +{ + return make_int4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API int4 operator/(const int s, const int4& a) +{ + return make_int4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(int4& a, const int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API int4 clamp(const int4& v, const int a, const int b) +{ + return make_int4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API int4 clamp(const int4& v, const int4& a, const int4& b) +{ + return make_int4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const int4& a, const int4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const int4& a, const int4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API int getByIndex(const int4& v, int i) +{ + return ((int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(int4& v, int i, int x) +{ + ((int*) (&v))[i] = x; +} + + +/* uint functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API unsigned int clamp(const unsigned int f, const unsigned int a, const unsigned int b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned int getByIndex(const uint1& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(uint1& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint2 make_uint2(const unsigned int s) +{ + return make_uint2(s, s); +} +VECTOR_MATH_API uint2 make_uint2(const float2& a) +{ + return make_uint2((unsigned int) a.x, (unsigned int) a.y); +} +/** @} */ + +/** min */ +VECTOR_MATH_API uint2 min(const uint2& a, const uint2& b) +{ + return make_uint2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API uint2 max(const uint2& a, const uint2& b) +{ + return make_uint2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API uint2 operator+(const uint2& a, const uint2& b) +{ + return make_uint2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(uint2& a, const uint2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint2 operator-(const uint2& a, const uint2& b) +{ + return make_uint2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API uint2 operator-(const uint2& a, const unsigned int b) +{ + return make_uint2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(uint2& a, const uint2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint2 operator*(const uint2& a, const uint2& b) +{ + return make_uint2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API uint2 operator*(const uint2& a, const unsigned int s) +{ + return make_uint2(a.x * s, a.y * s); +} +VECTOR_MATH_API uint2 operator*(const unsigned int s, const uint2& a) +{ + return make_uint2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(uint2& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint2 clamp(const uint2& v, const unsigned int a, const unsigned int b) +{ + return make_uint2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API uint2 clamp(const uint2& v, const uint2& a, const uint2& b) +{ + return make_uint2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint2& a, const uint2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const uint2& a, const uint2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned int getByIndex(const uint2& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(uint2& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint3 make_uint3(const unsigned int s) +{ + return make_uint3(s, s, s); +} +VECTOR_MATH_API uint3 make_uint3(const float3& a) +{ + return make_uint3((unsigned int) a.x, (unsigned int) a.y, (unsigned int) a.z); +} +/** @} */ + +/** min */ +VECTOR_MATH_API uint3 min(const uint3& a, const uint3& b) +{ + return make_uint3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API uint3 max(const uint3& a, const uint3& b) +{ + return make_uint3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API uint3 operator+(const uint3& a, const uint3& b) +{ + return make_uint3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(uint3& a, const uint3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint3 operator-(const uint3& a, const uint3& b) +{ + return make_uint3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(uint3& a, const uint3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint3 operator*(const uint3& a, const uint3& b) +{ + return make_uint3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API uint3 operator*(const uint3& a, const unsigned int s) +{ + return make_uint3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API uint3 operator*(const unsigned int s, const uint3& a) +{ + return make_uint3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(uint3& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API uint3 operator/(const uint3& a, const uint3& b) +{ + return make_uint3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API uint3 operator/(const uint3& a, const unsigned int s) +{ + return make_uint3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API uint3 operator/(const unsigned int s, const uint3& a) +{ + return make_uint3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(uint3& a, const unsigned int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint3 clamp(const uint3& v, const unsigned int a, const unsigned int b) +{ + return make_uint3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API uint3 clamp(const uint3& v, const uint3& a, const uint3& b) +{ + return make_uint3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint3& a, const uint3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const uint3& a, const uint3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API unsigned int getByIndex(const uint3& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(uint3& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + + +/* uint4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API uint4 make_uint4(const unsigned int s) +{ + return make_uint4(s, s, s, s); +} +VECTOR_MATH_API uint4 make_uint4(const float4& a) +{ + return make_uint4((unsigned int) a.x, (unsigned int) a.y, (unsigned int) a.z, (unsigned int) a.w); +} +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API uint4 min(const uint4& a, const uint4& b) +{ + return make_uint4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API uint4 max(const uint4& a, const uint4& b) +{ + return make_uint4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API uint4 operator+(const uint4& a, const uint4& b) +{ + return make_uint4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(uint4& a, const uint4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API uint4 operator-(const uint4& a, const uint4& b) +{ + return make_uint4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(uint4& a, const uint4& b) +{ + a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API uint4 operator*(const uint4& a, const uint4& b) +{ + return make_uint4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API uint4 operator*(const uint4& a, const unsigned int s) +{ + return make_uint4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API uint4 operator*(const unsigned int s, const uint4& a) +{ + return make_uint4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(uint4& a, const unsigned int s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API uint4 operator/(const uint4& a, const uint4& b) +{ + return make_uint4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API uint4 operator/(const uint4& a, const unsigned int s) +{ + return make_uint4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API uint4 operator/(const unsigned int s, const uint4& a) +{ + return make_uint4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(uint4& a, const unsigned int s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API uint4 clamp(const uint4& v, const unsigned int a, const unsigned int b) +{ + return make_uint4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API uint4 clamp(const uint4& v, const uint4& a, const uint4& b) +{ + return make_uint4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const uint4& a, const uint4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const uint4& a, const uint4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API unsigned int getByIndex(const uint4& v, unsigned int i) +{ + return ((unsigned int*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(uint4& v, int i, unsigned int x) +{ + ((unsigned int*) (&v))[i] = x; +} + +/* long long functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API long long clamp(const long long f, const long long a, const long long b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong1& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong1& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong2 make_longlong2(const long long s) +{ + return make_longlong2(s, s); +} +VECTOR_MATH_API longlong2 make_longlong2(const float2& a) +{ + return make_longlong2(int(a.x), int(a.y)); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong2 operator-(const longlong2& a) +{ + return make_longlong2(-a.x, -a.y); +} + +/** min */ +VECTOR_MATH_API longlong2 min(const longlong2& a, const longlong2& b) +{ + return make_longlong2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API longlong2 max(const longlong2& a, const longlong2& b) +{ + return make_longlong2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong2 operator+(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(longlong2& a, const longlong2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong2 operator-(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API longlong2 operator-(const longlong2& a, const long long b) +{ + return make_longlong2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(longlong2& a, const longlong2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong2 operator*(const longlong2& a, const longlong2& b) +{ + return make_longlong2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API longlong2 operator*(const longlong2& a, const long long s) +{ + return make_longlong2(a.x * s, a.y * s); +} +VECTOR_MATH_API longlong2 operator*(const long long s, const longlong2& a) +{ + return make_longlong2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(longlong2& a, const long long s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong2 clamp(const longlong2& v, const long long a, const long long b) +{ + return make_longlong2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API longlong2 clamp(const longlong2& v, const longlong2& a, const longlong2& b) +{ + return make_longlong2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong2& a, const longlong2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const longlong2& a, const longlong2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong2& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong2& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong3 make_longlong3(const long long s) +{ + return make_longlong3(s, s, s); +} +VECTOR_MATH_API longlong3 make_longlong3(const float3& a) +{ + return make_longlong3((long long) a.x, (long long) a.y, (long long) a.z); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong3 operator-(const longlong3& a) +{ + return make_longlong3(-a.x, -a.y, -a.z); +} + +/** min */ +VECTOR_MATH_API longlong3 min(const longlong3& a, const longlong3& b) +{ + return make_longlong3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API longlong3 max(const longlong3& a, const longlong3& b) +{ + return make_longlong3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong3 operator+(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(longlong3& a, const longlong3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong3 operator-(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(longlong3& a, const longlong3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong3 operator*(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API longlong3 operator*(const longlong3& a, const long long s) +{ + return make_longlong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API longlong3 operator*(const long long s, const longlong3& a) +{ + return make_longlong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(longlong3& a, const long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API longlong3 operator/(const longlong3& a, const longlong3& b) +{ + return make_longlong3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API longlong3 operator/(const longlong3& a, const long long s) +{ + return make_longlong3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API longlong3 operator/(const long long s, const longlong3& a) +{ + return make_longlong3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(longlong3& a, const long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong3 clamp(const longlong3& v, const long long a, const long long b) +{ + return make_longlong3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API longlong3 clamp(const longlong3& v, const longlong3& a, const longlong3& b) +{ + return make_longlong3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong3& a, const longlong3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const longlong3& a, const longlong3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong3& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong3& v, int i, int x) +{ + ((long long*) (&v))[i] = x; +} + + +/* longlong4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API longlong4 make_longlong4(const long long s) +{ + return make_longlong4(s, s, s, s); +} +VECTOR_MATH_API longlong4 make_longlong4(const float4& a) +{ + return make_longlong4((long long) a.x, (long long) a.y, (long long) a.z, (long long) a.w); +} +/** @} */ + +/** negate */ +VECTOR_MATH_API longlong4 operator-(const longlong4& a) +{ + return make_longlong4(-a.x, -a.y, -a.z, -a.w); +} + +/** min */ +VECTOR_MATH_API longlong4 min(const longlong4& a, const longlong4& b) +{ + return make_longlong4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} + +/** max */ +VECTOR_MATH_API longlong4 max(const longlong4& a, const longlong4& b) +{ + return make_longlong4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API longlong4 operator+(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(longlong4& a, const longlong4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API longlong4 operator-(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(longlong4& a, const longlong4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API longlong4 operator*(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API longlong4 operator*(const longlong4& a, const long long s) +{ + return make_longlong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API longlong4 operator*(const long long s, const longlong4& a) +{ + return make_longlong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(longlong4& a, const long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API longlong4 operator/(const longlong4& a, const longlong4& b) +{ + return make_longlong4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API longlong4 operator/(const longlong4& a, const long long s) +{ + return make_longlong4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API longlong4 operator/(const long long s, const longlong4& a) +{ + return make_longlong4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(longlong4& a, const long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API longlong4 clamp(const longlong4& v, const long long a, const long long b) +{ + return make_longlong4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API longlong4 clamp(const longlong4& v, const longlong4& a, const longlong4& b) +{ + return make_longlong4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const longlong4& a, const longlong4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const longlong4& a, const longlong4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API long long getByIndex(const longlong4& v, int i) +{ + return ((long long*) (&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(longlong4& v, int i, long long x) +{ + ((long long*) (&v))[i] = x; +} + +/* ulonglong functions */ +/******************************************************************************/ + +/** clamp */ +VECTOR_MATH_API unsigned long long clamp(const unsigned long long f, const unsigned long long a, const unsigned long long b) +{ + return max(a, min(f, b)); +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong1& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(ulonglong1& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong2 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong2 make_ulonglong2(const unsigned long long s) +{ + return make_ulonglong2(s, s); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const float2& a) +{ + return make_ulonglong2((unsigned long long)a.x, (unsigned long long)a.y); +} +/** @} */ + +/** min */ +VECTOR_MATH_API ulonglong2 min(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(min(a.x, b.x), min(a.y, b.y)); +} + +/** max */ +VECTOR_MATH_API ulonglong2 max(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(max(a.x, b.x), max(a.y, b.y)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator+(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x + b.x, a.y + b.y); +} +VECTOR_MATH_API void operator+=(ulonglong2& a, const ulonglong2& b) +{ + a.x += b.x; + a.y += b.y; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator-(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x - b.x, a.y - b.y); +} +VECTOR_MATH_API ulonglong2 operator-(const ulonglong2& a, const unsigned long long b) +{ + return make_ulonglong2(a.x - b, a.y - b); +} +VECTOR_MATH_API void operator-=(ulonglong2& a, const ulonglong2& b) +{ + a.x -= b.x; + a.y -= b.y; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong2 operator*(const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(a.x * b.x, a.y * b.y); +} +VECTOR_MATH_API ulonglong2 operator*(const ulonglong2& a, const unsigned long long s) +{ + return make_ulonglong2(a.x * s, a.y * s); +} +VECTOR_MATH_API ulonglong2 operator*(const unsigned long long s, const ulonglong2& a) +{ + return make_ulonglong2(a.x * s, a.y * s); +} +VECTOR_MATH_API void operator*=(ulonglong2& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong2 clamp(const ulonglong2& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong2(clamp(v.x, a, b), clamp(v.y, a, b)); +} + +VECTOR_MATH_API ulonglong2 clamp(const ulonglong2& v, const ulonglong2& a, const ulonglong2& b) +{ + return make_ulonglong2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong2& a, const ulonglong2& b) +{ + return a.x == b.x && a.y == b.y; +} + +VECTOR_MATH_API bool operator!=(const ulonglong2& a, const ulonglong2& b) +{ + return a.x != b.x || a.y != b.y; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong2& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory */ +VECTOR_MATH_API void setByIndex(ulonglong2& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong3 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong3 make_ulonglong3(const unsigned long long s) +{ + return make_ulonglong3(s, s, s); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const float3& a) +{ + return make_ulonglong3((unsigned long long)a.x, (unsigned long long)a.y, (unsigned long long)a.z); +} +/** @} */ + +/** min */ +VECTOR_MATH_API ulonglong3 min(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +/** max */ +VECTOR_MATH_API ulonglong3 max(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator+(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x + b.x, a.y + b.y, a.z + b.z); +} +VECTOR_MATH_API void operator+=(ulonglong3& a, const ulonglong3& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator-(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECTOR_MATH_API void operator-=(ulonglong3& a, const ulonglong3& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator*(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x * b.x, a.y * b.y, a.z * b.z); +} +VECTOR_MATH_API ulonglong3 operator*(const ulonglong3& a, const unsigned long long s) +{ + return make_ulonglong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API ulonglong3 operator*(const unsigned long long s, const ulonglong3& a) +{ + return make_ulonglong3(a.x * s, a.y * s, a.z * s); +} +VECTOR_MATH_API void operator*=(ulonglong3& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API ulonglong3 operator/(const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(a.x / b.x, a.y / b.y, a.z / b.z); +} +VECTOR_MATH_API ulonglong3 operator/(const ulonglong3& a, const unsigned long long s) +{ + return make_ulonglong3(a.x / s, a.y / s, a.z / s); +} +VECTOR_MATH_API ulonglong3 operator/(const unsigned long long s, const ulonglong3& a) +{ + return make_ulonglong3(s / a.x, s / a.y, s / a.z); +} +VECTOR_MATH_API void operator/=(ulonglong3& a, const unsigned long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong3 clamp(const ulonglong3& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} + +VECTOR_MATH_API ulonglong3 clamp(const ulonglong3& v, const ulonglong3& a, const ulonglong3& b) +{ + return make_ulonglong3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong3& a, const ulonglong3& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +VECTOR_MATH_API bool operator!=(const ulonglong3& a, const ulonglong3& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z; +} +/** @} */ + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong3& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(ulonglong3& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/* ulonglong4 functions */ +/******************************************************************************/ + +/** additional constructors +* @{ +*/ +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long s) +{ + return make_ulonglong4(s, s, s, s); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const float4& a) +{ + return make_ulonglong4((unsigned long long)a.x, (unsigned long long)a.y, (unsigned long long)a.z, (unsigned long long)a.w); +} +/** @} */ + +/** min +* @{ +*/ +VECTOR_MATH_API ulonglong4 min(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); +} +/** @} */ + +/** max +* @{ +*/ +VECTOR_MATH_API ulonglong4 max(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); +} +/** @} */ + +/** add +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator+(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +VECTOR_MATH_API void operator+=(ulonglong4& a, const ulonglong4& b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +/** @} */ + +/** subtract +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator-(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +VECTOR_MATH_API void operator-=(ulonglong4& a, const ulonglong4& b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +/** @} */ + +/** multiply +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator*(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +VECTOR_MATH_API ulonglong4 operator*(const ulonglong4& a, const unsigned long long s) +{ + return make_ulonglong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API ulonglong4 operator*(const unsigned long long s, const ulonglong4& a) +{ + return make_ulonglong4(a.x * s, a.y * s, a.z * s, a.w * s); +} +VECTOR_MATH_API void operator*=(ulonglong4& a, const unsigned long long s) +{ + a.x *= s; + a.y *= s; + a.z *= s; + a.w *= s; +} +/** @} */ + +/** divide +* @{ +*/ +VECTOR_MATH_API ulonglong4 operator/(const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +VECTOR_MATH_API ulonglong4 operator/(const ulonglong4& a, const unsigned long long s) +{ + return make_ulonglong4(a.x / s, a.y / s, a.z / s, a.w / s); +} +VECTOR_MATH_API ulonglong4 operator/(const unsigned long long s, const ulonglong4& a) +{ + return make_ulonglong4(s / a.x, s / a.y, s / a.z, s / a.w); +} +VECTOR_MATH_API void operator/=(ulonglong4& a, const unsigned long long s) +{ + a.x /= s; + a.y /= s; + a.z /= s; + a.w /= s; +} +/** @} */ + +/** clamp +* @{ +*/ +VECTOR_MATH_API ulonglong4 clamp(const ulonglong4& v, const unsigned long long a, const unsigned long long b) +{ + return make_ulonglong4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} + +VECTOR_MATH_API ulonglong4 clamp(const ulonglong4& v, const ulonglong4& a, const ulonglong4& b) +{ + return make_ulonglong4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} +/** @} */ + +/** equality +* @{ +*/ +VECTOR_MATH_API bool operator==(const ulonglong4& a, const ulonglong4& b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +VECTOR_MATH_API bool operator!=(const ulonglong4& a, const ulonglong4& b) +{ + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; +} +/** @} */ + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API unsigned long long getByIndex(const ulonglong4& v, unsigned int i) +{ + return ((unsigned long long*)(&v))[i]; +} + +/** If used on the device, this could place the 'v' in local memory +*/ +VECTOR_MATH_API void setByIndex(ulonglong4& v, int i, unsigned long long x) +{ + ((unsigned long long*)(&v))[i] = x; +} + + +/******************************************************************************/ + +/** Narrowing functions +* @{ +*/ +VECTOR_MATH_API int2 make_int2(const int3& v0) +{ + return make_int2(v0.x, v0.y); +} +VECTOR_MATH_API int2 make_int2(const int4& v0) +{ + return make_int2(v0.x, v0.y); +} +VECTOR_MATH_API int3 make_int3(const int4& v0) +{ + return make_int3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API uint2 make_uint2(const uint3& v0) +{ + return make_uint2(v0.x, v0.y); +} +VECTOR_MATH_API uint2 make_uint2(const uint4& v0) +{ + return make_uint2(v0.x, v0.y); +} +VECTOR_MATH_API uint3 make_uint3(const uint4& v0) +{ + return make_uint3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API longlong2 make_longlong2(const longlong3& v0) +{ + return make_longlong2(v0.x, v0.y); +} +VECTOR_MATH_API longlong2 make_longlong2(const longlong4& v0) +{ + return make_longlong2(v0.x, v0.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const longlong4& v0) +{ + return make_longlong3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const ulonglong3& v0) +{ + return make_ulonglong2(v0.x, v0.y); +} +VECTOR_MATH_API ulonglong2 make_ulonglong2(const ulonglong4& v0) +{ + return make_ulonglong2(v0.x, v0.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const ulonglong4& v0) +{ + return make_ulonglong3(v0.x, v0.y, v0.z); +} +VECTOR_MATH_API float2 make_float2(const float3& v0) +{ + return make_float2(v0.x, v0.y); +} +VECTOR_MATH_API float2 make_float2(const float4& v0) +{ + return make_float2(v0.x, v0.y); +} +VECTOR_MATH_API float2 make_float2(const uint3& v0) +{ + return make_float2(float(v0.x), float(v0.y)); +} +VECTOR_MATH_API float3 make_float3(const float4& v0) +{ + return make_float3(v0.x, v0.y, v0.z); +} +/** @} */ + +/** Assemble functions from smaller vectors +* @{ +*/ +VECTOR_MATH_API int3 make_int3(const int v0, const int2& v1) +{ + return make_int3(v0, v1.x, v1.y); +} +VECTOR_MATH_API int3 make_int3(const int2& v0, const int v1) +{ + return make_int3(v0.x, v0.y, v1); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int v1, const int2& v2) +{ + return make_int4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int2& v1, const int v2) +{ + return make_int4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API int4 make_int4(const int2& v0, const int v1, const int v2) +{ + return make_int4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API int4 make_int4(const int v0, const int3& v1) +{ + return make_int4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API int4 make_int4(const int3& v0, const int v1) +{ + return make_int4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API int4 make_int4(const int2& v0, const int2& v1) +{ + return make_int4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API uint3 make_uint3(const unsigned int v0, const uint2& v1) +{ + return make_uint3(v0, v1.x, v1.y); +} +VECTOR_MATH_API uint3 make_uint3(const uint2& v0, const unsigned int v1) +{ + return make_uint3(v0.x, v0.y, v1); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const unsigned int v1, const uint2& v2) +{ + return make_uint4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const uint2& v1, const unsigned int v2) +{ + return make_uint4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API uint4 make_uint4(const uint2& v0, const unsigned int v1, const unsigned int v2) +{ + return make_uint4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API uint4 make_uint4(const unsigned int v0, const uint3& v1) +{ + return make_uint4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API uint4 make_uint4(const uint3& v0, const unsigned int v1) +{ + return make_uint4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API uint4 make_uint4(const uint2& v0, const uint2& v1) +{ + return make_uint4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const long long v0, const longlong2& v1) +{ + return make_longlong3(v0, v1.x, v1.y); +} +VECTOR_MATH_API longlong3 make_longlong3(const longlong2& v0, const long long v1) +{ + return make_longlong3(v0.x, v0.y, v1); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const long long v1, const longlong2& v2) +{ + return make_longlong4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const longlong2& v1, const long long v2) +{ + return make_longlong4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong2& v0, const long long v1, const long long v2) +{ + return make_longlong4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API longlong4 make_longlong4(const long long v0, const longlong3& v1) +{ + return make_longlong4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong3& v0, const long long v1) +{ + return make_longlong4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API longlong4 make_longlong4(const longlong2& v0, const longlong2& v1) +{ + return make_longlong4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const unsigned long long v0, const ulonglong2& v1) +{ + return make_ulonglong3(v0, v1.x, v1.y); +} +VECTOR_MATH_API ulonglong3 make_ulonglong3(const ulonglong2& v0, const unsigned long long v1) +{ + return make_ulonglong3(v0.x, v0.y, v1); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const unsigned long long v1, const ulonglong2& v2) +{ + return make_ulonglong4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const ulonglong2& v1, const unsigned long long v2) +{ + return make_ulonglong4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong2& v0, const unsigned long long v1, const unsigned long long v2) +{ + return make_ulonglong4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const unsigned long long v0, const ulonglong3& v1) +{ + return make_ulonglong4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong3& v0, const unsigned long long v1) +{ + return make_ulonglong4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API ulonglong4 make_ulonglong4(const ulonglong2& v0, const ulonglong2& v1) +{ + return make_ulonglong4(v0.x, v0.y, v1.x, v1.y); +} +VECTOR_MATH_API float3 make_float3(const float2& v0, const float v1) +{ + return make_float3(v0.x, v0.y, v1); +} +VECTOR_MATH_API float3 make_float3(const float v0, const float2& v1) +{ + return make_float3(v0, v1.x, v1.y); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float v1, const float2& v2) +{ + return make_float4(v0, v1, v2.x, v2.y); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float2& v1, const float v2) +{ + return make_float4(v0, v1.x, v1.y, v2); +} +VECTOR_MATH_API float4 make_float4(const float2& v0, const float v1, const float v2) +{ + return make_float4(v0.x, v0.y, v1, v2); +} +VECTOR_MATH_API float4 make_float4(const float v0, const float3& v1) +{ + return make_float4(v0, v1.x, v1.y, v1.z); +} +VECTOR_MATH_API float4 make_float4(const float3& v0, const float v1) +{ + return make_float4(v0.x, v0.y, v0.z, v1); +} +VECTOR_MATH_API float4 make_float4(const float2& v0, const float2& v1) +{ + return make_float4(v0.x, v0.y, v1.x, v1.y); +} +/** @} */ + +#endif // VECTOR_MATH_H diff --git a/apps/bench_shared_offscreen/shaders/vertex_attributes.h b/apps/bench_shared_offscreen/shaders/vertex_attributes.h new file mode 100644 index 00000000..fda243e5 --- /dev/null +++ b/apps/bench_shared_offscreen/shaders/vertex_attributes.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#pragma once + +#ifndef VERTEX_ATTRIBUTES_H +#define VERTEX_ATTRIBUTES_H + +struct TriangleAttributes +{ + float3 vertex; + float3 tangent; + float3 normal; + float3 texcoord; +}; + +#endif // VERTEX_ATTRIBUTES_H diff --git a/apps/bench_shared_offscreen/src/Application.cpp b/apps/bench_shared_offscreen/src/Application.cpp new file mode 100644 index 00000000..3f58f63e --- /dev/null +++ b/apps/bench_shared_offscreen/src/Application.cpp @@ -0,0 +1,2461 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Application.h" +#include "inc/Parser.h" +#include "inc/Raytracer.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "inc/CheckMacros.h" + +#include "inc/MyAssert.h" + +Application::Application(/* GLFWwindow* window, */ const Options& options) +: // m_window(window), + m_isValid(false) +//, m_guiState(GUI_STATE_NONE) +//, m_isVisibleGUI(false) // DAR HACK Hide the GUI by default to allow better interactive to offscreenbenchmark comparisons. +, m_width(512) +, m_height(512) +, m_mode(0) +, m_maskDevices(0x00FFFFFF) // A maximum of 24 devices is supported by default. Limited by the UUID arrays +, m_sizeArena(64) // Default to 64 MiB Arenas when nothing is specified inside the system description. +, m_light(0) +, m_miss(1) +, m_interop(0) +, m_peerToPeer(P2P_TEX | P2P_GAS) // Enable material texture and GAS sharing via NVLINK only by default. +, m_present(false) +//, m_presentNext(true) +//, m_presentAtSecond(1.0) +//, m_previousComplete(false) +, m_lensShader(LENS_SHADER_PINHOLE) +, m_samplesSqrt(1) +, m_epsilonFactor(500.0f) +, m_environmentRotation(0.0f) +, m_clockFactor(1000.0f) +, m_mouseSpeedRatio(10.0f) +, m_idGroup(0) +, m_idInstance(0) +, m_idGeometry(0) +{ + try + { + m_timer.restart(); + + // Initialize the top-level keywords of the scene description for faster search. + m_mapKeywordScene["albedo"] = KS_ALBEDO; + m_mapKeywordScene["albedoTexture"] = KS_ALBEDO_TEXTURE; + m_mapKeywordScene["cutoutTexture"] = KS_CUTOUT_TEXTURE; + m_mapKeywordScene["roughness"] = KS_ROUGHNESS; + m_mapKeywordScene["absorption"] = KS_ABSORPTION; + m_mapKeywordScene["absorptionScale"] = KS_ABSORPTION_SCALE; + m_mapKeywordScene["ior"] = KS_IOR; + m_mapKeywordScene["thinwalled"] = KS_THINWALLED; + m_mapKeywordScene["material"] = KS_MATERIAL; + m_mapKeywordScene["identity"] = KS_IDENTITY; + m_mapKeywordScene["push"] = KS_PUSH; + m_mapKeywordScene["pop"] = KS_POP; + m_mapKeywordScene["rotate"] = KS_ROTATE; + m_mapKeywordScene["scale"] = KS_SCALE; + m_mapKeywordScene["translate"] = KS_TRANSLATE; + m_mapKeywordScene["model"] = KS_MODEL; + + const double timeConstructor = m_timer.getTime(); + + m_width = std::max(1, options.getWidth()); + m_height = std::max(1, options.getHeight()); + m_mode = std::max(0, options.getMode()); + m_optimize = options.getOptimize(); + + // Initialize the system options to minimum defaults to work, but require useful settings inside the system options file. + // The minumum path length values will generate useful direct lighting results, but transmissions will be mostly black. + m_resolution = make_int2(1, 1); + m_tileSize = make_int2(8, 8); + m_pathLengths = make_int2(0, 2); + + m_prefixScreenshot = std::string("./img"); // Default to current working directory and prefix "img". + + // Tonmapper neutral defaults. The system description overrides these. + m_tonemapperGUI.gamma = 1.0f; + m_tonemapperGUI.whitePoint = 1.0f; + m_tonemapperGUI.colorBalance[0] = 1.0f; + m_tonemapperGUI.colorBalance[1] = 1.0f; + m_tonemapperGUI.colorBalance[2] = 1.0f; + m_tonemapperGUI.burnHighlights = 1.0f; + m_tonemapperGUI.crushBlacks = 0.0f; + m_tonemapperGUI.saturation = 1.0f; + m_tonemapperGUI.brightness = 1.0f; + + // System wide parameters are loaded from this file to keep the number of command line options small. + const std::string filenameSystem = options.getSystem(); + if (!loadSystemDescription(filenameSystem)) + { + std::cerr << "ERROR: Application() failed to load system description file " << filenameSystem << '\n'; + MY_ASSERT(!"Failed to load system description"); + return; // m_isValid == false. + } + + std::cout << "Arena size = " << m_sizeArena << " MB\n"; + +# if 0 + // DAR HACK Generate 5x5x5 1024x1024 textures with different RGB8 colors. + // These are tiny when compressed because there is only one color inside the whole image. + // This needs to happen after the system description is loaded becuase the filename is based on the m_prefixScreenshot string. + unsigned int previous = 0; + + for (unsigned int z = 0; z < 5; ++z) + { + for (unsigned int y = 0; y < 5; ++y) + { + for (unsigned int x = 0; x < 5; ++x) + { + previous = previous * 1664525u + 1013904223u; + + const unsigned char r = previous & 0xFF; + const unsigned char g = (previous >> 8) & 0xFF; + const unsigned char b = (previous >> 16) & 0xFF; + + generatePNG(1024, 1024, r, g, b); + } + } + } + return; // This keeps the app invalid. +#endif + + // The user interface is part of the main application. + // Setup ImGui binding. + //ImGui::CreateContext(); + //ImGui_ImplGlfwGL3_Init(window, true); + + //// This initializes the GLFW part including the font texture. + //ImGui_ImplGlfwGL3_NewFrame(); + //ImGui::EndFrame(); + +#if 0 + // Style the GUI colors to a neutral greyscale with plenty of transparency to concentrate on the image. + ImGuiStyle& style = ImGui::GetStyle(); + + // Change these RGB values to get any other tint. + const float r = 1.0f; + const float g = 1.0f; + const float b = 1.0f; + + style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.6f); + style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_PopupBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_Border] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(r * 0.0f, g * 0.0f, b * 0.0f, 0.4f); + style.Colors[ImGuiCol_FrameBg] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_TitleBg] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 1.0f); + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.2f); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Button] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Header] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_Column] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_ColumnHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ColumnActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_CloseButton] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); + style.Colors[ImGuiCol_CloseButtonHovered] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); + style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); + style.Colors[ImGuiCol_PlotLines] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 1.0f); + style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_PlotHistogram] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 1.0f); + style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(r * 0.5f, g * 0.5f, b * 0.5f, 1.0f); + style.Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(r * 0.2f, g * 0.2f, b * 0.2f, 0.2f); + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(r * 1.0f, g * 1.0f, b * 0.0f, 1.0f); // Yellow + style.Colors[ImGuiCol_NavHighlight] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); + style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(r * 1.0f, g * 1.0f, b * 1.0f, 1.0f); +#endif + + //const double timeGUI = m_timer.getTime(); + + m_camera.setResolution(m_resolution.x, m_resolution.y); + m_camera.setSpeedRatio(m_mouseSpeedRatio); + + // Initialize the OpenGL rasterizer. + //m_rasterizer = std::make_unique(m_width, m_height, m_interop); + + // Must set the resolution explicitly to be able to calculate + // the proper vertex attributes for display and the PBO size in case of interop. + //m_rasterizer->setResolution(m_resolution.x, m_resolution.y); + //m_rasterizer->setTonemapper(m_tonemapperGUI); + + //const unsigned int tex = m_rasterizer->getTextureObject(); + //const unsigned int pbo = m_rasterizer->getPixelBufferObject(); + + //const double timeRasterizer = m_timer.getTime(); + + m_raytracer = std::make_unique(m_maskDevices, m_miss, /* m_interop, tex, pbo, */ m_sizeArena, m_peerToPeer); + + // If the raytracer could not be initialized correctly, return and leave Application invalid. + if (!m_raytracer->m_isValid) + { + std::cerr << "ERROR: Application() Could not initialize Raytracer\n"; + return; // Exit application. + } + +// // Determine which device is the one running the OpenGL implementation. +// // The first OpenGL-CUDA device match wins. +// int deviceMatch = -1; +// +//#if 1 +// // UUID works under Windows and Linux. +// const int numDevicesOGL = m_rasterizer->getNumDevices(); +// +// for (int i = 0; i < numDevicesOGL && deviceMatch == -1; ++i) +// { +// deviceMatch = m_raytracer->matchUUID(reinterpret_cast(m_rasterizer->getUUID(i))); +// } +//#else +// // LUID only works under Windows because it requires the EXT_external_objects_win32 extension. +// // DEBUG With multicast enabled, both devices have the same LUID and the OpenGL node mask is the OR of the individual device node masks. +// // Means the result of the deviceMatch here is depending on the CUDA device order. +// // Seems like multicast needs to handle CUDA - OpenGL interop differently. +// // With multicast enabled, uploading the PBO with glTexImage2D halves the framerate when presenting each image in both the single-GPU and multi-GPU P2P strategy. +// // Means there is an expensive PCI-E copy going on in that case. +// const unsigned char* luid = m_rasterizer->getLUID(); +// const int nodeMask = m_rasterizer->getNodeMask(); +// +// // The cuDeviceGetLuid() takes char* and unsigned int though. +// deviceMatch = m_raytracer->matchLUID(reinterpret_cast(luid), nodeMask); +//#endif + + //if (deviceMatch == -1) + //{ + // if (m_interop == INTEROP_MODE_TEX) + // { + // std::cerr << "ERROR: Application() OpenGL texture image interop without OpenGL device in active devices will not display the image!\n"; + // return; // Exit application. + // } + // if (m_interop == INTEROP_MODE_PBO) + // { + // std::cerr << "WARNING: Application() OpenGL pixel buffer interop without OpenGL device in active devices will result in reduced performance!\n"; + // } + //} + + m_state.resolution = m_resolution; + m_state.tileSize = m_tileSize; + m_state.pathLengths = m_pathLengths; + m_state.samplesSqrt = m_samplesSqrt; + m_state.lensShader = m_lensShader; + m_state.epsilonFactor = m_epsilonFactor; + m_state.envRotation = m_environmentRotation; + m_state.clockFactor = m_clockFactor; + + // Sync the state with the default GUI data. + m_raytracer->initState(m_state); + + const double timeRaytracer = m_timer.getTime(); + + // Host side scene graph information. + m_scene = std::make_shared(m_idGroup++); // Create the scene's root group first. + + createPictures(); + createCameras(); + createLights(); + + // Load the scene description file and generate the host side scene. + const std::string filenameScene = options.getScene(); + if (!loadSceneDescription(filenameScene)) + { + std::cerr << "ERROR: Application() failed to load scene description file " << filenameScene << '\n'; + MY_ASSERT(!"Failed to load scene description"); + return; + } + + MY_ASSERT(m_idGeometry == m_geometries.size()); + + const double timeScene = m_timer.getTime(); + + // Device side scene information. + m_raytracer->initTextures(m_mapPictures); + m_raytracer->initCameras(m_cameras); // Currently there is only one but this supports arbitrary many which could be used to select viewpoints or do animation (and camera motion blur) in the future. + m_raytracer->initLights(m_lights); // DAR FIXME Encode the environment texture data into the LightDefinition. + m_raytracer->initMaterials(m_materialsGUI); // This will handle the per material textures as well. + m_raytracer->initScene(m_scene, m_idGeometry); // m_idGeometry is the number of geometries in the scene. + + const double timeRenderer = m_timer.getTime(); + + // Print out how long the initialization of each module took. + std::cout << "Application() " << timeRenderer - timeConstructor << " seconds overall\n"; + std::cout << "{\n"; + //std::cout << " GUI = " << timeGUI - timeConstructor << " seconds\n"; + //std::cout << " Rasterizer = " << timeRasterizer - timeGUI << " seconds\n"; + std::cout << " Raytracer = " << timeRaytracer - timeConstructor << " seconds\n"; + std::cout << " Scene = " << timeScene - timeRaytracer << " seconds\n"; + std::cout << " Renderer = " << timeRenderer - timeScene << " seconds\n"; + std::cout << "}\n"; + + restartRendering(); // Trigger a new rendering. + + m_isValid = true; + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + +Application::~Application() +{ + for (std::map::const_iterator it = m_mapPictures.begin(); it != m_mapPictures.end(); ++it) + { + delete it->second; + } + + //ImGui_ImplGlfwGL3_Shutdown(); + //ImGui::DestroyContext(); +} + +bool Application::isValid() const +{ + return m_isValid; +} + +void Application::reshape(const int w, const int h) +{ + // Do not allow zero size client windows! All other reshape() routines depend on this and do not check this again. + if ( (m_width != w || m_height != h) && w != 0 && h != 0) + { + m_width = w; + m_height = h; + + //m_rasterizer->reshape(m_width, m_height); + } +} + +void Application::restartRendering() +{ + //guiRenderingIndicator(true); + + //m_presentNext = true; + //m_presentAtSecond = 1.0; + // + //m_previousComplete = false; + + m_timer.restart(); +} + +//bool Application::render() +//{ +// bool finish = false; +// bool flush = false; +// +// try +// { +// CameraDefinition camera; +// +// const bool cameraChanged = m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W); +// if (cameraChanged) +// { +// m_cameras[0] = camera; +// m_raytracer->updateCamera(0, camera); +// +// restartRendering(); +// } +// +// const unsigned int iterationIndex = m_raytracer->render(); +// +// // When the renderer has completed all iterations, change the GUI title bar to green. +// const bool complete = ((unsigned int)(m_samplesSqrt * m_samplesSqrt) <= iterationIndex); +// +// if (complete) +// { +// //guiRenderingIndicator(false); // Not rendering anymore. +// +// flush = !m_previousComplete && complete; // Completion status changed to true. +// } +// +// m_previousComplete = complete; +// +// // When benchmark is enabled, exit the application when the requested samples per pixel have been rendered. +// // Actually this render() function is not called when m_mode == 1 but keep the finish here to exit on exceptions. +// finish = ((m_mode == 1) && complete); +// +// // Only update the texture when a restart happened, one second passed to reduce required bandwidth, or the rendering is newly complete. +// if (m_presentNext || flush) +// { +// //m_raytracer->updateDisplayTexture(); // This directly updates the display HDR texture for all rendering strategies. +// +// m_presentNext = m_present; +// } +// +// double seconds = m_timer.getTime(); +//#if 1 +// // When in interactive mode, show the all rendered frames during the first half second to get some initial refinement. +// if (m_mode == 0 && seconds < 0.5) +// { +// m_presentAtSecond = 1.0; +// m_presentNext = true; +// } +//#endif +// +// if (m_presentAtSecond < seconds || flush || finish) // Print performance every second or when the rendering is complete or the benchmark finished. +// { +// m_presentAtSecond = ceil(seconds); +// m_presentNext = true; // Present at least every second. +// +// if (flush || finish) // Only print the performance when the samples per pixels are reached. +// { +// const double fps = double(iterationIndex) / seconds; +// +// std::ostringstream stream; +// stream.precision(3); // Precision is # digits in fraction part. +// stream << std::fixed << iterationIndex << " / " << seconds << " = " << fps << " fps"; +// std::cout << stream.str() << '\n'; +// +//#if 0 // Automated benchmark in interactive mode. Change m_isVisibleGUI default to false! +// std::ostringstream filename; +// filename << "result_interactive_" << m_interop << "_" << m_tileSize.x << "_" << m_tileSize.y << ".log"; +// const bool success = saveString(filename.str(), stream.str()); +// if (success) +// { +// std::cout << filename.str() << '\n'; // Print out the filename to indicate success. +// } +// finish = true; // Exit application after interactive rendering finished. +//#endif +// } +// } +// } +// catch (const std::exception& e) +// { +// std::cerr << e.what() << '\n'; +// finish = true; +// } +// return finish; +//} + +void Application::benchmark() +{ + try + { + const unsigned int spp = (unsigned int)(m_samplesSqrt * m_samplesSqrt); + unsigned int iterationIndex = 0; + + m_timer.restart(); + + while (iterationIndex < spp) + { + iterationIndex = m_raytracer->render(); + } + + m_raytracer->synchronize(); // Wait until any asynchronous operations have finished. + + const double seconds = m_timer.getTime(); + const double fps = double(iterationIndex) / seconds; + + std::ostringstream stream; + stream.precision(3); // Precision is # digits in fraction part. + stream << std::fixed << iterationIndex << " / " << seconds << " = " << fps << " fps"; + std::cout << stream.str() << '\n'; + +#if 0 // Automated benchmark in batch mode. + std::ostringstream filename; + filename << "result_batch_" << m_interop << "_" << m_tileSize.x << "_" << m_tileSize.y << ".log"; + const bool success = saveString(filename.str(), stream.str()); + if (success) + { + std::cout << filename.str() << '\n'; // Print out the filename to indicate success. + } +#endif + + screenshot(true); + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + +//void Application::display() +//{ +// m_rasterizer->display(); +//} + +//void Application::guiNewFrame() +//{ +// ImGui_ImplGlfwGL3_NewFrame(); +//} + +//void Application::guiReferenceManual() +//{ +// ImGui::ShowTestWindow(); +//} + +//void Application::guiRender() +//{ +// ImGui::Render(); +// ImGui_ImplGlfwGL3_RenderDrawData(ImGui::GetDrawData()); +//} + +// DAR FIXME Move all environment texture handling into the the LightDefinition. +void Application::createPictures() +{ + if (m_miss == 2 && !m_environment.empty()) + { + Picture* picture = new Picture(); + picture->load(m_environment, IMAGE_FLAG_2D | IMAGE_FLAG_ENV); // Special case for the spherical environment. + + m_mapPictures[std::string("environment")] = picture; + } +} + +void Application::createCameras() +{ + CameraDefinition camera; + + m_camera.getFrustum(camera.P, camera.U, camera.V, camera.W, true); + + m_cameras.push_back(camera); +} + +void Application::createLights() +{ + LightDefinition light; + + // Unused in environment lights. + light.position = make_float3(0.0f, 0.0f, 0.0f); + light.vecU = make_float3(1.0f, 0.0f, 0.0f); + light.vecV = make_float3(0.0f, 1.0f, 0.0f); + light.normal = make_float3(0.0f, 0.0f, 1.0f); + light.area = 1.0f; + light.emission = make_float3(1.0f, 1.0f, 1.0f); + + // The environment light is expected in sysData.lightDefinitions[0], but since there is only one, + // the sysData struct contains the data for the spherical HDR environment light when enabled. + // All other lights are indexed by their position inside the array. + switch (m_miss) + { + case 0: // No environment light at all. Faster than a zero emission constant environment! + default: + break; + + case 1: // Constant environment light. + case 2: // HDR Environment mapping with loaded texture. Texture handling happens in the Raytracer::initTextures(). + light.type = LIGHT_ENVIRONMENT; + light.area = 4.0f * M_PIf; // Unused. + + m_lights.push_back(light); // The environment light is always in m_lights[0]. + break; + } + + const int indexLight = static_cast(m_lights.size()); + float3 normal; + + switch (m_light) + { + case 0: // No area light. + default: + break; + + case 1: // Add a 1x1 square area light over the scene objects at y = 1.95 to fit into a 2x2x2 box. + light.type = LIGHT_PARALLELOGRAM; // A geometric area light with diffuse emission distribution function. + light.position = make_float3(-0.5f, 1.95f, -0.5f); // Corner position. + light.vecU = make_float3(1.0f, 0.0f, 0.0f); // To the right. + light.vecV = make_float3(0.0f, 0.0f, 1.0f); // To the front. + normal = cross(light.vecU, light.vecV); // Length of the cross product is the area. + light.area = length(normal); // Calculate the world space area of that rectangle, unit is [m^2] + light.normal = normal / light.area; // Normalized normal + light.emission = make_float3(10.0f); // Radiant exitance in Watt/m^2. + m_lights.push_back(light); + break; + + case 2: // Add a 4x4 square area light over the scene objects at y = 4.0. + light.type = LIGHT_PARALLELOGRAM; // A geometric area light with diffuse emission distribution function. + light.position = make_float3(-2.0f, 4.0f, -2.0f); // Corner position. + light.vecU = make_float3(4.0f, 0.0f, 0.0f); // To the right. + light.vecV = make_float3(0.0f, 0.0f, 4.0f); // To the front. + normal = cross(light.vecU, light.vecV); // Length of the cross product is the area. + light.area = length(normal); // Calculate the world space area of that rectangle, unit is [m^2] + light.normal = normal / light.area; // Normalized normal + light.emission = make_float3(10.0f); // Radiant exitance in Watt/m^2. + m_lights.push_back(light); + break; + } + + if (0 < m_light) // If there is an area light in the scene + { + // Create a material for this light. + const std::string reference("bench_shared_offscreen_area_light"); + + const int indexMaterial = static_cast(m_materialsGUI.size()); + + MaterialGUI materialGUI; + + materialGUI.name = reference; + materialGUI.nameTextureAlbedo = std::string(); + materialGUI.nameTextureCutout = std::string(); + materialGUI.indexBSDF = INDEX_BRDF_SPECULAR; + materialGUI.albedo = make_float3(0.0f); // Black + materialGUI.absorptionColor = make_float3(1.0f); // White means no absorption. + materialGUI.absorptionScale = 0.0f; // 0.0f means no absoption. + materialGUI.roughness = make_float2(0.1f); + materialGUI.ior = 1.5f; + materialGUI.thinwalled = true; + + m_materialsGUI.push_back(materialGUI); // at indexMaterial. + + m_mapMaterialReferences[reference] = indexMaterial; + + // Create the Triangles for this parallelogram light. + //m_mapGeometries[reference] = m_idGeometry; + + std::shared_ptr geometry(new sg::Triangles(m_idGeometry++)); + geometry->createParallelogram(light.position, light.vecU, light.vecV, light.normal); + + m_geometries.push_back(geometry); + + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + // instance->setTransform(trafo); // Instance default matrix is identity. + instance->setChild(geometry); + instance->setMaterial(indexMaterial); + instance->setLight(indexLight); + + m_scene->addChild(instance); + } +} + +//void Application::guiEventHandler() +//{ +// const ImGuiIO& io = ImGui::GetIO(); +// +// if (ImGui::IsKeyPressed(' ', false)) // Key Space: Toggle the GUI window display. +// { +// m_isVisibleGUI = !m_isVisibleGUI; +// } +// if (ImGui::IsKeyPressed('S', false)) // Key S: Save the current system options to a file "system_bench_shared_offscreen___.txt" +// { +// MY_VERIFY( saveSystemDescription() ); +// } +// if (ImGui::IsKeyPressed('P', false)) // Key P: Save the current output buffer with tonemapping into a *.png file. +// { +// MY_VERIFY( screenshot(true) ); +// } +// if (ImGui::IsKeyPressed('H', false)) // Key H: Save the current linear output buffer into a *.hdr file. +// { +// MY_VERIFY( screenshot(false) ); +// } +// +// const ImVec2 mousePosition = ImGui::GetMousePos(); // Mouse coordinate window client rect. +// const int x = int(mousePosition.x); +// const int y = int(mousePosition.y); +// +// switch (m_guiState) +// { +// case GUI_STATE_NONE: +// if (!io.WantCaptureMouse) // Only allow camera interactions to begin when interacting with the GUI. +// { +// if (ImGui::IsMouseDown(0)) // LMB down event? +// { +// m_camera.setBaseCoordinates(x, y); +// m_guiState = GUI_STATE_ORBIT; +// } +// else if (ImGui::IsMouseDown(1)) // RMB down event? +// { +// m_camera.setBaseCoordinates(x, y); +// m_guiState = GUI_STATE_DOLLY; +// } +// else if (ImGui::IsMouseDown(2)) // MMB down event? +// { +// m_camera.setBaseCoordinates(x, y); +// m_guiState = GUI_STATE_PAN; +// } +// else if (io.MouseWheel != 0.0f) // Mouse wheel zoom. +// { +// m_camera.zoom(io.MouseWheel); +// } +// } +// break; +// +// case GUI_STATE_ORBIT: +// if (ImGui::IsMouseReleased(0)) // LMB released? End of orbit mode. +// { +// m_guiState = GUI_STATE_NONE; +// } +// else +// { +// m_camera.orbit(x, y); +// } +// break; +// +// case GUI_STATE_DOLLY: +// if (ImGui::IsMouseReleased(1)) // RMB released? End of dolly mode. +// { +// m_guiState = GUI_STATE_NONE; +// } +// else +// { +// m_camera.dolly(x, y); +// } +// break; +// +// case GUI_STATE_PAN: +// if (ImGui::IsMouseReleased(2)) // MMB released? End of pan mode. +// { +// m_guiState = GUI_STATE_NONE; +// } +// else +// { +// m_camera.pan(x, y); +// } +// break; +// } +//} + +//void Application::guiWindow() +//{ +// if (!m_isVisibleGUI || m_mode == 1) // Use SPACE to toggle the display of the GUI window. +// { +// return; +// } +// +// bool refresh = false; +// +// ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiSetCond_FirstUseEver); +// +// ImGuiWindowFlags window_flags = 0; +// if (!ImGui::Begin("bench_shared_offscreen", nullptr, window_flags)) // No bool flag to omit the close button. +// { +// // Early out if the window is collapsed, as an optimization. +// ImGui::End(); +// return; +// } +// +// ImGui::PushItemWidth(-120); // Right-aligned, keep pixels for the labels. +// +// if (ImGui::CollapsingHeader("System")) +// { +// if (ImGui::DragFloat("Mouse Ratio", &m_mouseSpeedRatio, 0.1f, 0.1f, 1000.0f, "%.1f")) +// { +// m_camera.setSpeedRatio(m_mouseSpeedRatio); +// } +// if (ImGui::Checkbox("Present", &m_present)) +// { +// // No action needed, happens automatically. +// } +// if (ImGui::Combo("Camera", (int*) &m_lensShader, "Pinhole\0Fisheye\0Spherical\0\0")) +// { +// m_state.lensShader = m_lensShader; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +// if (ImGui::InputInt2("Resolution", &m_resolution.x, ImGuiInputTextFlags_EnterReturnsTrue)) // This requires RETURN to apply a new value. +// { +// m_resolution.x = std::max(1, m_resolution.x); +// m_resolution.y = std::max(1, m_resolution.y); +// +// m_camera.setResolution(m_resolution.x, m_resolution.y); +// m_rasterizer->setResolution(m_resolution.x, m_resolution.y); +// m_state.resolution = m_resolution; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +// if (ImGui::InputInt("SamplesSqrt", &m_samplesSqrt, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue)) +// { +// m_samplesSqrt = clamp(m_samplesSqrt, 1, 256); // Samples per pixel are squares in the range [1, 65536]. +// m_state.samplesSqrt = m_samplesSqrt; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +// if (ImGui::DragInt2("Path Lengths", &m_pathLengths.x, 1.0f, 0, 100)) +// { +// m_state.pathLengths = m_pathLengths; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +// if (ImGui::DragFloat("Scene Epsilon", &m_epsilonFactor, 1.0f, 0.0f, 10000.0f)) +// { +// m_state.epsilonFactor = m_epsilonFactor; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +// if (ImGui::DragFloat("Env Rotation", &m_environmentRotation, 0.001f, 0.0f, 1.0f)) +// { +// m_state.envRotation = m_environmentRotation; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +//#if USE_TIME_VIEW +// if (ImGui::DragFloat("Clock Factor", &m_clockFactor, 1.0f, 0.0f, 1000000.0f, "%.0f")) +// { +// m_state.clockFactor = m_clockFactor; +// m_raytracer->updateState(m_state); +// refresh = true; +// } +//#endif +// } +// +//#if !USE_TIME_VIEW +// if (ImGui::CollapsingHeader("Tonemapper")) +// { +// bool changed = false; +// if (ImGui::ColorEdit3("Balance", (float*) &m_tonemapperGUI.colorBalance)) +// { +// changed = true; +// } +// if (ImGui::DragFloat("Gamma", &m_tonemapperGUI.gamma, 0.01f, 0.01f, 10.0f)) // Must not get 0.0f +// { +// changed = true; +// } +// if (ImGui::DragFloat("White Point", &m_tonemapperGUI.whitePoint, 0.01f, 0.01f, 255.0f, "%.2f", 2.0f)) // Must not get 0.0f +// { +// changed = true; +// } +// if (ImGui::DragFloat("Burn Lights", &m_tonemapperGUI.burnHighlights, 0.01f, 0.0f, 10.0f, "%.2f")) +// { +// changed = true; +// } +// if (ImGui::DragFloat("Crush Blacks", &m_tonemapperGUI.crushBlacks, 0.01f, 0.0f, 1.0f, "%.2f")) +// { +// changed = true; +// } +// if (ImGui::DragFloat("Saturation", &m_tonemapperGUI.saturation, 0.01f, 0.0f, 10.0f, "%.2f")) +// { +// changed = true; +// } +// if (ImGui::DragFloat("Brightness", &m_tonemapperGUI.brightness, 0.01f, 0.0f, 100.0f, "%.2f", 2.0f)) +// { +// changed = true; +// } +// if (changed) +// { +// m_rasterizer->setTonemapper(m_tonemapperGUI); // This doesn't need a refresh. +// } +// } +//#endif // !USE_TIME_VIEW +// if (ImGui::CollapsingHeader("Materials")) +// { +// for (int i = 0; i < static_cast(m_materialsGUI.size()); ++i) +// { +// bool changed = false; +// +// MaterialGUI& materialGUI = m_materialsGUI[i]; +// +// if (ImGui::TreeNode((void*)(intptr_t) i, "%s", m_materialsGUI[i].name.c_str())) +// { +// if (ImGui::Combo("BxDF Type", (int*) &materialGUI.indexBSDF, "BRDF Diffuse\0BRDF Specular\0BSDF Specular\0BRDF GGX Smith\0BSDF GGX Smith\0\0")) +// { +// changed = true; +// } +// if (ImGui::ColorEdit3("Albedo", (float*) &materialGUI.albedo)) +// { +// changed = true; +// } +// if (ImGui::Checkbox("Thin-Walled", &materialGUI.thinwalled)) // Set this to true when using cutout opacity! +// { +// changed = true; +// } +// // Only show material parameters for the BxDFs which are affected by IOR and volume absorption. +// if (materialGUI.indexBSDF == INDEX_BSDF_SPECULAR || +// materialGUI.indexBSDF == INDEX_BSDF_GGX_SMITH) +// { +// if (ImGui::ColorEdit3("Absorption", (float*) &materialGUI.absorptionColor)) +// { +// changed = true; +// } +// if (ImGui::DragFloat("Absorption Scale", &materialGUI.absorptionScale, 0.01f, 0.0f, 1000.0f, "%.2f")) +// { +// changed = true; +// } +// if (ImGui::DragFloat("IOR", &materialGUI.ior, 0.01f, 0.0f, 10.0f, "%.2f")) +// { +// changed = true; +// } +// } +// // Only show material parameters for the BxDFs which are affected by roughness. +// if (materialGUI.indexBSDF == INDEX_BRDF_GGX_SMITH || +// materialGUI.indexBSDF == INDEX_BSDF_GGX_SMITH) +// { +// if (ImGui::DragFloat2("Roughness", reinterpret_cast(&materialGUI.roughness), 0.001f, 0.0f, 1.0f, "%.3f")) +// { +// // Clamp the microfacet roughness to working values minimum values. +// // FIXME When both roughness values fall below that threshold, use a specular BXDF instead. +// if (materialGUI.roughness.x < MICROFACET_MIN_ROUGHNESS) +// { +// materialGUI.roughness.x = MICROFACET_MIN_ROUGHNESS; +// } +// if (materialGUI.roughness.y < MICROFACET_MIN_ROUGHNESS) +// { +// materialGUI.roughness.y = MICROFACET_MIN_ROUGHNESS; +// } +// changed = true; +// } +// } +// +// if (changed) +// { +// m_raytracer->updateMaterial(i, materialGUI); +// refresh = true; +// } +// ImGui::TreePop(); +// } +// } +// } +// if (ImGui::CollapsingHeader("Lights")) +// { +// for (int i = 0; i < static_cast(m_lights.size()); ++i) +// { +// LightDefinition& light = m_lights[i]; +// +// // Allow to change the emission (radiant exitance in Watt/m^2 of the rectangle lights in the scene. +// if (light.type == LIGHT_PARALLELOGRAM) +// { +// if (ImGui::TreeNode((void*)(intptr_t) i, "Light %d", i)) +// { +// if (ImGui::DragFloat3("Emission", (float*) &light.emission, 0.1f, 0.0f, 10000.0f, "%.1f")) +// { +// m_raytracer->updateLight(i, light); +// refresh = true; +// } +// ImGui::TreePop(); +// } +// } +// } +// } +// +// ImGui::PopItemWidth(); +// +// ImGui::End(); +// +// if (refresh) +// { +// restartRendering(); +// } +//} + +//void Application::guiRenderingIndicator(const bool isRendering) +//{ +// // NVIDIA Green when rendering is complete. +// float r = 0.462745f; +// float g = 0.72549f; +// float b = 0.0f; +// +// if (isRendering) +// { +// // Neutral grey while rendering. +// r = 1.0f; +// g = 1.0f; +// b = 1.0f; +// } +// +// ImGuiStyle& style = ImGui::GetStyle(); +// +// // Use the GUI window title bar color as rendering indicator. Green when rendering is completed. +// style.Colors[ImGuiCol_TitleBg] = ImVec4(r * 0.6f, g * 0.6f, b * 0.6f, 0.6f); +// style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(r * 0.4f, g * 0.4f, b * 0.4f, 0.4f); +// style.Colors[ImGuiCol_TitleBgActive] = ImVec4(r * 0.8f, g * 0.8f, b * 0.8f, 0.8f); +//} + + +bool Application::loadSystemDescription(const std::string& filename) +{ + Parser parser; + + if (!parser.load(filename)) + { + std::cerr << "ERROR: loadSystemDescription() failed in loadString(" << filename << ")\n"; + return false; + } + + ParserTokenType tokenType; + std::string token; + + while ((tokenType = parser.getNextToken(token)) != PTT_EOF) + { + if (tokenType == PTT_UNKNOWN) + { + std::cerr << "ERROR: loadSystemDescription() " << filename << " (" << parser.getLine() << "): Unknown token type.\n"; + MY_ASSERT(!"Unknown token type."); + return false; + } + + if (tokenType == PTT_ID) + { + if (token == "strategy") + { + // Ignored in this renderer. Behaves like RS_INTERACTIVE_MULTI_GPU_LOCAL_COPY. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const int strategy = atoi(token.c_str()); + + std::cerr << "WARNING: loadSystemDescription() renderer strategy " << strategy << " ignored.\n"; + } + else if (token == "devicesMask") // DAR FIXME Kept the old name to be able to mix and match apps. + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_maskDevices = atoi(token.c_str()); + } + else if (token == "arenaSize") // In mega-bytes. + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_sizeArena = std::max(1, atoi(token.c_str())); + } + else if (token == "interop") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_interop = atoi(token.c_str()); + if (m_interop < 0 || 2 < m_interop) + { + std::cerr << "WARNING: loadSystemDescription() Invalid interop value " << m_interop << ", using interop 0 (host).\n"; + m_interop = 0; + } + } + else if (token == "peerToPeer") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_peerToPeer = atoi(token.c_str()); + } + else if (token == "present") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_present = (atoi(token.c_str()) != 0); + } + else if (token == "resolution") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_resolution.x = std::max(1, atoi(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_resolution.y = std::max(1, atoi(token.c_str())); + } + else if (token == "tileSize") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tileSize.x = std::max(1, atoi(token.c_str())); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tileSize.y = std::max(1, atoi(token.c_str())); + + // Make sure the values are power-of-two. + if (m_tileSize.x & (m_tileSize.x - 1)) + { + std::cerr << "ERROR: loadSystemDescription() tileSize.x = " << m_tileSize.x << " is not power-of-two, using 8.\n"; + m_tileSize.x = 8; + } + if (m_tileSize.y & (m_tileSize.y - 1)) + { + std::cerr << "ERROR: loadSystemDescription() tileSize.y = " << m_tileSize.y << " is not power-of-two, using 8.\n"; + m_tileSize.y = 8; + } + } + else if (token == "samplesSqrt") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_samplesSqrt = std::max(1, atoi(token.c_str())); // spp = m_samplesSqrt * m_samplesSqrt + } + else if (token == "miss") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_miss = atoi(token.c_str()); + } + else if (token == "envMap") + { + tokenType = parser.getNextToken(token); // Needs to be a filename in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + m_environment = token; + } + else if (token == "envRotation") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_environmentRotation = (float) atof(token.c_str()); + } + else if (token == "clockFactor") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_clockFactor = (float) atof(token.c_str()); + } + else if (token == "light") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_light = atoi(token.c_str()); + if (m_light < 0) + { + m_light = 0; + } + else if (2 < m_light) + { + m_light = 2; + } + } + else if (token == "pathLengths") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_pathLengths.x = atoi(token.c_str()); // min path length before Russian Roulette kicks in + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_pathLengths.y = atoi(token.c_str()); // max path length + } + else if (token == "epsilonFactor") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_epsilonFactor = (float) atof(token.c_str()); + } + else if (token == "lensShader") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_lensShader = static_cast(atoi(token.c_str())); + if (m_lensShader < LENS_SHADER_PINHOLE || LENS_SHADER_SPHERE < m_lensShader) + { + m_lensShader = LENS_SHADER_PINHOLE; + } + } + else if (token == "center") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float z = (float) atof(token.c_str()); + m_camera.m_center = make_float3(x, y, z); + m_camera.markDirty(); + } + else if (token == "camera") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_phi = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_theta = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_fov = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_camera.m_distance = (float) atof(token.c_str()); + m_camera.markDirty(); + } + else if (token == "prefixScreenshot") + { + tokenType = parser.getNextToken(token); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + m_prefixScreenshot = token; + } + else if (token == "gamma") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.gamma = (float) atof(token.c_str()); + } + else if (token == "colorBalance") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.colorBalance[2] = (float) atof(token.c_str()); + } + else if (token == "whitePoint") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.whitePoint = (float) atof(token.c_str()); + } + else if (token == "burnHighlights") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.burnHighlights = (float) atof(token.c_str()); + } + else if (token == "crushBlacks") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.crushBlacks = (float) atof(token.c_str()); + } + else if (token == "saturation") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.saturation = (float) atof(token.c_str()); + } + else if (token == "brightness") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + m_tonemapperGUI.brightness = (float) atof(token.c_str()); + } + else + { + std::cerr << "WARNING: loadSystemDescription() Unknown system option name: " << token << '\n'; + } + } + } + return true; +} + + +bool Application::saveSystemDescription() +{ + std::ostringstream description; + + description << "strategy " << m_strategy << '\n'; // Ignored in this renderer. + description << "devicesMask " << m_maskDevices << '\n'; + description << "arenaSize " << m_sizeArena << '\n'; + description << "interop " << m_interop << '\n'; + description << "present " << ((m_present) ? "1" : "0") << '\n'; + description << "resolution " << m_resolution.x << " " << m_resolution.y << '\n'; + description << "tileSize " << m_tileSize.x << " " << m_tileSize.y << '\n'; + description << "samplesSqrt " << m_samplesSqrt << '\n'; + description << "miss " << m_miss << '\n'; + if (!m_environment.empty()) + { + description << "envMap \"" << m_environment << "\"\n"; + } + description << "envRotation " << m_environmentRotation << '\n'; + description << "clockFactor " << m_clockFactor << '\n'; + description << "light " << m_light << '\n'; + description << "pathLengths " << m_pathLengths.x << " " << m_pathLengths.y << '\n'; + description << "epsilonFactor " << m_epsilonFactor << '\n'; + description << "lensShader " << m_lensShader << '\n'; + description << "center " << m_camera.m_center.x << " " << m_camera.m_center.y << " " << m_camera.m_center.z << '\n'; + description << "camera " << m_camera.m_phi << " " << m_camera.m_theta << " " << m_camera.m_fov << " " << m_camera.m_distance << '\n'; + if (!m_prefixScreenshot.empty()) + { + description << "prefixScreenshot \"" << m_prefixScreenshot << "\"\n"; + } + description << "gamma " << m_tonemapperGUI.gamma << '\n'; + description << "colorBalance " << m_tonemapperGUI.colorBalance[0] << " " << m_tonemapperGUI.colorBalance[1] << " " << m_tonemapperGUI.colorBalance[2] << '\n'; + description << "whitePoint " << m_tonemapperGUI.whitePoint << '\n'; + description << "burnHighlights " << m_tonemapperGUI.burnHighlights << '\n'; + description << "crushBlacks " << m_tonemapperGUI.crushBlacks << '\n'; + description << "saturation " << m_tonemapperGUI.saturation << '\n'; + description << "brightness " << m_tonemapperGUI.brightness << '\n'; + + const std::string filename = std::string("system_bench_shared_offscreen_") + getDateTime() + std::string(".txt"); + const bool success = saveString(filename, description.str()); + if (success) + { + std::cout << filename << '\n'; // Print out the filename to indicate success. + } + return success; +} + +void Application::appendInstance(std::shared_ptr& group, + std::shared_ptr geometry, + const dp::math::Mat44f& matrix, + const std::string& reference, + unsigned int& idInstance) +{ + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + const float trafo[12] = + { + matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], + matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1], + matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2] + }; + + MY_ASSERT(matrix[0][3] == 0.0f && + matrix[1][3] == 0.0f && + matrix[2][3] == 0.0f && + matrix[3][3] == 1.0f); + + std::shared_ptr instance(new sg::Instance(idInstance++)); + instance->setTransform(trafo); + instance->setChild(geometry); + + int indexMaterial = -1; + std::map::const_iterator itm = m_mapMaterialReferences.find(reference); + if (itm != m_mapMaterialReferences.end()) + { + indexMaterial = itm->second; + } + else + { + std::cerr << "WARNING: appendInstance() No material found for " << reference << ". Trying default.\n"; + + std::map::const_iterator itmd = m_mapMaterialReferences.find(std::string("default")); + if (itmd != m_mapMaterialReferences.end()) + { + indexMaterial = itmd->second; + } + else + { + std::cerr << "ERROR: appendInstance() No default material found\n"; + } + } + + instance->setMaterial(indexMaterial); + + group->addChild(instance); +} + +bool Application::loadSceneDescription(const std::string& filename) +{ + Parser parser; + + if (!parser.load(filename)) + { + std::cerr << "ERROR: loadSceneDescription() failed in loadString(" << filename << ")\n"; + return false; + } + + ParserTokenType tokenType; + std::string token; + + // Reusing some math routines from the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + // Note that matrices in the nvpro-pipeline are defined row-major but are multiplied from the right, + // which means the order of transformations is simply from left to right matrix, means first matrix is applied first, + // but puts the translation into the last row elements (12 to 14). + + std::stack stackMatrix; + std::stack stackInverse; + std::stack stackOrientation; + + // Initialize all current transformations with identity. + dp::math::Mat44f curMatrix(dp::math::cIdentity44f); // object to world + dp::math::Mat44f curInverse(dp::math::cIdentity44f); // world to object + dp::math::Quatf curOrientation(0.0f, 0.0f, 0.0f, 1.0f); // object to world + + // Material parameters. + float3 curAlbedo = make_float3(1.0f); + float2 curRoughness = make_float2(0.1f); + float3 curAbsorptionColor = make_float3(1.0f); + float curAbsorptionScale = 0.0f; // 0.0f means off. + float curIOR = 1.5f; + bool curThinwalled = false; + std::string curAlbedoTexture; + std::string curCutoutTexture; + + // FIXME Add a mechanism to specify albedo textures per material and make that resetable or add a push/pop mechanism for materials. + // E.g. special case filename "none" which translates to empty filename, which switches off albedo textures. + // Get rid of the single hardcoded texture and the toggle. + + while ((tokenType = parser.getNextToken(token)) != PTT_EOF) + { + if (tokenType == PTT_UNKNOWN) + { + std::cerr << "ERROR: loadSceneDescription() " << filename << " (" << parser.getLine() << "): Unknown token type.\n"; + MY_ASSERT(!"Unknown token type."); + return false; + } + + if (tokenType == PTT_ID) + { + std::map::const_iterator itKeyword = m_mapKeywordScene.find(token); + if (itKeyword == m_mapKeywordScene.end()) + { + std::cerr << "WARNING: loadSceneDescription() Unknown token " << token << " ignored.\n"; + // MY_ASSERT(!"loadSceneDescription() Unknown token ignored."); + continue; // Just keep getting the next token until a known keyword is found. + } + + const KeywordScene keyword = itKeyword->second; + + switch (keyword) + { + case KS_ALBEDO: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAlbedo.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAlbedo.y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAlbedo.z = (float) atof(token.c_str()); + break; + + case KS_ALBEDO_TEXTURE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + curAlbedoTexture = token; + break; + + case KS_CUTOUT_TEXTURE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_STRING); + convertPath(token); + curCutoutTexture = token; + break; + + case KS_ROUGHNESS: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curRoughness.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curRoughness.y = (float) atof(token.c_str()); + break; + + case KS_ABSORPTION: // For convenience this is an absoption color used to calculate the absorption coefficient. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionColor.x = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionColor.y = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionColor.z = (float) atof(token.c_str()); + break; + + case KS_ABSORPTION_SCALE: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curAbsorptionScale = (float) atof(token.c_str()); + break; + + case KS_IOR: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curIOR = (float) atof(token.c_str()); + break; + + case KS_THINWALLED: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + curThinwalled = (atoi(token.c_str()) != 0); + break; + + case KS_MATERIAL: + { + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); // Internal material name. If there are duplicates the last name wins. + //MY_ASSERT(tokenType == PTT_ID); // Allow any type of identifier, including strings and numbers. + + std::string nameMaterial; + tokenType = parser.getNextToken(nameMaterial); // The actual material name. + //MY_ASSERT(tokenType == PTT_ID); // Allow any type of identifier, including string and numbers. + + // Create this material in the GUI. + const int indexMaterial = static_cast(m_materialsGUI.size()); + + MaterialGUI materialGUI; + + materialGUI.name = nameMaterialReference; + + materialGUI.indexBSDF = INDEX_BRDF_DIFFUSE; // Set a default BSDF. // Base direct callable index for the BXDFs. + // Handle all cases to get the correct error. + // DAR FIXME Put these into a std::map and do a fined here. + if (nameMaterial == std::string("brdf_diffuse")) + { + materialGUI.indexBSDF = INDEX_BRDF_DIFFUSE; + } + else if (nameMaterial == std::string("brdf_specular")) + { + materialGUI.indexBSDF = INDEX_BRDF_SPECULAR; + } + else if (nameMaterial == std::string("bsdf_specular")) + { + materialGUI.indexBSDF = INDEX_BSDF_SPECULAR; + } + else if (nameMaterial == std::string("brdf_ggx_smith")) + { + materialGUI.indexBSDF = INDEX_BRDF_GGX_SMITH; + } + else if (nameMaterial == std::string("bsdf_ggx_smith")) + { + materialGUI.indexBSDF = INDEX_BSDF_GGX_SMITH; + } + else + { + std::cerr << "WARNING: loadSceneDescription() unknown material " << nameMaterial << '\n'; + } + + materialGUI.nameTextureAlbedo = curAlbedoTexture; + materialGUI.nameTextureCutout = curCutoutTexture; + materialGUI.albedo = curAlbedo; + materialGUI.absorptionColor = curAbsorptionColor; + materialGUI.absorptionScale = curAbsorptionScale; + materialGUI.roughness = curRoughness; + materialGUI.ior = curIOR; + materialGUI.thinwalled = curThinwalled; + + m_materialsGUI.push_back(materialGUI); // at indexMaterial. + + m_mapMaterialReferences[nameMaterialReference] = indexMaterial; + + // Cache the referenced pictures to load them only once. + if (!curAlbedoTexture.empty()) + { + std::map::const_iterator it = m_mapPictures.find(curAlbedoTexture); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + picture->load(curAlbedoTexture, IMAGE_FLAG_2D); + + m_mapPictures[curAlbedoTexture] = picture; + } + } + + if (!curCutoutTexture.empty()) + { + std::map::const_iterator it = m_mapPictures.find(curCutoutTexture); + if (it == m_mapPictures.end()) + { + Picture* picture = new Picture(); + picture->load(curCutoutTexture, IMAGE_FLAG_2D); + + m_mapPictures[curCutoutTexture] = picture; + } + } + + // Special handling: Texture names are not persistent state, but single shot. + // Otherwise there would need to be a mechanism to reset the name inside the scene description. + // Think about push/pop for materials? + curAlbedoTexture.clear(); + curCutoutTexture.clear(); + } + break; + + case KS_IDENTITY: + curMatrix = dp::math::cIdentity44f; + curInverse = dp::math::cIdentity44f; + curOrientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); // identity orientation + break; + + case KS_PUSH: + stackMatrix.push(curMatrix); + stackInverse.push(curInverse); + stackOrientation.push(curOrientation); + break; + + case KS_POP: + if (!stackMatrix.empty()) + { + MY_ASSERT(!stackInverse.empty()); + MY_ASSERT(!stackOrientation.empty()); + curMatrix = stackMatrix.top(); + stackMatrix.pop(); + curInverse = stackInverse.top(); + stackInverse.pop(); + curOrientation = stackOrientation.top(); + stackOrientation.pop(); + } + else + { + std::cerr << "ERROR: loadSceneDescription() pop on empty stack. Resetting to identity.\n"; + curMatrix = dp::math::cIdentity44f; + curInverse = dp::math::cIdentity44f; + curOrientation = dp::math::Quatf(0.0f, 0.0f, 0.0f, 1.0f); // identity orientation + } + break; + + case KS_ROTATE: + { + dp::math::Vec3f axis; + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + axis[2] = (float) atof(token.c_str()); + axis.normalize(); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float angle = dp::math::degToRad((float) atof(token.c_str())); + + dp::math::Quatf rotation(axis, angle); + curOrientation *= rotation; + + dp::math::Mat44f matrix(rotation, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); // Zero translation to get a Mat44f back. + curMatrix *= matrix; // DEBUG No need for the local matrix variable. + + // Inverse. Opposite order of matrix multiplications to make M * M^-1 = I. + dp::math::Quatf rotationInv(axis, -angle); + dp::math::Mat44f matrixInv(rotationInv, dp::math::Vec3f(0.0f, 0.0f, 0.0f)); // Zero translation to get a Mat44f back. + curInverse = matrixInv * curInverse; // DEBUG No need for the local matrixInv variable. + } + break; + + case KS_SCALE: + { + dp::math::Mat44f scaling(dp::math::cIdentity44f); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[0][0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[1][1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + scaling[2][2] = (float) atof(token.c_str()); + + curMatrix *= scaling; + + // Inverse. // DEBUG Requires scalings to not contain zeros. + scaling[0][0] = 1.0f / scaling[0][0]; + scaling[1][1] = 1.0f / scaling[1][1]; + scaling[2][2] = 1.0f / scaling[2][2]; + + curInverse = scaling * curInverse; + } + break; + + case KS_TRANSLATE: + { + dp::math::Mat44f translation(dp::math::cIdentity44f); + + // Translation is in the third row in dp::math::Mat44f. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][0] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][1] = (float) atof(token.c_str()); + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + translation[3][2] = (float) atof(token.c_str()); + + curMatrix *= translation; + + translation[3][0] = -translation[3][0]; + translation[3][1] = -translation[3][1]; + translation[3][2] = -translation[3][2]; + + curInverse = translation * curInverse; + } + break; + + case KS_MODEL: + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_ID); + + if (token == "plane") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int upAxis = atoi(token.c_str()); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "plane_" << tessU << "_" << tessV << "_" << upAxis; + + std::shared_ptr geometry; + + // DAR HACK DEBUG Disable model instancing + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry.str()] = m_idGeometry; // PERF Equal to static_cast(m_geometries.size()); + + geometry = std::make_shared(m_idGeometry++); + geometry->createPlane(tessU, tessV, upAxis); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "box") + { + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + // FIXME Implement tessellation. Must be a single value to get even distributions across edges. + std::string keyGeometry("box_1_1"); + + std::shared_ptr geometry; + + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createBox(); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "sphere") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + // Theta is in the range [0.0f, 1.0f] and 1.0f means closed sphere, smaller values open the noth pole. + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float theta = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "sphere_" << tessU << "_" << tessV << "_" << theta; + + std::shared_ptr geometry; + + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createSphere(tessU, tessV, 1.0f, theta * M_PIf); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "torus") + { + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessU = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const unsigned int tessV = atoi(token.c_str()); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float innerRadius = float(atof(token.c_str())); + + tokenType = parser.getNextToken(token); + MY_ASSERT(tokenType == PTT_VAL); + const float outerRadius = float(atof(token.c_str())); + + std::string nameMaterialReference; + tokenType = parser.getNextToken(nameMaterialReference); + + std::ostringstream keyGeometry; + keyGeometry << "torus_" << tessU << "_" << tessV << "_" << innerRadius << "_" << outerRadius; + + std::shared_ptr geometry; + + //std::map::const_iterator itg = m_mapGeometries.find(keyGeometry.str()); + //if (itg == m_mapGeometries.end()) + //{ + // m_mapGeometries[keyGeometry.str()] = m_idGeometry; + + geometry = std::make_shared(m_idGeometry++); + geometry->createTorus(tessU, tessV, innerRadius, outerRadius); + + m_geometries.push_back(geometry); + //} + //else + //{ + // geometry = std::dynamic_pointer_cast(m_geometries[itg->second]); + //} + + appendInstance(m_scene, geometry, curMatrix, nameMaterialReference, m_idInstance); + } + else if (token == "assimp") + { + std::string filenameModel; + tokenType = parser.getNextToken(filenameModel); // Needs to be a path in quotation marks. + MY_ASSERT(tokenType == PTT_STRING); + convertPath(filenameModel); + + std::shared_ptr model = createASSIMP(filenameModel); + + // nvpro-pipeline matrices are row-major multiplied from the right, means the translation is in the last row. Transpose! + const float trafo[12] = + { + curMatrix[0][0], curMatrix[1][0], curMatrix[2][0], curMatrix[3][0], + curMatrix[0][1], curMatrix[1][1], curMatrix[2][1], curMatrix[3][1], + curMatrix[0][2], curMatrix[1][2], curMatrix[2][2], curMatrix[3][2] + }; + + MY_ASSERT(curMatrix[0][3] == 0.0f && + curMatrix[1][3] == 0.0f && + curMatrix[2][3] == 0.0f && + curMatrix[3][3] == 1.0f); + + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + instance->setTransform(trafo); + instance->setChild(model); + + m_scene->addChild(instance); + } + break; + + default: + std::cerr << "ERROR: loadSceneDescription() Unexpected KeywordScene value " << keyword << " ignored.\n"; + MY_ASSERT(!"ERROR: loadSceneDescription() Unexpected KeywordScene value"); + break; + } + } + } + + std::cout << "loadSceneDescription() m_idGroup = " << m_idGroup << ", m_idInstance = " << m_idInstance << ", m_idGeometry = " << m_idGeometry << '\n'; + + return true; +} + +bool Application::loadString(const std::string& filename, std::string& text) +{ + std::ifstream inputStream(filename); + + if (!inputStream) + { + std::cerr << "ERROR: loadString() Failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: loadString() Failed to read file " << filename << '\n'; + return false; + } + + text = data.str(); + return true; +} + +bool Application::saveString(const std::string& filename, const std::string& text) +{ + std::ofstream outputStream(filename); + + if (!outputStream) + { + std::cerr << "ERROR: saveString() Failed to open file " << filename << '\n'; + return false; + } + + outputStream << text; + + if (outputStream.fail()) + { + std::cerr << "ERROR: saveString() Failed to write file " << filename << '\n'; + return false; + } + + return true; +} + +std::string Application::getDateTime() +{ +#if defined(_WIN32) + SYSTEMTIME time; + GetLocalTime(&time); +#elif defined(__linux__) + time_t rawtime; + struct tm* ts; + time(&rawtime); + ts = localtime(&rawtime); +#else + #error "OS not supported." +#endif + + std::ostringstream oss; + +#if defined( _WIN32 ) + oss << time.wYear; + if (time.wMonth < 10) + { + oss << '0'; + } + oss << time.wMonth; + if (time.wDay < 10) + { + oss << '0'; + } + oss << time.wDay << '_'; + if (time.wHour < 10) + { + oss << '0'; + } + oss << time.wHour; + if (time.wMinute < 10) + { + oss << '0'; + } + oss << time.wMinute; + if (time.wSecond < 10) + { + oss << '0'; + } + oss << time.wSecond << '_'; + if (time.wMilliseconds < 100) + { + oss << '0'; + } + if (time.wMilliseconds < 10) + { + oss << '0'; + } + oss << time.wMilliseconds; +#elif defined(__linux__) + oss << ts->tm_year; + if (ts->tm_mon < 10) + { + oss << '0'; + } + oss << ts->tm_mon; + if (ts->tm_mday < 10) + { + oss << '0'; + } + oss << ts->tm_mday << '_'; + if (ts->tm_hour < 10) + { + oss << '0'; + } + oss << ts->tm_hour; + if (ts->tm_min < 10) + { + oss << '0'; + } + oss << ts->tm_min; + if (ts->tm_sec < 10) + { + oss << '0'; + } + oss << ts->tm_sec << '_'; + oss << "000"; // No milliseconds available. +#else + #error "OS not supported." +#endif + + return oss.str(); +} + +static void updateAABB(float3& minimum, float3& maximum, const float3& v) +{ + if (v.x < minimum.x) + { + minimum.x = v.x; + } + else if (maximum.x < v.x) + { + maximum.x = v.x; + } + + if (v.y < minimum.y) + { + minimum.y = v.y; + } + else if (maximum.y < v.y) + { + maximum.y = v.y; + } + + if (v.z < minimum.z) + { + minimum.z = v.z; + } + else if (maximum.z < v.z) + { + maximum.z = v.z; + } +} + +//static void calculateTexcoordsSpherical(std::vector& attributes, const std::vector& indices) +//{ +// dp::math::Vec3f center(0.0f, 0.0f, 0.0f); +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// center += attributes[i].vertex; +// } +// center /= (float) attributes.size(); +// +// float u; +// float v; +// +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// dp::math::Vec3f p = attributes[i].vertex - center; +// if (FLT_EPSILON < fabsf(p[1])) +// { +// u = 0.5f * atan2f(p[0], -p[1]) / dp::math::PI + 0.5f; +// } +// else +// { +// u = (0.0f <= p[0]) ? 0.75f : 0.25f; +// } +// float d = sqrtf(dp::math::square(p[0]) + dp::math::square(p[1])); +// if (FLT_EPSILON < d) +// { +// v = atan2f(p[2], d) / dp::math::PI + 0.5f; +// } +// else +// { +// v = (0.0f <= p[2]) ? 1.0f : 0.0f; +// } +// attributes[i].texcoord0 = dp::math::Vec3f(u, v, 0.0f); +// } +// +// //// The code from the environment texture lookup. +// //for (size_t i = 0; i < attributes.size(); ++i) +// //{ +// // dp::math::Vec3f R = attributes[i].vertex - center; +// // dp::math::normalize(R); +// +// // // The seam u == 0.0 == 1.0 is in positive z-axis direction. +// // // Compensate for the environment rotation done inside the direct lighting. +// // const float u = (atan2f(R[0], -R[2]) + dp::math::PI) * 0.5f / dp::math::PI; +// // const float theta = acosf(-R[1]); // theta == 0.0f is south pole, theta == M_PIf is north pole. +// // const float v = theta / dp::math::PI; // Texture is with origin at lower left, v == 0.0f is south pole. +// +// // attributes[i].texcoord0 = dp::math::Vecf(u, v, 0.0f); +// //} +//} + + +// Calculate texture tangents based on the texture coordinate gradients. +// Doesn't work when all texture coordinates are identical! Thats the reason for the other routine below. +//static void calculateTangents(std::vector& attributes, const std::vector& indices) +//{ +// for (size_t i = 0; i < indices.size(); i += 4) +// { +// unsigned int i0 = indices[i ]; +// unsigned int i1 = indices[i + 1]; +// unsigned int i2 = indices[i + 2]; +// +// dp::math::Vec3f e0 = attributes[i1].vertex - attributes[i0].vertex; +// dp::math::Vec3f e1 = attributes[i2].vertex - attributes[i0].vertex; +// dp::math::Vec2f d0 = dp::math::Vec2f(attributes[i1].texcoord0) - dp::math::Vec2f(attributes[i0].texcoord0); +// dp::math::Vec2f d1 = dp::math::Vec2f(attributes[i2].texcoord0) - dp::math::Vec2f(attributes[i0].texcoord0); +// attributes[i0].tangent += d1[1] * e0 - d0[1] * e1; +// +// e0 = attributes[i2].vertex - attributes[i1].vertex; +// e1 = attributes[i0].vertex - attributes[i1].vertex; +// d0 = dp::math::Vec2f(attributes[i2].texcoord0) - dp::math::Vec2f(attributes[i1].texcoord0); +// d1 = dp::math::Vec2f(attributes[i0].texcoord0) - dp::math::Vec2f(attributes[i1].texcoord0); +// attributes[i1].tangent += d1[1] * e0 - d0[1] * e1; +// +// e0 = attributes[i0].vertex - attributes[i2].vertex; +// e1 = attributes[i1].vertex - attributes[i2].vertex; +// d0 = dp::math::Vec2f(attributes[i0].texcoord0) - dp::math::Vec2f(attributes[i2].texcoord0); +// d1 = dp::math::Vec2f(attributes[i1].texcoord0) - dp::math::Vec2f(attributes[i2].texcoord0); +// attributes[i2].tangent += d1[1] * e0 - d0[1] * e1; +// } +// +// for (size_t i = 0; i < attributes.size(); ++i) +// { +// dp::math::Vec3f tangent(attributes[i].tangent); +// dp::math::normalize(tangent); // This normalizes the sums from above! +// +// dp::math::Vec3f normal(attributes[i].normal); +// +// dp::math::Vec3f bitangent = normal ^ tangent; +// dp::math::normalize(bitangent); +// +// tangent = bitangent ^ normal; +// dp::math::normalize(tangent); +// +// attributes[i].tangent = tangent; +// +//#if USE_BITANGENT +// attributes[i].bitangent = bitantent; +//#endif +// } +//} + +// Calculate (geometry) tangents with the global tangent direction aligned to the biggest AABB extend of this part. +void Application::calculateTangents(std::vector& attributes, const std::vector& indices) +{ + MY_ASSERT(3 <= indices.size()); + + // Initialize with the first vertex to be able to use else-if comparisons in updateAABB(). + float3 aabbLo = attributes[indices[0]].vertex; + float3 aabbHi = attributes[indices[0]].vertex; + + // Build an axis aligned bounding box. + for (size_t i = 0; i < indices.size(); i += 3) + { + unsigned int i0 = indices[i ]; + unsigned int i1 = indices[i + 1]; + unsigned int i2 = indices[i + 2]; + + updateAABB(aabbLo, aabbHi, attributes[i0].vertex); + updateAABB(aabbLo, aabbHi, attributes[i1].vertex); + updateAABB(aabbLo, aabbHi, attributes[i2].vertex); + } + + // Get the longest extend and use that as general tangent direction. + const float3 extents = aabbHi - aabbLo; + + float f = extents.x; + int maxComponent = 0; + + if (f < extents.y) + { + f = extents.y; + maxComponent = 1; + } + if (f < extents.z) + { + maxComponent = 2; + } + + float3 direction; + float3 bidirection; + + switch (maxComponent) + { + case 0: // x-axis + default: + direction = make_float3(1.0f, 0.0f, 0.0f); + bidirection = make_float3(0.0f, 1.0f, 0.0f); + break; + case 1: // y-axis // DEBUG It might make sense to keep these directions aligned to the global coordinate system. Use the same coordinates as for z-axis then. + direction = make_float3(0.0f, 1.0f, 0.0f); + bidirection = make_float3(0.0f, 0.0f, -1.0f); + break; + case 2: // z-axis + direction = make_float3(0.0f, 0.0f, -1.0f); + bidirection = make_float3(0.0f, 1.0f, 0.0f); + break; + } + + // Build an ortho-normal basis with the existing normal. + for (size_t i = 0; i < attributes.size(); ++i) + { + float3 tangent = direction; + float3 bitangent = bidirection; + // float3 normal = attributes[i].normal; + float3 normal; + normal.x = attributes[i].normal.x; + normal.y = attributes[i].normal.y; + normal.z = attributes[i].normal.z; + + if (0.001f < 1.0f - fabsf(dot(normal, tangent))) + { + bitangent = normalize(cross(normal, tangent)); + tangent = normalize(cross(bitangent, normal)); + } + else // Normal and tangent direction too collinear. + { + MY_ASSERT(0.001f < 1.0f - fabsf(dot(bitangent, normal))); + tangent = normalize(cross(bitangent, normal)); + //bitangent = normalize(cross(normal, tangent)); + } + attributes[i].tangent = tangent; + } +} + +bool Application::screenshot(const bool tonemap) +{ + ILboolean hasImage = false; + + const int spp = m_samplesSqrt * m_samplesSqrt; // Add the samples per pixel to the filename for quality comparisons. + + std::ostringstream path; + + path << m_prefixScreenshot << "_" << spp << "spp_" << getDateTime(); + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + + ilBindImage(imageID); + ilActiveImage(0); + ilActiveFace(0); + + ilDisable(IL_ORIGIN_SET); + + const float4* bufferHost = reinterpret_cast(m_raytracer->getOutputBufferHost()); + + if (tonemap) + { + // Store a tonemapped RGB8 *.png image + path << ".png"; + + if (ilTexImage(m_resolution.x, m_resolution.y, 1, 3, IL_RGB, IL_UNSIGNED_BYTE, nullptr)) + { + uchar3* dst = reinterpret_cast(ilGetData()); + + const float invGamma = 1.0f / m_tonemapperGUI.gamma; + const float3 colorBalance = make_float3(m_tonemapperGUI.colorBalance[0], m_tonemapperGUI.colorBalance[1], m_tonemapperGUI.colorBalance[2]); + const float invWhitePoint = m_tonemapperGUI.brightness / m_tonemapperGUI.whitePoint; + const float burnHighlights = m_tonemapperGUI.burnHighlights; + const float crushBlacks = m_tonemapperGUI.crushBlacks + m_tonemapperGUI.crushBlacks + 1.0f; + const float saturation = m_tonemapperGUI.saturation; + + for (int y = 0; y < m_resolution.y; ++y) + { + for (int x = 0; x < m_resolution.x; ++x) + { + const int idx = y * m_resolution.x + x; + + // Tonemapper. // PERF Add a native CUDA kernel doing this. + float3 hdrColor = make_float3(bufferHost[idx]); + float3 ldrColor = invWhitePoint * colorBalance * hdrColor; + ldrColor *= ((ldrColor * burnHighlights) + 1.0f) / (ldrColor + 1.0f); + + float luminance = dot(ldrColor, make_float3(0.3f, 0.59f, 0.11f)); + ldrColor = lerp(make_float3(luminance), ldrColor, saturation); // This can generate negative values for saturation > 1.0f! + ldrColor = fmaxf(make_float3(0.0f), ldrColor); // Prevent negative values. + + luminance = dot(ldrColor, make_float3(0.3f, 0.59f, 0.11f)); + if (luminance < 1.0f) + { + const float3 crushed = powf(ldrColor, crushBlacks); + ldrColor = lerp(crushed, ldrColor, sqrtf(luminance)); + ldrColor = fmaxf(make_float3(0.0f), ldrColor); // Prevent negative values. + } + ldrColor = clamp(powf(ldrColor, invGamma), 0.0f, 1.0f); // Saturate, clamp to range [0.0f, 1.0f]. + + dst[idx] = make_uchar3((unsigned char) (ldrColor.x * 255.0f), + (unsigned char) (ldrColor.y * 255.0f), + (unsigned char) (ldrColor.z * 255.0f)); + } + } + hasImage = true; + } + } + else + { + // Store the float4 linear output buffer as *.hdr image. + // FIXME Add a half float conversion and store as *.exr. (Pre-built DevIL 1.7.8 supports EXR, DevIL 1.8.0 doesn't!) + path << ".hdr"; + + hasImage = ilTexImage(m_resolution.x, m_resolution.y, 1, 4, IL_RGBA, IL_FLOAT, (void*) bufferHost); + } + + if (hasImage) + { + ilEnable(IL_FILE_OVERWRITE); // By default, always overwrite + + std::string filename = path.str(); + convertPath(filename); + + if (ilSaveImage((const ILstring) filename.c_str())) + { + ilDeleteImages(1, &imageID); + + std::cout << filename << '\n'; // Print out filename to indicate that a screenshot has been taken. + return true; + } + } + + // There was an error when reaching this code. + ILenum error = ilGetError(); // DEBUG + std::cerr << "ERROR: screenshot() failed with IL error " << error << '\n'; + + while (ilGetError() != IL_NO_ERROR) // Clean up errors. + { + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + + return false; +} + +// Convert between slashes and backslashes in paths depending on the operating system +void Application::convertPath(std::string& path) +{ +#if defined(_WIN32) + std::string::size_type pos = path.find("/", 0); + while (pos != std::string::npos) + { + path[pos] = '\\'; + pos = path.find("/", pos); + } +#elif defined(__linux__) + std::string::size_type pos = path.find("\\", 0); + while (pos != std::string::npos) + { + path[pos] = '/'; + pos = path.find("\\", pos); + } +#endif +} + +void Application::convertPath(char* path) +{ +#if defined(_WIN32) + for (size_t i = 0; i < strlen(path); ++i) + { + if (path[i] == '/') + { + path[i] = '\\'; + } + } +#elif defined(__linux__) + for (size_t i = 0; i < strlen(path); ++i) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +#endif +} + +// DAR HACK DEBUG Small helper class to generate a lot of different RGB8 PNG images to be used for texture sharing benchmarks. +bool Application::generatePNG(const unsigned int width, + const unsigned int height, + const unsigned char r, + const unsigned char g, + const unsigned char b) +{ + static unsigned int number = 0; + + std::ostringstream path; + + path << m_prefixScreenshot << "_" << number << ".png"; + + ++number; // Each call this function will generate a new runnign number. + + ILboolean hasImage = false; + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + + ilBindImage(imageID); + ilActiveImage(0); + ilActiveFace(0); + + ilDisable(IL_ORIGIN_SET); + + if (ilTexImage(width, height, 1, 3, IL_RGB, IL_UNSIGNED_BYTE, nullptr)) + { + uchar3* dst = reinterpret_cast(ilGetData()); + + for (unsigned int y = 0; y < height; ++y) + { + for (unsigned int x = 0; x < width; ++x) + { + const unsigned int idx = y * width + x; + + dst[idx] = make_uchar3(r, g, b); + } + } + hasImage = true; + } + + if (hasImage) + { + ilEnable(IL_FILE_OVERWRITE); // By default, always overwrite + + std::string filename = path.str(); + convertPath(filename); + + if (ilSaveImage((const ILstring) filename.c_str())) + { + ilDeleteImages(1, &imageID); + + std::cout << filename << '\n'; // Print out filename to indicate that a screenshot has been taken. + return true; + } + } + + // There was an error when reaching this code. + ILenum error = ilGetError(); // DEBUG + std::cerr << "ERROR: screenshot() failed with IL error " << error << '\n'; + + while (ilGetError() != IL_NO_ERROR) // Clean up errors. + { + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + + return false; +} diff --git a/apps/bench_shared_offscreen/src/Arena.cpp b/apps/bench_shared_offscreen/src/Arena.cpp new file mode 100644 index 00000000..199b8329 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Arena.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include + +#include "inc/Arena.h" + +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include +#include +#include + + +#if !defined(NDEBUG) +// Only used inside a MY_ASSERT(). Prevent unused function warning in debug targets. +static bool isPowerOfTwo(const size_t s) +{ + return (s != 0) && ((s & (s - 1)) == 0); +} +#endif + + +namespace cuda +{ + + Block::Block(cuda::Arena* arena, const CUdeviceptr addr, const size_t size, const CUdeviceptr ptr) + : m_arena(arena) + , m_addr(addr) + , m_size(size) + , m_ptr(ptr) + { + } + + bool Block::isValid() const + { + return (m_ptr != 0); + } + + bool Block::operator<(const cuda::Block& rhs) + { + return (m_addr < rhs.m_addr); + } + + + Arena::Arena(const size_t size) + : m_size((size + 255) & ~255) // Make sure the Arena has a multiple of 256 bytes size. + { + // This can fail with CUDA OOM errors and then none of the further allocations will work. + // Same as if there would be no arena. + CU_CHECK( cuMemAlloc(&m_addr, m_size) ); + + // Make sure the base address of the arena is 256-byte aligned. + MY_ASSERT((m_addr & 255) == 0); + + // When the Arena is created, there is one big free block of the full size. + // Free blocks do not have a user pointer assigned, only allocated blocks are valid. + m_blocksFree.push_back(cuda::Block(this, m_addr, m_size, 0)); + } + + Arena::~Arena() + { + // By design there is no need to clear this here. + // These are not pointers and there is no Block destructor. + // m_blocksFree.clear(); + + // Wipe the arena. + // All active blocks after this point are invalid and must not be in use anymore. + CU_CHECK_NO_THROW( cuMemFree(m_addr) ); + } + + bool Arena::allocBlock(cuda::Block& block, const size_t size, const size_t alignment, const cuda::Usage usage) + { + const size_t maskAligned = alignment - 1; + + for (std::list::iterator it = m_blocksFree.begin(); it != m_blocksFree.end(); ++it) + { + if (usage == cuda::USAGE_STATIC) // Static blocks are allocated at the front of free blocks. + { + const size_t offset = it->m_addr & maskAligned; // If the address of this free block is not on the required alignment + const size_t adjust = (offset) ? alignment - offset : 0; // the size needs to be adjusted by this many bytes. + + const size_t sizeAdjusted = size + adjust; // The resulting block needs to be this big to fit the requested size with the proper alignment. + + if (sizeAdjusted <= it->m_size) + { + // The new block address starts at the beginning of the free block. + // The adjusted address is the properly aligned pointer in that free block. + block = cuda::Block(this, it->m_addr, sizeAdjusted, it->m_addr + adjust); + MY_ASSERT((block.m_ptr & maskAligned) == 0); // DEBUG + + it->m_addr += sizeAdjusted; // Advance the free block pointer. + it->m_size -= sizeAdjusted; // Reduce the free block size. + + if (it->m_size == 0) // If the block was fully used, remove it from the list of free blocks. + { + m_blocksFree.erase(it); + } + + return true; + } + } + else // if (usage == cuda::USAGE_TEMP) // Temporary allocations are placed at the end of free blocks to reduce fragmentation inside the arena. + { + const CUdeviceptr addrEnd = it->m_addr + it->m_size; // The poiner behind the last byte of the free block. + CUdeviceptr addrTmp = addrEnd - size; // The pointer to the start of the allocated block if the alignment fits. + + const size_t adjust = addrTmp & maskAligned; // If the start address is not properly aligned, this is the amount of bytes we need to start earlier to get an aligned pointer. + + const size_t sizeAdjusted = size + adjust; // For performance, do not split the free block into size and adjust, although there will be adjust many bytes free at the end of the block. + + if (sizeAdjusted <= it->m_size) + { + addrTmp -= adjust; // The block address needs to be that many bytes ealier to be aligned. + MY_ASSERT(it->m_addr <= addrTmp); // DEBUG Cannot happen with the size check above. + + // The new block starts at the aligned address at the end of the free block. + block = cuda::Block(this, addrTmp, sizeAdjusted, addrTmp); + MY_ASSERT((block.m_ptr & maskAligned) == 0); // DEBUG Check the user pointer for proper alignment. + + it->m_size -= sizeAdjusted; // Reduce the free block size. The address stays the same because we allocated at the end. + + if (it->m_size == 0) // If the block was fully used, remove it from the list of free blocks. + { + MY_ASSERT(it->m_addr == addrTmp); // If we used the whole size, these two addresses must match. + m_blocksFree.erase(it); + } + + return true; + } + } + } + + return false; + } + + void Arena::freeBlock(const cuda::Block& block) + { + // Search for the list element which has the next higher m_addr than the block. + std::list::iterator itNext = m_blocksFree.begin(); + + while (itNext != m_blocksFree.end() && itNext->m_addr < block.m_addr) + { + ++itNext; + } + + // Insert block before itNext. Returned iterator "it" points to block. + std::list::iterator it = m_blocksFree.insert(itNext, block); + + // If itNext is not end(), then it points to a free block and "it" is directly before that. + if (itNext != m_blocksFree.end()) + { + if (it->m_addr + it->m_size == itNext->m_addr) // Check if the memory blocks are adjacent. + { + it->m_size += itNext->m_size; // Merge the two blocks to the first + m_blocksFree.erase(itNext); // and erase the second. + } + } + + // If "it" is not begin(), then there is an element before it which could be adjacent. + if (it != m_blocksFree.begin()) + { + itNext = it--; // Now "it" can be at least begin() and itNext is always the element directly after it. + if (it->m_addr + it->m_size == itNext->m_addr) + { + it->m_size += itNext->m_size; // Merge the two blocks to the first + m_blocksFree.erase(itNext); // and erase the second. + } + } + } + + + ArenaAllocator::ArenaAllocator(const size_t sizeArenaBytes) + : m_sizeArenaBytes(sizeArenaBytes) + , m_sizeMemoryAllocated(0) + { + } + + ArenaAllocator::~ArenaAllocator() + { + for (auto arena : m_arenas) + { + delete arena; + } + } + + CUdeviceptr ArenaAllocator::alloc(const size_t size, const size_t alignment, const cuda::Usage usage) + { + // All memory alignments needed in this implementation are at max 256 and power-of-two + // which means adjustments can be done with bitmasks instead of modulo operators. + MY_ASSERT(0 < size && alignment <= 256 && isPowerOfTwo(alignment)); + + // This allocator does not support allocating a pointer with zero bytes capacity. (cuMemAlloc() doesn't either.) + // That would break the uniqueness of the user pointer inside the m_blocksAllocated because the free block wouldn't advance its address. + // If really needed, override the zero size here. + if (size == 0) + { + return 0; + } + + cuda::Block block; + + // PERF Normally the biggest free block is inside the most recently created arena. + // Means if the search starts from the back of the vector, the chance to find a free block is much higher. + size_t i = m_arenas.size(); + while (0 < i--) + { + if (m_arenas[i]->allocBlock(block, size, alignment, usage)) + { + break; + } + } + + // If none of the existing Arenas had a sufficient contiguous memory block, create a new Arena which can hold the size. + if (!block.isValid()) + { + try + { + const size_t sizeArenaBytes = std::max(m_sizeArenaBytes, size); + + // Allocate a new arena which can hold the requested size of bytes. + // No user pointer adjustment is required if m_sizeArenaBytes <= size. This is always aligned to 256 bytes. + Arena* arena = new Arena(sizeArenaBytes); // This can fail with a CUDA out-of-memory error! + + m_arenas.push_back(arena); // Append it to the vector of arenas. + + (void) arena->allocBlock(block, size, alignment, usage); + MY_ASSERT(block.isValid()); // This allocation should not fail. + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + } + + if (block.isValid()) + { + // DEBUG Make sure the user pointer is unique. (It wasn't when using size == 0.) + //std::map::const_iterator it = m_blocksAllocated.find(block.m_ptr); + //if (it != m_blocksAllocated.end()) + //{ + // MY_ASSERT(false); + //} + + m_blocksAllocated[block.m_ptr] = block; // Track all successful allocations inside the ArenaAllocator with the user pointer as key. + + m_sizeMemoryAllocated += block.m_size; // Track the overall number of bytes allocated. + } + + return block.m_ptr; // This is 0 when the block is invalid which can only happen with a CUDA OOM error. + } + + void ArenaAllocator::free(const CUdeviceptr ptr) + { + // Allow free() to be called with nullptr. This actually happens on purpose. + if (ptr == 0) + { + return; + } + + std::map::const_iterator it = m_blocksAllocated.find(ptr); + if (it != m_blocksAllocated.end()) + { + const cuda::Block& block = it->second; + + MY_ASSERT(block.m_size <= m_sizeMemoryAllocated); + m_sizeMemoryAllocated -= block.m_size; // Track overall number of byts allocated. + + block.m_arena->freeBlock(block); // Merge this block to the list of free blocks in this arena. + + m_blocksAllocated.erase(it); // Remove it from the map of allocated blocks. + } + else + { + std::cerr << "ERROR: ArenaAllocator::free() failed to find the pointer " << ptr << "\n"; + } + } + + size_t ArenaAllocator::getSizeMemoryAllocated() const + { + return m_sizeMemoryAllocated; + } + +} // namespace cuda diff --git a/apps/bench_shared_offscreen/src/Assimp.cpp b/apps/bench_shared_offscreen/src/Assimp.cpp new file mode 100644 index 00000000..77abdacb --- /dev/null +++ b/apps/bench_shared_offscreen/src/Assimp.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Application.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "inc/MyAssert.h" + + +std::shared_ptr Application::createASSIMP(const std::string& filename) +{ + std::map< std::string, std::shared_ptr >::const_iterator itGroup = m_mapGroups.find(filename); + if (itGroup != m_mapGroups.end()) + { + return itGroup->second; // Full model instancing under an Instance node. + } + + std::ifstream fin(filename); + if (!fin.fail()) + { + fin.close(); // Ok, file found. + } + else + { + std::cerr << "createASSIMP() could not open " << filename << '\n'; + + // Generate a Group node in any case. It will not have children when the file loading fails. + std::shared_ptr group(new sg::Group(m_idGroup++)); + m_mapGroups[filename] = group; // Allow instancing of this whole model (to fail again quicker next time). + return group; + } + + Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL; // or Assimp::Logger::VERBOSE; + + Assimp::DefaultLogger::create("", severity, aiDefaultLogStream_STDOUT); // Create a logger instance for Console Output + //Assimp::DefaultLogger::create("assimp_log.txt", severity, aiDefaultLogStream_FILE); // Create a logger instance for File Output (found in project folder or near .exe) + + Assimp::DefaultLogger::get()->info("Assimp::DefaultLogger initialized."); // Will add message with "info" tag. + // Assimp::DefaultLogger::get()->debug(""); // Will add message with "debug" tag. + + unsigned int postProcessSteps = 0 + //| aiProcess_CalcTangentSpace + //| aiProcess_JoinIdenticalVertices + //| aiProcess_MakeLeftHanded + | aiProcess_Triangulate + //| aiProcess_RemoveComponent + //| aiProcess_GenNormals + | aiProcess_GenSmoothNormals + //| aiProcess_SplitLargeMeshes + //| aiProcess_PreTransformVertices + //| aiProcess_LimitBoneWeights + //| aiProcess_ValidateDataStructure + //| aiProcess_ImproveCacheLocality + | aiProcess_RemoveRedundantMaterials + //| aiProcess_FixInfacingNormals + | aiProcess_SortByPType + //| aiProcess_FindDegenerates + //| aiProcess_FindInvalidData + //| aiProcess_GenUVCoords + //| aiProcess_TransformUVCoords + //| aiProcess_FindInstances + //| aiProcess_OptimizeMeshes + //| aiProcess_OptimizeGraph + //| aiProcess_FlipUVs + //| aiProcess_FlipWindingOrder + //| aiProcess_SplitByBoneCount + //| aiProcess_Debone + //| aiProcess_GlobalScale + //| aiProcess_EmbedTextures + //| aiProcess_ForceGenNormals + //| aiProcess_DropNormals + ; + + Assimp::Importer importer; + + if (m_optimize) + { + postProcessSteps |= aiProcess_FindDegenerates | aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; + + // Removing degenerate triangles. + // If you don't support lines and points, then + // specify the aiProcess_FindDegenerates flag, + // specify the aiProcess_SortByPType flag, + // set the AI_CONFIG_PP_SBP_REMOVE importer property to (aiPrimitiveType_POINT | aiPrimitiveType_LINE). + importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE); + // This step also removes very small triangles with a surface area smaller than 10^-6. + // If you rely on having these small triangles, or notice holes in your model, + // set the property AI_CONFIG_PP_FD_CHECKAREA to false. + importer.SetPropertyBool(AI_CONFIG_PP_FD_CHECKAREA, false); + // The degenerate triangles are put into point or line primitives which are then ignored when building the meshes. + // Finally the traverseScene() function filters out any instance node in the hierarchy which doesn't have a polygonal mesh assigned. + } + + const aiScene* scene = importer.ReadFile(filename, postProcessSteps); + + // If the import failed, report it + if (!scene) + { + Assimp::DefaultLogger::get()->info(importer.GetErrorString()); + Assimp::DefaultLogger::kill(); // Kill it after the work is done + + std::shared_ptr group(new sg::Group(m_idGroup++)); + m_mapGroups[filename] = group; // Allow instancing of this whole model (to fail again quicker next time). + return group; + } + + // Each scene needs to know where its geometries begin in the m_geometries to calculate the correct mesh index in traverseScene() + const unsigned int indexSceneBase = static_cast(m_geometries.size()); + + m_remappedMeshIndices.clear(); // Clear the local remapping vector from iMesh to m_geometries index. + + // Create all geometries in the assimp scene with triangle data. Ignore the others and remap their geometry indices. + for (unsigned int iMesh = 0; iMesh < scene->mNumMeshes; ++iMesh) + { + const aiMesh* mesh = scene->mMeshes[iMesh]; + + unsigned int remapMeshToGeometry = ~0u; // Remap mesh index to geometry index. ~0 means there was no geometry for a mesh. + + // The post-processor took care of meshes per primitive type and triangulation. + // Need to do a bitwise comparison of the mPrimitiveTypes here because newer ASSIMP versions + // indicate triangulated former polygons with the additional aiPrimitiveType_NGONEncodingFlag. + if ((mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) && 2 < mesh->mNumVertices) + { + std::vector attributes(mesh->mNumVertices); + + bool needsTangents = false; + bool needsNormals = false; + bool needsTexcoords = false; + + for (unsigned int iVertex = 0; iVertex < mesh->mNumVertices; ++iVertex) + { + TriangleAttributes& attrib = attributes[iVertex]; + + const aiVector3D& v = mesh->mVertices[iVertex]; + attrib.vertex = make_float3(v.x, v.y, v.z); + + if (mesh->HasTangentsAndBitangents()) + { + const aiVector3D& t = mesh->mTangents[iVertex]; + attrib.tangent = make_float3(t.x, t.y, t.z); + } + else + { + needsTangents = true; + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + } + + if (mesh->HasNormals()) + { + const aiVector3D& n = mesh->mNormals[iVertex]; + attrib.normal = make_float3(n.x, n.y, n.z); + } + else + { + needsNormals = true; + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + } + + if (mesh->HasTextureCoords(0)) + { + const aiVector3D& t = mesh->mTextureCoords[0][iVertex]; + attrib.texcoord = make_float3(t.x, t.y, t.z); + } + else + { + needsTexcoords = true; + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + } + } + + std::vector indices; + + for (unsigned int iFace = 0; iFace < mesh->mNumFaces; ++iFace) + { + const struct aiFace* face = &mesh->mFaces[iFace]; + MY_ASSERT(face->mNumIndices == 3); // Must be true because of aiProcess_Triangulate. + + for (unsigned int iIndex = 0; iIndex < face->mNumIndices; ++iIndex) + { + indices.push_back(face->mIndices[iIndex]); + } + } + + //if (needsNormals) // Assimp handled that via the aiProcess_GenSmoothNormals flag. + //{ + // calculateNormals(attributes, indices); + //} + if (needsTangents) + { + calculateTangents(attributes, indices); // This calculates geometry tangents though. + } + + remapMeshToGeometry = static_cast(m_geometries.size()); + + std::shared_ptr geometry(new sg::Triangles(m_idGeometry++)); + geometry->setAttributes(attributes); + geometry->setIndices(indices); + + m_geometries.push_back(geometry); + } + + m_remappedMeshIndices.push_back(remapMeshToGeometry); + } + + std::shared_ptr group = traverseScene(scene, indexSceneBase, scene->mRootNode); + m_mapGroups[filename] = group; // Allow instancing of this whole model. + + Assimp::DefaultLogger::kill(); // Kill it after the work is done + + return group; +} + +std::shared_ptr Application::traverseScene(const struct aiScene *scene, const unsigned int indexSceneBase, const struct aiNode* node) +{ + // Create a group to hold all children and all meshes of this node. + std::shared_ptr group(new sg::Group(m_idGroup++)); + + const aiMatrix4x4& m = node->mTransformation; + + const float trafo[12] = + { + float(m.a1), float(m.a2), float(m.a3), float(m.a4), + float(m.b1), float(m.b2), float(m.b3), float(m.b4), + float(m.c1), float(m.c2), float(m.c3), float(m.c4) + }; + + // Need to do a depth first traversal here to attach the bottom most nodes to each node's group. + for (unsigned int iChild = 0; iChild < node->mNumChildren; ++iChild) + { + std::shared_ptr child = traverseScene(scene, indexSceneBase, node->mChildren[iChild]); + + // Create an instance which holds the subtree. + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(child); + + group->addChild(instance); + } + + // Now also gather all meshes assigned to this node. + for (unsigned int iMesh = 0; iMesh < node->mNumMeshes; ++iMesh) + { + const unsigned int indexMesh = node->mMeshes[iMesh]; // Original mesh index in the assimp scene. + MY_ASSERT(indexMesh < m_remappedMeshIndices.size()) + + if (m_remappedMeshIndices[indexMesh] != ~0u) // If there exists a Triangles geometry for this assimp mesh, then build the Instance. + { + const unsigned int indexGeometry = m_remappedMeshIndices[indexMesh]; + + // Create an instance with the current nodes transformation and append it to the parent group. + std::shared_ptr instance(new sg::Instance(m_idInstance++)); + + instance->setTransform(trafo); + instance->setChild(m_geometries[indexGeometry]); + + const struct aiMesh* mesh = scene->mMeshes[indexMesh]; + + // Allow to specify different materials per assimp model by using the filename (no path no extension) and the material index. + struct aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + + std::string nameMaterialReference; + aiString materialName; + if (material->Get(AI_MATKEY_NAME, materialName) == aiReturn_SUCCESS) + { + nameMaterialReference = std::string(materialName.C_Str()); + } + + int indexMaterial = -1; + std::map::const_iterator itm = m_mapMaterialReferences.find(nameMaterialReference); + if (itm != m_mapMaterialReferences.end()) + { + indexMaterial = itm->second; + + // The materials had been created with default albedo colors. + // Change it to the diffuse color of the assimp material. + aiColor4D diffuse; + if (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == aiReturn_SUCCESS) + { + m_materialsGUI[indexMaterial].albedo = make_float3(diffuse.r, diffuse.g, diffuse.b); + } + } + else + { + std::cerr << "WARNING: traverseScene() No material found for " << nameMaterialReference << ". Trying default.\n"; + + std::map::const_iterator itmd = m_mapMaterialReferences.find(std::string("default")); + if (itmd != m_mapMaterialReferences.end()) + { + indexMaterial = itmd->second; + } + else + { + std::cerr << "ERROR: loadSceneDescription() No default material found\n"; + } + } + instance->setMaterial(indexMaterial); + + group->addChild(instance); + } + } + return group; +} diff --git a/apps/bench_shared_offscreen/src/Box.cpp b/apps/bench_shared_offscreen/src/Box.cpp new file mode 100644 index 00000000..40960b11 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Box.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // A simple unit cube built from 12 triangles. + void Triangles::createBox() + { + m_attributes.clear(); + m_indices.clear(); + + const float left = -1.0f; + const float right = 1.0f; + const float bottom = -1.0f; + const float top = 1.0f; + const float back = -1.0f; + const float front = 1.0f; + + TriangleAttributes attrib; + + // Left. + attrib.tangent = make_float3(0.0f, 0.0f, 1.0f); + attrib.normal = make_float3(-1.0f, 0.0f, 0.0f); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Right. + attrib.tangent = make_float3(0.0f, 0.0f, -1.0f); + attrib.normal = make_float3(1.0f, 0.0f, 0.0f); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Back. + attrib.tangent = make_float3(-1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, -1.0f); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Front. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Bottom. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, -1.0f, 0.0f); + + attrib.vertex = make_float3(left, bottom, back); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, back); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, bottom, front); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, bottom, front); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + // Top. + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 1.0f, 0.0f); + + attrib.vertex = make_float3(left, top, front); + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, front); + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(right, top, back); + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = make_float3(left, top, back); + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + for (unsigned int i = 0; i < 6; ++i) + { + const unsigned int idx = i * 4; // Four m_attributes per box face. + + m_indices.push_back(idx); + m_indices.push_back(idx + 1); + m_indices.push_back(idx + 2); + + m_indices.push_back(idx + 2); + m_indices.push_back(idx + 3); + m_indices.push_back(idx); + } + } + +} // namespace sg diff --git a/apps/bench_shared_offscreen/src/Camera.cpp b/apps/bench_shared_offscreen/src/Camera.cpp new file mode 100644 index 00000000..18c91647 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Camera.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Camera.h" + +#include + +#include "shaders/shader_common.h" + + +Camera::Camera() +: m_distance(10.0f) // Camera is 10 units aways from the point of interest +, m_phi(0.75f) // on the positive z-axis +, m_theta(0.6f) // slightly above the equator (at 0.5f). +, m_fov(60.0f) +, m_widthResolution(1) +, m_heightResolution(1) +, m_aspect(1.0f) +, m_baseX(0) +, m_baseY(0) +, m_speedRatio(10.0f) +, m_dx(0) +, m_dy(0) +, m_changed(false) +{ + m_center = make_float3(0.0f, 0.0f, 0.0f); + + m_cameraP = make_float3(0.0f, 0.0f, 1.0f); + m_cameraU = make_float3(1.0f, 0.0f, 0.0f); + m_cameraV = make_float3(0.0f, 1.0f, 0.0f); + m_cameraW = make_float3(0.0f, 0.0f, -1.0f); +} + +//Camera::~Camera() +//{ +//} + +void Camera::setResolution(int w, int h) +{ + if (m_widthResolution != w || m_heightResolution != h) + { + // Never drop to zero viewport size. This avoids lots of checks for zero in other routines. + m_widthResolution = (0 < w) ? w : 1; + m_heightResolution = (0 < h) ? h : 1; + m_aspect = float(m_widthResolution) / float(m_heightResolution); + m_changed = true; + } +} + +void Camera::setBaseCoordinates(int x, int y) +{ + m_baseX = x; + m_baseY = y; +} + +void Camera::orbit(int x, int y) +{ + if (setDelta(x, y)) + { + m_phi -= float(m_dx) / float(m_widthResolution); // Negative to match the mouse movement to the phi progression. + // Wrap phi. + if (m_phi < 0.0f) + { + m_phi += 1.0f; + } + else if (1.0f < m_phi) + { + m_phi -= 1.0f; + } + + m_theta += float(m_dy) / float(m_heightResolution); + // Clamp theta. + if (m_theta < 0.0f) + { + m_theta = 0.0f; + } + else if (1.0f < m_theta) + { + m_theta = 1.0f; + } + } +} + +void Camera::pan(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float u = float(m_dx) / m_speedRatio; + float v = float(m_dy) / m_speedRatio; + // Pan the center of interest, the rest will follow. + m_center = m_center - u * m_cameraU + v * m_cameraV; + } +} + +void Camera::dolly(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float w = float(m_dy) / m_speedRatio; + // Adjust the distance, the center of interest stays fixed so that the orbit is around the same center. + m_distance -= w * length(m_cameraW); // Dragging down moves the camera forwards. "Drag-in the object". + if (m_distance < 0.001f) // Avoid swapping sides. Scene units are meters [m]. + { + m_distance = 0.001f; + } + } +} + +void Camera::focus(int x, int y) +{ + if (setDelta(x, y)) + { + // m_speedRatio pixels will move one vector length. + float w = float(m_dy) / m_speedRatio; + // Adjust the center of interest. + setFocusDistance(m_distance - w * length(m_cameraW)); + } +} + +void Camera::setFocusDistance(float f) +{ + if (m_distance != f && 0.001f < f) // Avoid swapping sides. + { + m_distance = f; + m_center = m_cameraP + m_distance * m_cameraW; // Keep the camera position fixed and calculate a new center of interest which is the focus plane. + m_changed = true; // m_changed is only reset when asking for the frustum + } +} + +void Camera::zoom(float x) +{ + m_fov += float(x); + if (m_fov < 1.0f) + { + m_fov = 1.0f; + } + else if (179.0 < m_fov) + { + m_fov = 179.0f; + } + m_changed = true; +} + +float Camera::getAspectRatio() const +{ + return m_aspect; +} + +void Camera::markDirty() +{ + m_changed = true; +} + +bool Camera::getFrustum(float3& p, float3& u, float3& v, float3& w, bool force) +{ + bool changed = force || m_changed; + if (changed) + { + // Recalculate the camera parameters. + const float cosPhi = cosf(m_phi * 2.0f * M_PIf); + const float sinPhi = sinf(m_phi * 2.0f * M_PIf); + const float cosTheta = cosf(m_theta * M_PIf); + const float sinTheta = sinf(m_theta * M_PIf); + + const float3 normal = make_float3(cosPhi * sinTheta, -cosTheta, -sinPhi * sinTheta); // "normal", unit vector from origin to spherical coordinates (phi, theta) + + const float tanFovHalf = tanf((m_fov * 0.5f) * M_PIf / 180.0f); // m_fov is in the range [1.0f, 179.0f]. + + m_cameraP = m_center + m_distance * normal; + + m_cameraU = m_aspect * make_float3(-sinPhi, 0.0f, -cosPhi) * tanFovHalf; // "tangent" + m_cameraV = make_float3(cosTheta * cosPhi, sinTheta, cosTheta * -sinPhi) * tanFovHalf; // "bitangent" + m_cameraW = -normal; // "-normal" to look at the center. + + p = m_cameraP; + u = m_cameraU; + v = m_cameraV; + w = m_cameraW; + + m_changed = false; // Next time asking for the frustum will return false unless the camera has changed again. + } + return changed; +} + +bool Camera::setDelta(int x, int y) +{ + if (m_baseX != x || m_baseY != y) + { + m_dx = x - m_baseX; + m_dy = y - m_baseY; + + m_baseX = x; + m_baseY = y; + + m_changed = true; // m_changed is only reset when asking for the frustum. + return true; // There is a delta. + } + return false; +} + +void Camera::setSpeedRatio(float f) +{ + m_speedRatio = f; + if (m_speedRatio < 0.01f) + { + m_speedRatio = 0.01f; + } + else if (1000.0f < m_speedRatio) + { + m_speedRatio = 1000.0f; + } +} diff --git a/apps/bench_shared_offscreen/src/Device.cpp b/apps/bench_shared_offscreen/src/Device.cpp new file mode 100644 index 00000000..55155838 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Device.cpp @@ -0,0 +1,1836 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Device.h" + +#include "inc/CheckMacros.h" + +#include "shaders/compositor_data.h" + + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +// The cfgmgr32 header is necessary for interrogating driver information in the registry. +#include +// For convenience the library is also linked in automatically using the #pragma command. +#pragma comment(lib, "Cfgmgr32.lib") +#else +#include +#endif + +//#include +//#if defined( _WIN32 ) +//#include +//#endif + +// CUDA Driver API version of the OpenGL interop header. +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +// Original code from optix_stubs.h +static void* optixLoadWindowsDll(void) +{ + const char* optixDllName = "nvoptix.dll"; + void* handle = NULL; + + // Get the size of the path first, then allocate + unsigned int size = GetSystemDirectoryA(NULL, 0); + if (size == 0) + { + // Couldn't get the system path size, so bail + return NULL; + } + + size_t pathSize = size + 1 + strlen(optixDllName); + char* systemPath = (char*) malloc(pathSize); + + if (GetSystemDirectoryA(systemPath, size) != size - 1) + { + // Something went wrong + free(systemPath); + return NULL; + } + + strcat(systemPath, "\\"); + strcat(systemPath, optixDllName); + + handle = LoadLibraryA(systemPath); + + free(systemPath); + + if (handle) + { + return handle; + } + + // If we didn't find it, go looking in the register store. Since nvoptix.dll doesn't + // have its own registry entry, we are going to look for the OpenGL driver which lives + // next to nvoptix.dll. 0 (null) will be returned if any errors occured. + + static const char* deviceInstanceIdentifiersGUID = "{4d36e968-e325-11ce-bfc1-08002be10318}"; + const ULONG flags = CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT; + ULONG deviceListSize = 0; + + if (CM_Get_Device_ID_List_SizeA(&deviceListSize, deviceInstanceIdentifiersGUID, flags) != CR_SUCCESS) + { + return NULL; + } + + char* deviceNames = (char*) malloc(deviceListSize); + + if (CM_Get_Device_ID_ListA(deviceInstanceIdentifiersGUID, deviceNames, deviceListSize, flags)) + { + free(deviceNames); + return NULL; + } + + DEVINST devID = 0; + + // Continue to the next device if errors are encountered. + for (char* deviceName = deviceNames; *deviceName; deviceName += strlen(deviceName) + 1) + { + if (CM_Locate_DevNodeA(&devID, deviceName, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) + { + continue; + } + + HKEY regKey = 0; + if (CM_Open_DevNode_Key(devID, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, ®Key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) + { + continue; + } + + const char* valueName = "OpenGLDriverName"; + DWORD valueSize = 0; + + LSTATUS ret = RegQueryValueExA(regKey, valueName, NULL, NULL, NULL, &valueSize); + if (ret != ERROR_SUCCESS) + { + RegCloseKey(regKey); + continue; + } + + char* regValue = (char*) malloc(valueSize); + ret = RegQueryValueExA(regKey, valueName, NULL, NULL, (LPBYTE) regValue, &valueSize); + if (ret != ERROR_SUCCESS) + { + free(regValue); + RegCloseKey(regKey); + continue; + } + + // Strip the OpenGL driver dll name from the string then create a new string with + // the path and the nvoptix.dll name + for (int i = valueSize - 1; i >= 0 && regValue[i] != '\\'; --i) + { + regValue[i] = '\0'; + } + + size_t newPathSize = strlen(regValue) + strlen(optixDllName) + 1; + char* dllPath = (char*) malloc(newPathSize); + strcpy(dllPath, regValue); + strcat(dllPath, optixDllName); + + free(regValue); + RegCloseKey(regKey); + + handle = LoadLibraryA((LPCSTR) dllPath); + free(dllPath); + + if (handle) + { + break; + } + } + + free(deviceNames); + + return handle; +} +#endif + + +// Global logger function instead of the Logger class to be able to submit per device date via the cbdata pointer. +static std::mutex g_mutexLogger; + +static void callbackLogger(unsigned int level, const char* tag, const char* message, void* cbdata) +{ + std::lock_guard lock(g_mutexLogger); + + Device* device = static_cast(cbdata); + + std::cerr << tag << " (" << level << ") [" << device->m_ordinal << "]: " << ((message) ? message : "(no message)") << '\n'; +} + + +static std::vector readData(std::string const& filename) +{ + std::ifstream inputData(filename, std::ios::binary); + + if (inputData.fail()) + { + std::cerr << "ERROR: readData() Failed to open file " << filename << '\n'; + return std::vector(); + } + + // Copy the input buffer to a char vector. + std::vector data(std::istreambuf_iterator(inputData), {}); + + if (inputData.fail()) + { + std::cerr << "ERROR: readData() Failed to read file " << filename << '\n'; + return std::vector(); + } + + return data; +} + + +Device::Device(const int ordinal, + const int index, + const int count, + const int miss, + //const int interop, + //const unsigned int tex, + //const unsigned int pbo, + const size_t sizeArena) +: m_ordinal(ordinal) +, m_index(index) +, m_count(count) +, m_miss(miss) +//, m_interop(interop) +//, m_tex(tex) +//, m_pbo(pbo) +, m_nodeMask(0) +, m_launchWidth(0) +, m_ownsSharedBuffer(false) +, m_d_compositorData(0) +//, m_cudaGraphicsResource(nullptr) +, m_sizeMemoryTextureArrays(0) +{ + // Get the CUdevice handle from the CUDA device ordinal. + CU_CHECK( cuDeviceGet(&m_cudaDevice, m_ordinal) ); + + initDeviceAttributes(); // Query all CUDA capabilities of this device. + + OPTIX_CHECK( initFunctionTable() ); + + // Create a CUDA Context and make it current to this thread. + // PERF What is the best CU_CTX_SCHED_* setting here? + // CU_CTX_MAP_HOST host to allow pinned memory. + CU_CHECK( cuCtxCreate(&m_cudaContext, CU_CTX_SCHED_SPIN | CU_CTX_MAP_HOST, m_cudaDevice) ); + + // PERF To make use of asynchronous copies. Currently not really anything happening in parallel due to synchronize calls. + CU_CHECK( cuStreamCreate(&m_cudaStream, CU_STREAM_NON_BLOCKING) ); + + size_t sizeFree = 0; + size_t sizeTotal = 0; + + CU_CHECK( cuMemGetInfo(&sizeFree, &sizeTotal) ); + + std::cout << "Device ordinal " << m_ordinal << ": " << sizeFree << " bytes free; " << sizeTotal << " bytes total\n"; + + m_allocator = new cuda::ArenaAllocator(sizeArena * 1024 * 1024); // The ArenaAllocator gets the default Arena size in bytes! + +#if 1 + // UUID works under Windows and Linux. + memset(&m_deviceUUID, 0, 16); + CU_CHECK( cuDeviceGetUuid(&m_deviceUUID, m_cudaDevice) ); +#else + // LUID only works under Windows and only in WDDM mode, not in TCC mode! + // Get the LUID and node mask to be able to determine which device needs to allocate the peer-to-peer staging buffer for the OpenGL interop PBO. + memset(m_deviceLUID, 0, 8); + CU_CHECK( cuDeviceGetLuid(m_deviceLUID, &m_nodeMask, m_cudaDevice) ); +#endif + + CU_CHECK( cuModuleLoad(&m_moduleCompositor, "./bench_shared_offscreen_core/compositor.ptx") ); // DAR FIXME Only load this on the primary device! + CU_CHECK( cuModuleGetFunction(&m_functionCompositor, m_moduleCompositor, "compositor") ); + + OptixDeviceContextOptions options = {}; + + options.logCallbackFunction = &callbackLogger; + options.logCallbackData = this; // This allows per device logs. It's currently printing the device ordinal. + options.logCallbackLevel = 3; // Keep at warning level to suppress the disk cache messages. + + OPTIX_CHECK( m_api.optixDeviceContextCreate(m_cudaContext, &options, &m_optixContext) ); + + initDeviceProperties(); // OptiX + + m_d_systemData = reinterpret_cast(memAlloc(sizeof(SystemData), 16)); // Currently 8 would be enough. + + m_isDirtySystemData = true; // Trigger SystemData update before the next launch. + + // Initialize all renderer system data. + //m_systemData.rect = make_int4(0, 0, 1, 1); // Unused, this is not a tiled renderer. + m_systemData.topObject = 0; + m_systemData.outputBuffer = 0; // Deferred allocation. Only done in render() of the derived Device classes to allow for different memory spaces! + m_systemData.tileBuffer = 0; // For the final frame tiled renderer the intermediate buffer is only tileSize. + m_systemData.texelBuffer = 0; // For the final frame tiled renderer. Contains the accumulated result of the current tile. + m_systemData.cameraDefinitions = nullptr; + m_systemData.lightDefinitions = nullptr; + m_systemData.materialDefinitions = nullptr; + m_systemData.envTexture = 0; + m_systemData.envCDF_U = nullptr; + m_systemData.envCDF_V = nullptr; + m_systemData.resolution = make_int2(1, 1); // Deferred allocation after setResolution() when m_isDirtyOutputBuffer == true. + m_systemData.tileSize = make_int2(8, 8); // Default value for multi-GPU tiling. Must be power-of-two values. (8x8 covers either 8x4 or 4x8 internal 2D warp shapes.) + m_systemData.tileShift = make_int2(3, 3); // The right-shift for the division by tileSize. + m_systemData.pathLengths = make_int2(2, 5); // min, max + m_systemData.deviceCount = m_count; // The number of active devices. + m_systemData.deviceIndex = m_index; // This allows to distinguish multiple devices. + m_systemData.iterationIndex = 0; + m_systemData.samplesSqrt = 0; // Invalid value! Enforces that there is at least one setState() call before rendering. + m_systemData.sceneEpsilon = 500.0f * SCENE_EPSILON_SCALE; + m_systemData.clockScale = 1000.0f * CLOCK_FACTOR_SCALE; + m_systemData.lensShader = 0; + m_systemData.numCameras = 0; + m_systemData.numLights = 0; + m_systemData.numMaterials = 0; + m_systemData.envWidth = 0; + m_systemData.envHeight = 0; + m_systemData.envIntegral = 1.0f; + m_systemData.envRotation = 0.0f; + + m_isDirtyOutputBuffer = true; // First render call initializes it. This is done in the derived render() functions. + + m_moduleFilenames.resize(NUM_MODULE_IDENTIFIERS); + + // Starting with OptiX SDK 7.5.0 and CUDA 11.7 either PTX or OptiX IR input can be used to create modules. + // Just initialize the m_moduleFilenames depending on the definition of USE_OPTIX_IR. + // That is added to the project definitions inside the CMake script when OptiX SDK 7.5.0 and CUDA 11.7 or newer are found. +#if defined(USE_OPTIX_IR) + m_moduleFilenames[MODULE_ID_RAYGENERATION] = std::string("./bench_shared_offscreen_core/raygeneration.optixir"); + m_moduleFilenames[MODULE_ID_EXCEPTION] = std::string("./bench_shared_offscreen_core/exception.optixir"); + m_moduleFilenames[MODULE_ID_MISS] = std::string("./bench_shared_offscreen_core/miss.optixir"); + m_moduleFilenames[MODULE_ID_CLOSESTHIT] = std::string("./bench_shared_offscreen_core/closesthit.optixir"); + m_moduleFilenames[MODULE_ID_ANYHIT] = std::string("./bench_shared_offscreen_core/anyhit.optixir"); + m_moduleFilenames[MODULE_ID_LENS_SHADER] = std::string("./bench_shared_offscreen_core/lens_shader.optixir"); + m_moduleFilenames[MODULE_ID_LIGHT_SAMPLE] = std::string("./bench_shared_offscreen_core/light_sample.optixir"); + m_moduleFilenames[MODULE_ID_BXDF_DIFFUSE] = std::string("./bench_shared_offscreen_core/bxdf_diffuse.optixir"); + m_moduleFilenames[MODULE_ID_BXDF_SPECULAR] = std::string("./bench_shared_offscreen_core/bxdf_specular.optixir"); + m_moduleFilenames[MODULE_ID_BXDF_GGX_SMITH] = std::string("./bench_shared_offscreen_core/bxdf_ggx_smith.optixir"); +#else + m_moduleFilenames[MODULE_ID_RAYGENERATION] = std::string("./bench_shared_offscreen_core/raygeneration.ptx"); + m_moduleFilenames[MODULE_ID_EXCEPTION] = std::string("./bench_shared_offscreen_core/exception.ptx"); + m_moduleFilenames[MODULE_ID_MISS] = std::string("./bench_shared_offscreen_core/miss.ptx"); + m_moduleFilenames[MODULE_ID_CLOSESTHIT] = std::string("./bench_shared_offscreen_core/closesthit.ptx"); + m_moduleFilenames[MODULE_ID_ANYHIT] = std::string("./bench_shared_offscreen_core/anyhit.ptx"); + m_moduleFilenames[MODULE_ID_LENS_SHADER] = std::string("./bench_shared_offscreen_core/lens_shader.ptx"); + m_moduleFilenames[MODULE_ID_LIGHT_SAMPLE] = std::string("./bench_shared_offscreen_core/light_sample.ptx"); + m_moduleFilenames[MODULE_ID_BXDF_DIFFUSE] = std::string("./bench_shared_offscreen_core/bxdf_diffuse.ptx"); + m_moduleFilenames[MODULE_ID_BXDF_SPECULAR] = std::string("./bench_shared_offscreen_core/bxdf_specular.ptx"); + m_moduleFilenames[MODULE_ID_BXDF_GGX_SMITH] = std::string("./bench_shared_offscreen_core/bxdf_ggx_smith.ptx"); +#endif + + initPipeline(); +} + + +Device::~Device() +{ + CU_CHECK_NO_THROW( cuCtxSetCurrent(m_cudaContext) ); // Activate this CUDA context. Not using activate() because this needs a no-throw check. + CU_CHECK_NO_THROW( cuCtxSynchronize() ); // Make sure everthing running on this CUDA context has finished. + + //if (m_cudaGraphicsResource != nullptr) + //{ + // CU_CHECK_NO_THROW( cuGraphicsUnregisterResource(m_cudaGraphicsResource) ); + //} + + CU_CHECK_NO_THROW( cuModuleUnload(m_moduleCompositor) ); + + for (std::map::const_iterator it = m_mapTextures.begin(); it != m_mapTextures.end(); ++it) + { + if (it->second) + { + // The texture array data might be owned by a peer device. + // Explicitly destroy only the parts which belong to this device. + m_sizeMemoryTextureArrays -= it->second->destroy(this); + + delete it->second; // This will delete the CUtexObject which exists per device. + } + } + MY_ASSERT(m_sizeMemoryTextureArrays == 0); // Make sure the texture memory racking + + OPTIX_CHECK_NO_THROW( m_api.optixPipelineDestroy(m_pipeline) ); + OPTIX_CHECK_NO_THROW( m_api.optixDeviceContextDestroy(m_optixContext) ); + + delete m_allocator; // This frees all CUDA allocations! + + CU_CHECK_NO_THROW( cuStreamDestroy(m_cudaStream) ); + CU_CHECK_NO_THROW( cuCtxDestroy(m_cudaContext) ); +} + + +void Device::initDeviceAttributes() +{ + char buffer[1024]; + buffer[1023] = 0; + + CU_CHECK( cuDeviceGetName(buffer, 1023, m_cudaDevice) ); + m_deviceName = std::string(buffer); + + CU_CHECK(cuDeviceGetPCIBusId(buffer, 1023, m_cudaDevice)); + m_devicePciBusId = std::string(buffer); + + std::cout << "Device ordinal " << m_ordinal << ": " << m_deviceName << " visible\n"; + + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxThreadsPerBlock, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimX, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimY, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Y, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxBlockDimZ, CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Z, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimX, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimY, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxGridDimZ, CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerBlock, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.sharedMemoryPerBlock, CU_DEVICE_ATTRIBUTE_SHARED_MEMORY_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.totalConstantMemory, CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.warpSize, CU_DEVICE_ATTRIBUTE_WARP_SIZE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxPitch, CU_DEVICE_ATTRIBUTE_MAX_PITCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxRegistersPerBlock, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.registersPerBlock, CU_DEVICE_ATTRIBUTE_REGISTERS_PER_BLOCK, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.clockRate, CU_DEVICE_ATTRIBUTE_CLOCK_RATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.textureAlignment, CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.gpuOverlap, CU_DEVICE_ATTRIBUTE_GPU_OVERLAP, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiprocessorCount, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.kernelExecTimeout, CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.integrated, CU_DEVICE_ATTRIBUTE_INTEGRATED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canMapHostMemory, CU_DEVICE_ATTRIBUTE_CAN_MAP_HOST_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeMode, CU_DEVICE_ATTRIBUTE_COMPUTE_MODE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dDepth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dArrayNumslices, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_NUMSLICES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.surfaceAlignment, CU_DEVICE_ATTRIBUTE_SURFACE_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.concurrentKernels, CU_DEVICE_ATTRIBUTE_CONCURRENT_KERNELS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.eccEnabled, CU_DEVICE_ATTRIBUTE_ECC_ENABLED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciBusId, CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciDeviceId, CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.tccDriver, CU_DEVICE_ATTRIBUTE_TCC_DRIVER, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.memoryClockRate, CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.globalMemoryBusWidth, CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.l2CacheSize, CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxThreadsPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.asyncEngineCount, CU_DEVICE_ATTRIBUTE_ASYNC_ENGINE_COUNT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.unifiedAddressing, CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canTex2dGather, CU_DEVICE_ATTRIBUTE_CAN_TEX2D_GATHER, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dGatherWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dGatherHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dWidthAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dHeightAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture3dDepthAlternate, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH_ALTERNATE, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pciDomainId, CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.texturePitchAlignment, CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexturecubemapLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface3dDepth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_DEPTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface1dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurface2dLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapLayeredWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumSurfacecubemapLayeredLayers, CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_LAYERS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dLinearWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LINEAR_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dLinearPitch, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_PITCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dMipmappedWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture2dMipmappedHeight, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_HEIGHT, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeCapabilityMajor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computeCapabilityMinor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maximumTexture1dMipmappedWidth, CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_MIPMAPPED_WIDTH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.streamPrioritiesSupported, CU_DEVICE_ATTRIBUTE_STREAM_PRIORITIES_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.globalL1CacheSupported, CU_DEVICE_ATTRIBUTE_GLOBAL_L1_CACHE_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.localL1CacheSupported, CU_DEVICE_ATTRIBUTE_LOCAL_L1_CACHE_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxRegistersPerMultiprocessor, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_MULTIPROCESSOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.managedMemory, CU_DEVICE_ATTRIBUTE_MANAGED_MEMORY, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiGpuBoard, CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.multiGpuBoardGroupId, CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD_GROUP_ID, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.hostNativeAtomicSupported, CU_DEVICE_ATTRIBUTE_HOST_NATIVE_ATOMIC_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.singleToDoublePrecisionPerfRatio, CU_DEVICE_ATTRIBUTE_SINGLE_TO_DOUBLE_PRECISION_PERF_RATIO, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pageableMemoryAccess, CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.concurrentManagedAccess, CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.computePreemptionSupported, CU_DEVICE_ATTRIBUTE_COMPUTE_PREEMPTION_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUseHostPointerForRegisteredMem, CU_DEVICE_ATTRIBUTE_CAN_USE_HOST_POINTER_FOR_REGISTERED_MEM, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUse64BitStreamMemOps, CU_DEVICE_ATTRIBUTE_CAN_USE_64_BIT_STREAM_MEM_OPS, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canUseStreamWaitValueNor, CU_DEVICE_ATTRIBUTE_CAN_USE_STREAM_WAIT_VALUE_NOR, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.cooperativeLaunch, CU_DEVICE_ATTRIBUTE_COOPERATIVE_LAUNCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.cooperativeMultiDeviceLaunch, CU_DEVICE_ATTRIBUTE_COOPERATIVE_MULTI_DEVICE_LAUNCH, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.maxSharedMemoryPerBlockOptin, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK_OPTIN, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.canFlushRemoteWrites, CU_DEVICE_ATTRIBUTE_CAN_FLUSH_REMOTE_WRITES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.hostRegisterSupported, CU_DEVICE_ATTRIBUTE_HOST_REGISTER_SUPPORTED, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.pageableMemoryAccessUsesHostPageTables, CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS_USES_HOST_PAGE_TABLES, m_cudaDevice) ); + CU_CHECK( cuDeviceGetAttribute(&m_deviceAttribute.directManagedMemAccessFromHost, CU_DEVICE_ATTRIBUTE_DIRECT_MANAGED_MEM_ACCESS_FROM_HOST, m_cudaDevice) ); +} + +void Device::initDeviceProperties() +{ + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_RTCORE_VERSION, &m_deviceProperty.rtcoreVersion, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH, &m_deviceProperty.limitMaxTraceDepth, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH, &m_deviceProperty.limitMaxTraversableGraphDepth, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS, &m_deviceProperty.limitMaxPrimitivesPerGas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS, &m_deviceProperty.limitMaxInstancesPerIas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID, &m_deviceProperty.limitMaxInstanceId, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK, &m_deviceProperty.limitNumBitsInstanceVisibilityMask, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS, &m_deviceProperty.limitMaxSbtRecordsPerGas, sizeof(unsigned int)) ); + OPTIX_CHECK( m_api.optixDeviceContextGetProperty(m_optixContext, OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET, &m_deviceProperty.limitMaxSbtOffset, sizeof(unsigned int)) ); + +#if 0 + std::cout << "OPTIX_DEVICE_PROPERTY_RTCORE_VERSION = " << m_deviceProperty.rtcoreVersion << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH = " << m_deviceProperty.limitMaxTraceDepth << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH = " << m_deviceProperty.limitMaxTraversableGraphDepth << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS = " << m_deviceProperty.limitMaxPrimitivesPerGas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS = " << m_deviceProperty.limitMaxInstancesPerIas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID = " << m_deviceProperty.limitMaxInstanceId << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK = " << m_deviceProperty.limitNumBitsInstanceVisibilityMask << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS = " << m_deviceProperty.limitMaxSbtRecordsPerGas << '\n'; + std::cout << "OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET = " << m_deviceProperty.limitMaxSbtOffset << '\n'; +#endif +} + + +OptixResult Device::initFunctionTable() +{ +#ifdef _WIN32 + void* handle = optixLoadWindowsDll(); + if (!handle) + { + return OPTIX_ERROR_LIBRARY_NOT_FOUND; + } + + void* symbol = reinterpret_cast(GetProcAddress((HMODULE) handle, "optixQueryFunctionTable")); + if (!symbol) + { + return OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND; + } +#else + void* handle = dlopen("libnvoptix.so.1", RTLD_NOW); + if (!handle) + { + return OPTIX_ERROR_LIBRARY_NOT_FOUND; + } + + void* symbol = dlsym(handle, "optixQueryFunctionTable"); + if (!symbol) + + { + return OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND; + } +#endif + + OptixQueryFunctionTable_t* optixQueryFunctionTable = reinterpret_cast(symbol); + + return optixQueryFunctionTable(OPTIX_ABI_VERSION, 0, 0, 0, &m_api, sizeof(OptixFunctionTable)); +} + + +void Device::initPipeline() +{ + MY_ASSERT(NUM_RAYTYPES == 2); // The following code only works for two raytypes. + + OptixModuleCompileOptions mco = {}; + + mco.maxRegisterCount = OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT; +#if USE_DEBUG_EXCEPTIONS + mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_0; // No optimizations. + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; // Full debug. Never profile kernels with this setting! +#else + mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_3; // All optimizations, is the default. + // Keep generated line info for Nsight Compute profiling. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) +#if (OPTIX_VERSION >= 70400) + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; +#else + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; +#endif +#endif // USE_DEBUG_EXCEPTIONS + + OptixPipelineCompileOptions pco = {}; + + pco.usesMotionBlur = 0; + pco.traversableGraphFlags = OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; + pco.numPayloadValues = 2; // I need two to encode a 64-bit pointer to the per ray payload structure. + pco.numAttributeValues = 2; // The minimum is two for the barycentrics. +#if USE_DEBUG_EXCEPTIONS + pco.exceptionFlags = + OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW + | OPTIX_EXCEPTION_FLAG_TRACE_DEPTH + | OPTIX_EXCEPTION_FLAG_USER +#if (OPTIX_VERSION < 80000) + // Removed in OptiX SDK 8.0.0. + // Use OptixDeviceContextOptions validationMode = OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL instead. + | OPTIX_EXCEPTION_FLAG_DEBUG +#endif + ; +#else + pco.exceptionFlags = OPTIX_EXCEPTION_FLAG_NONE; +#endif + pco.pipelineLaunchParamsVariableName = "sysData"; +#if (OPTIX_VERSION >= 70100) + // Only using built-in Triangles in this renderer. + // This is the recommended setting for best performance then. + pco.usesPrimitiveTypeFlags = OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; // New in OptiX 7.1.0. +#endif + + // Each source file results in one OptixModule. + std::vector modules(NUM_MODULE_IDENTIFIERS); + + // Create all modules: + for (size_t i = 0; i < m_moduleFilenames.size(); ++i) + { + // Since OptiX 7.5.0 the program input can either be *.ptx source code or *.optixir binary code. + // The module filenames are automatically switched between *.ptx or *.optixir extension based on the definition of USE_OPTIX_IR + std::vector programData = readData(m_moduleFilenames[i]); + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixModuleCreate(m_optixContext, &mco, &pco, programData.data(), programData.size(), nullptr, nullptr, &modules[i]) ); +#else + OPTIX_CHECK( m_api.optixModuleCreateFromPTX(m_optixContext, &mco, &pco, programData.data(), programData.size(), nullptr, nullptr, &modules[i]) ); +#endif + } + + std::vector programGroupDescriptions(NUM_PROGRAM_GROUP_IDS); + memset(programGroupDescriptions.data(), 0, sizeof(OptixProgramGroupDesc) * programGroupDescriptions.size()); + + OptixProgramGroupDesc* pgd; + + // All of these first because they are SbtRecordHeader and put into a single CUDA memory block. + pgd = &programGroupDescriptions[PGID_RAYGENERATION]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->raygen.module = modules[MODULE_ID_RAYGENERATION]; + pgd->raygen.entryFunctionName = "__raygen__path_tracer_local_copy"; + + pgd = &programGroupDescriptions[PGID_EXCEPTION]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_EXCEPTION; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->exception.module = modules[MODULE_ID_EXCEPTION]; + pgd->exception.entryFunctionName = "__exception__all"; + + pgd = &programGroupDescriptions[PGID_MISS_RADIANCE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_MISS; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->miss.module = modules[MODULE_ID_MISS]; + switch (m_miss) + { + case 0: // Black, not a light. + pgd->miss.entryFunctionName = "__miss__env_null"; + break; + case 1: // Constant white environment. + default: + pgd->miss.entryFunctionName = "__miss__env_constant"; + break; + case 2: // Spherical HDR environment light. + pgd->miss.entryFunctionName = "__miss__env_sphere"; + break; + } + + pgd = &programGroupDescriptions[PGID_MISS_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_MISS; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->miss.module = nullptr; + pgd->miss.entryFunctionName = nullptr; // No miss program for shadow rays. + + // CALLABLES + // Lens Shader + pgd = &programGroupDescriptions[PGID_LENS_PINHOLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__pinhole"; + + pgd = &programGroupDescriptions[PGID_LENS_FISHEYE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__fisheye"; + + pgd = &programGroupDescriptions[PGID_LENS_SPHERE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LENS_SHADER]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sphere"; + + // Light Sampler + pgd = &programGroupDescriptions[PGID_LIGHT_ENV]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = (m_miss == 2) ? "__direct_callable__light_env_sphere" : "__direct_callable__light_env_constant"; // miss == 0 is not a light, use constant program. + + pgd = &programGroupDescriptions[PGID_LIGHT_AREA]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_LIGHT_SAMPLE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__light_parallelogram"; + + // BxDF sample and eval + pgd = &programGroupDescriptions[PGID_BRDF_DIFFUSE_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_DIFFUSE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_brdf_diffuse"; + + pgd = &programGroupDescriptions[PGID_BRDF_DIFFUSE_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_DIFFUSE]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_diffuse"; + + pgd = &programGroupDescriptions[PGID_BRDF_SPECULAR_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_brdf_specular"; + + pgd = &programGroupDescriptions[PGID_BRDF_SPECULAR_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_specular"; // black + + pgd = &programGroupDescriptions[PGID_BSDF_SPECULAR_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_bsdf_specular"; + + pgd = &programGroupDescriptions[PGID_BSDF_SPECULAR_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + // No implementation for __direct_callable__eval_bsdf_specular, it's specular. + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_specular"; // black + + pgd = &programGroupDescriptions[PGID_BRDF_GGX_SMITH_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_GGX_SMITH]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_brdf_ggx_smith"; + + pgd = &programGroupDescriptions[PGID_BRDF_GGX_SMITH_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_GGX_SMITH]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_ggx_smith"; + + pgd = &programGroupDescriptions[PGID_BSDF_GGX_SMITH_SAMPLE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_GGX_SMITH]; + pgd->callables.entryFunctionNameDC = "__direct_callable__sample_bsdf_ggx_smith"; + + pgd = &programGroupDescriptions[PGID_BSDF_GGX_SMITH_EVAL]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + // No implementation for __direct_callable__eval_ggx_smith, it's specular. + pgd->callables.moduleDC = modules[MODULE_ID_BXDF_SPECULAR]; + pgd->callables.entryFunctionNameDC = "__direct_callable__eval_brdf_specular"; // black + + // HitGroups are using SbtRecordGeometryInstanceData and will be put into a separate CUDA memory block. + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_CLOSESTHIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_ANYHIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow"; + + pgd = &programGroupDescriptions[PGID_HIT_RADIANCE_CUTOUT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleCH = modules[MODULE_ID_CLOSESTHIT]; + pgd->hitgroup.entryFunctionNameCH = "__closesthit__radiance"; + pgd->hitgroup.moduleAH = modules[MODULE_ID_ANYHIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__radiance_cutout"; + + pgd = &programGroupDescriptions[PGID_HIT_SHADOW_CUTOUT]; + pgd->kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + pgd->flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE; + pgd->hitgroup.moduleAH = modules[MODULE_ID_ANYHIT]; + pgd->hitgroup.entryFunctionNameAH = "__anyhit__shadow_cutout"; + + OptixProgramGroupOptions pgo = {}; // This is a just placeholder. + + std::vector programGroups(programGroupDescriptions.size()); + + OPTIX_CHECK( m_api.optixProgramGroupCreate(m_optixContext, programGroupDescriptions.data(), (unsigned int) programGroupDescriptions.size(), &pgo, nullptr, nullptr, programGroups.data()) ); + + OptixPipelineLinkOptions plo = {}; + + plo.maxTraceDepth = 2; +#if (OPTIX_VERSION < 70700) + // OptixPipelineLinkOptions debugLevel is only present in OptiX SDK versions before 7.7.0. + #if USE_DEBUG_EXCEPTIONS + plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; // Full debug. Never profile kernels with this setting! + #else + // Keep generated line info for Nsight Compute profiling. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) + #if (OPTIX_VERSION >= 70400) + plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; + #else + plo.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; + #endif + #endif // USE_DEBUG_EXCEPTIONS +#endif // 70700 + + OPTIX_CHECK( m_api.optixPipelineCreate(m_optixContext, &pco, &plo, programGroups.data(), (unsigned int) programGroups.size(), nullptr, nullptr, &m_pipeline) ); + + // STACK SIZES + OptixStackSizes ssp = {}; // Whole pipeline. + + for (auto pg: programGroups) + { + OptixStackSizes ss; + +#if (OPTIX_VERSION >= 70700) + OPTIX_CHECK( m_api.optixProgramGroupGetStackSize(pg, &ss, m_pipeline) ); +#else + OPTIX_CHECK( m_api.optixProgramGroupGetStackSize(pg, &ss) ); +#endif + + ssp.cssRG = std::max(ssp.cssRG, ss.cssRG); + ssp.cssMS = std::max(ssp.cssMS, ss.cssMS); + ssp.cssCH = std::max(ssp.cssCH, ss.cssCH); + ssp.cssAH = std::max(ssp.cssAH, ss.cssAH); + ssp.cssIS = std::max(ssp.cssIS, ss.cssIS); + ssp.cssCC = std::max(ssp.cssCC, ss.cssCC); + ssp.dssDC = std::max(ssp.dssDC, ss.dssDC); + } + + // Temporaries + unsigned int cssCCTree = ssp.cssCC; // Should be 0. No continuation callables in this pipeline. // maxCCDepth == 0 + unsigned int cssCHOrMSPlusCCTree = std::max(ssp.cssCH, ssp.cssMS) + cssCCTree; + + // Arguments + unsigned int directCallableStackSizeFromTraversal = ssp.dssDC; // maxDCDepth == 1 // FromTraversal: DC is invoked from IS or AH. // Possible stack size optimizations. + unsigned int directCallableStackSizeFromState = ssp.dssDC; // maxDCDepth == 1 // FromState: DC is invoked from RG, MS, or CH. // Possible stack size optimizations. + unsigned int continuationStackSize = ssp.cssRG + cssCCTree + cssCHOrMSPlusCCTree * (std::max(1u, plo.maxTraceDepth) - 1u) + + std::min(1u, plo.maxTraceDepth) * std::max(cssCHOrMSPlusCCTree, ssp.cssAH + ssp.cssIS); + unsigned int maxTraversableGraphDepth = 2; + + OPTIX_CHECK( m_api.optixPipelineSetStackSize(m_pipeline, directCallableStackSizeFromTraversal, directCallableStackSizeFromState, continuationStackSize, maxTraversableGraphDepth) ); + + // Set up the fixed portion of the Shader Binding Table (SBT) + + // Put all SbtRecordHeader types in one CUdeviceptr. + const int numHeaders = LAST_DIRECT_CALLABLE_ID - PGID_RAYGENERATION + 1; + + std::vector sbtRecordHeaders(numHeaders); + + for (int i = 0; i < numHeaders; ++i) + { + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_RAYGENERATION + i], &sbtRecordHeaders[i]) ); + } + + m_d_sbtRecordHeaders = memAlloc(sizeof(SbtRecordHeader) * numHeaders, OPTIX_SBT_RECORD_ALIGNMENT); + CU_CHECK( cuMemcpyHtoDAsync(m_d_sbtRecordHeaders, sbtRecordHeaders.data(), sizeof(SbtRecordHeader) * numHeaders, m_cudaStream) ); + + // Hit groups for radiance and shadow rays. These will be initialized later per instance. + // This just provides the headers with the program group indices. + + // Note that the SBT record data field is uninitialized after these! + // These are stored to be able to initialize the SBT hitGroup with the respective opaque and cutout shaders. + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_RADIANCE], &m_sbtRecordHitRadiance) ); + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_SHADOW], &m_sbtRecordHitShadow) ); + + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_RADIANCE_CUTOUT], &m_sbtRecordHitRadianceCutout) ); + OPTIX_CHECK( m_api.optixSbtRecordPackHeader(programGroups[PGID_HIT_SHADOW_CUTOUT], &m_sbtRecordHitShadowCutout) ); + + // Setup the OptixShaderBindingTable. + + m_sbt.raygenRecord = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_RAYGENERATION; + + m_sbt.exceptionRecord = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_EXCEPTION; + + m_sbt.missRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * PGID_MISS_RADIANCE; + m_sbt.missRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + m_sbt.missRecordCount = NUM_RAYTYPES; + + // These are going to be setup after the RenderGraph has been built! + //m_sbt.hitgroupRecordBase = reinterpret_cast(m_d_sbtRecordGeometryInstanceData); + //m_sbt.hitgroupRecordStrideInBytes = (unsigned int) sizeof(SbtRecordGeometryInstanceData); + //m_sbt.hitgroupRecordCount = NUM_RAYTYPES * numInstances; + + m_sbt.callablesRecordBase = m_d_sbtRecordHeaders + sizeof(SbtRecordHeader) * FIRST_DIRECT_CALLABLE_ID; + m_sbt.callablesRecordStrideInBytes = (unsigned int) sizeof(SbtRecordHeader); + m_sbt.callablesRecordCount = LAST_DIRECT_CALLABLE_ID - FIRST_DIRECT_CALLABLE_ID + 1; + + // After all required optixSbtRecordPackHeader, optixProgramGroupGetStackSize, and optixPipelineCreate + // calls have been done, the OptixProgramGroup and OptixModule objects can be destroyed. + for (auto pg: programGroups) + { + OPTIX_CHECK( m_api.optixProgramGroupDestroy(pg) ); + } + + for (auto m : modules) + { + OPTIX_CHECK(m_api.optixModuleDestroy(m)); + } +} + + +void Device::initCameras(const std::vector& cameras) +{ + // DAR FIXME PERF For simplicity, the public Device functions make sure to set the CUDA context and wait for the previous operation to finish. + // Faster would be to do that only when needed, which means the caller would be responsible to do the proper synchronization, + // while the functions themselves work as asynchronously as possible. + activateContext(); + synchronizeStream(); + + const int numCameras = static_cast(cameras.size()); + MY_ASSERT(0 < numCameras); // There must be at least one camera defintion or the lens shaders won't work. + + // The default initialization of numCameras is 0. + if (m_systemData.numCameras != numCameras) + { + memFree(reinterpret_cast(m_systemData.cameraDefinitions)); + m_systemData.cameraDefinitions = reinterpret_cast(memAlloc(sizeof(CameraDefinition) * numCameras, 16)); + } + + // Update the camera data. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.cameraDefinitions), cameras.data(), sizeof(CameraDefinition) * numCameras, m_cudaStream) ); + m_systemData.numCameras = numCameras; + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::initLights(const std::vector& lights) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT((sizeof(LightDefinition) & 15) == 0); // Verify float4 alignment. + + const int numLights = static_cast(lights.size()); // This is allowed to be zero. + + // The default initialization of m_systemData.numLights is 0. + if (m_systemData.numLights != numLights) + { + memFree(reinterpret_cast(m_systemData.lightDefinitions)); + m_systemData.lightDefinitions = nullptr; + + m_systemData.lightDefinitions = (0 < numLights) ? reinterpret_cast(memAlloc(sizeof(LightDefinition) * numLights, 16)) : nullptr; + } + + if (0 < numLights) + { + // DAR FIXME Move this from global launch parameters to the LightDefinition. + if (m_miss == 2) + { + std::map::const_iterator it = m_mapTextures.find(std::string("environment")); + if (it != m_mapTextures.end()) + { + const Texture* env = it->second; + + m_systemData.envTexture = env->getTextureObject(); + m_systemData.envCDF_U = reinterpret_cast(env->getCDF_U()); + m_systemData.envCDF_V = reinterpret_cast(env->getCDF_V()); + m_systemData.envWidth = env->getWidth(); + m_systemData.envHeight = env->getHeight(); + m_systemData.envIntegral = env->getIntegral(); + } + } + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.lightDefinitions), lights.data(), sizeof(LightDefinition) * numLights, m_cudaStream) ); + m_systemData.numLights = numLights; + } + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::initMaterials(const std::vector& materialsGUI) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT((sizeof(MaterialDefinition) & 15) == 0); // Verify float4 structure size for proper array element alignment. + + const int numMaterials = static_cast(materialsGUI.size()); + MY_ASSERT(0 < numMaterials); // There must be at least one material or the hit shaders won't work. + + // The default initialization of m_systemData.numMaterials is 0. + if (m_systemData.numMaterials != numMaterials) // FIXME Could grow only with additional capacity tracker. + { + memFree(reinterpret_cast(m_systemData.materialDefinitions)); + m_systemData.materialDefinitions = reinterpret_cast(memAlloc(sizeof(MaterialDefinition) * numMaterials, 16)); + + m_materials.resize(numMaterials); + } + + for (int i = 0; i < numMaterials; ++i) + { + const MaterialGUI& materialGUI = materialsGUI[i]; // Material UI data in the host. + MaterialDefinition& material = m_materials[i]; // MaterialDefinition data on the host in device layout. + + material.textureAlbedo = 0; + if (!materialGUI.nameTextureAlbedo.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureAlbedo); + MY_ASSERT(it != m_mapTextures.end()); + material.textureAlbedo = it->second->getTextureObject(); + } + + material.textureCutout = 0; + if (!materialGUI.nameTextureCutout.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureCutout); + MY_ASSERT(it != m_mapTextures.end()); + material.textureCutout = it->second->getTextureObject(); + } + + material.roughness = materialGUI.roughness; + material.indexBSDF = materialGUI.indexBSDF; + material.albedo = materialGUI.albedo; + material.absorption = make_float3(0.0f); // Null coefficient means no absorption active. + if (0.0f < materialGUI.absorptionScale) + { + // Calculate the effective absorption coefficient from the GUI parameters. + // The absorption coefficient components must all be > 0.0f if absorptionScale > 0.0f. + // Prevent logf(0.0f) which results in infinity. + const float x = -logf(fmax(0.0001f, materialGUI.absorptionColor.x)); + const float y = -logf(fmax(0.0001f, materialGUI.absorptionColor.y)); + const float z = -logf(fmax(0.0001f, materialGUI.absorptionColor.z)); + material.absorption = make_float3(x, y, z) * materialGUI.absorptionScale; + //std::cout << "absorption = (" << material.absorption.x << ", " << material.absorption.y << ", " << material.absorption.z << ")\n"; // DEBUG + } + material.ior = materialGUI.ior; + material.flags = (materialGUI.thinwalled) ? FLAG_THINWALLED : 0; + } + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_systemData.materialDefinitions), m_materials.data(), sizeof(MaterialDefinition) * numMaterials, m_cudaStream) ); + + m_isDirtySystemData = true; // Trigger full update of the device system data on the next launch. +} + +void Device::updateCamera(const int idCamera, const CameraDefinition& camera) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idCamera < m_systemData.numCameras); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.cameraDefinitions[idCamera]), &camera, sizeof(CameraDefinition), m_cudaStream) ); +} + +void Device::updateLight(const int idLight, const LightDefinition& light) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idLight < m_systemData.numLights); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.lightDefinitions[idLight]), &light, sizeof(LightDefinition), m_cudaStream) ); +} + +void Device::updateMaterial(const int idMaterial, const MaterialGUI& materialGUI) +{ + activateContext(); + synchronizeStream(); + + MY_ASSERT(idMaterial < m_materials.size()); + MaterialDefinition& material = m_materials[idMaterial]; // MaterialDefinition on the host in device layout. + + material.textureAlbedo = 0; + if (!materialGUI.nameTextureAlbedo.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureAlbedo); + MY_ASSERT(it != m_mapTextures.end()); + material.textureAlbedo = it->second->getTextureObject(); + } + + // The material system in this renderer does not support switching cutout opacity at runtime. + // It's defined by the presence of the "cutoutTexture" filename in the material parameters. + material.textureCutout = 0; + if (!materialGUI.nameTextureCutout.empty()) + { + std::map::const_iterator it = m_mapTextures.find(materialGUI.nameTextureCutout); + MY_ASSERT(it != m_mapTextures.end()); + material.textureCutout = it->second->getTextureObject(); + } + + material.roughness = materialGUI.roughness; + material.indexBSDF = materialGUI.indexBSDF; + material.albedo = materialGUI.albedo; + material.absorption = make_float3(0.0f); // Null coefficient means no absorption active. + if (0.0f < materialGUI.absorptionScale) + { + // Calculate the effective absorption coefficient from the GUI parameters. + // The absorption coefficient components must all be > 0.0f if absoprionScale > 0.0f. + // Prevent logf(0.0f) which results in infinity. + const float x = -logf(fmax(0.0001f, materialGUI.absorptionColor.x)); + const float y = -logf(fmax(0.0001f, materialGUI.absorptionColor.y)); + const float z = -logf(fmax(0.0001f, materialGUI.absorptionColor.z)); + material.absorption = make_float3(x, y, z) * materialGUI.absorptionScale; + //std::cout << "absorption = (" << material.absorption.x << ", " << material.absorption.y << ", " << material.absorption.z << ")\n"; // DEBUG + } + material.ior = materialGUI.ior; + material.flags = (materialGUI.thinwalled) ? FLAG_THINWALLED : 0; + + // Copy only the one changed material. No need to trigger an update of the system data, because the m_systemData.materialDefinitions pointer itself didn't change. + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_systemData.materialDefinitions[idMaterial]), &material, sizeof(MaterialDefinition), m_cudaStream) ); +} + + +static int2 calculateTileShift(const int2 tileSize) +{ + int xShift = 0; + while (xShift < 32 && (tileSize.x & (1 << xShift)) == 0) + { + ++xShift; + } + + int yShift = 0; + while (yShift < 32 && (tileSize.y & (1 << yShift)) == 0) + { + ++yShift; + } + + MY_ASSERT(xShift < 32 && yShift < 32); // Can only happen for zero input. + + return make_int2(xShift, yShift); +} + + +void Device::setState(const DeviceState& state) +{ + activateContext(); + synchronizeStream(); + + // Special handling from the previous DeviceMultiGPULocalCopy class. + if (m_systemData.resolution != state.resolution || + m_systemData.tileSize != state.tileSize) + { + // Calculate the new launch width for the tiled rendering. + // It must be a multiple of the tileSize width, otherwise the right-most tiles will not get filled correctly. + const int width = (state.resolution.x + m_count - 1) / m_count; + const int mask = state.tileSize.x - 1; + m_launchWidth = (width + mask) & ~mask; // == ((width + (tileSize - 1)) / tileSize.x) * tileSize.x; + } + + if (m_systemData.resolution != state.resolution) + { + m_systemData.resolution = state.resolution; + + m_isDirtyOutputBuffer = true; + m_isDirtySystemData = true; + } + + if (m_systemData.tileSize != state.tileSize) + { + m_systemData.tileSize = state.tileSize; + m_systemData.tileShift = calculateTileShift(m_systemData.tileSize); + m_isDirtySystemData = true; + } + + if (m_systemData.samplesSqrt != state.samplesSqrt) + { + m_systemData.samplesSqrt = state.samplesSqrt; + m_isDirtySystemData = true; + } + + if (m_systemData.lensShader != state.lensShader) + { + m_systemData.lensShader = state.lensShader; + m_isDirtySystemData = true; + } + + if (m_systemData.pathLengths != state.pathLengths) + { + m_systemData.pathLengths = state.pathLengths; + m_isDirtySystemData = true; + } + + if (m_systemData.sceneEpsilon != state.epsilonFactor * SCENE_EPSILON_SCALE) + { + m_systemData.sceneEpsilon = state.epsilonFactor * SCENE_EPSILON_SCALE; + m_isDirtySystemData = true; + } + + if (m_systemData.envRotation != state.envRotation) + { + // FIXME Implement free rotation with a rotation matrix. + m_systemData.envRotation = state.envRotation; + m_isDirtySystemData = true; + } + +#if USE_TIME_VIEW + if (m_systemData.clockScale != state.clockFactor * CLOCK_FACTOR_SCALE) + { + m_systemData.clockScale = state.clockFactor * CLOCK_FACTOR_SCALE; + m_isDirtySystemData = true; + } +#endif +} + + +GeometryData Device::createGeometry(std::shared_ptr geometry) +{ + activateContext(); + synchronizeStream(); + + GeometryData data; + + data.primitiveType = PT_TRIANGLES; + data.owner = m_index; + + const std::vector& attributes = geometry->getAttributes(); + const std::vector& indices = geometry->getIndices(); + + const size_t attributesSizeInBytes = sizeof(TriangleAttributes) * attributes.size(); + const size_t indicesSizeInBytes = sizeof(unsigned int) * indices.size(); + + data.d_attributes = memAlloc(attributesSizeInBytes, 16); + data.d_indices = memAlloc(indicesSizeInBytes, sizeof(unsigned int)); + + data.numAttributes = attributes.size(); + data.numIndices = indices.size(); + + CU_CHECK( cuMemcpyHtoDAsync(data.d_attributes, attributes.data(), attributesSizeInBytes, m_cudaStream) ); + CU_CHECK( cuMemcpyHtoDAsync(data.d_indices, indices.data(), indicesSizeInBytes, m_cudaStream) ); + + OptixBuildInput buildInput = {}; + + buildInput.type = OPTIX_BUILD_INPUT_TYPE_TRIANGLES; + + buildInput.triangleArray.vertexFormat = OPTIX_VERTEX_FORMAT_FLOAT3; + buildInput.triangleArray.vertexStrideInBytes = sizeof(TriangleAttributes); + buildInput.triangleArray.numVertices = static_cast(attributes.size()); + buildInput.triangleArray.vertexBuffers = &data.d_attributes; + + buildInput.triangleArray.indexFormat = OPTIX_INDICES_FORMAT_UNSIGNED_INT3; + buildInput.triangleArray.indexStrideInBytes = sizeof(unsigned int) * 3; + + buildInput.triangleArray.numIndexTriplets = static_cast(indices.size()) / 3; + buildInput.triangleArray.indexBuffer = data.d_indices; + + unsigned int inputFlags[1] = { OPTIX_GEOMETRY_FLAG_NONE }; + + buildInput.triangleArray.flags = inputFlags; + buildInput.triangleArray.numSbtRecords = 1; + + OptixAccelBuildOptions accelBuildOptions = {}; + + // Note that OPTIX_BUILD_FLAG_PREFER_FAST_TRACE will use more memeory, which performs worse when sharing across the NVLINK bridge which is much slower than VRAM accesses. + accelBuildOptions.buildFlags = /* OPTIX_BUILD_FLAG_PREFER_FAST_TRACE | */ OPTIX_BUILD_FLAG_ALLOW_COMPACTION; + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &buildInput, 1, &accelBufferSizes) ); + + data.d_gas = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); // This is a temporary buffer. The Compaction will be the static one! + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OptixAccelEmitDesc accelEmit = {}; + + accelEmit.result = memAlloc(sizeof(size_t), sizeof(size_t), cuda::USAGE_TEMP); + accelEmit.type = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE; + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &buildInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + data.d_gas, accelBufferSizes.outputSizeInBytes, + &data.traversable, &accelEmit, 1) ); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); + + size_t sizeCompact; + + CU_CHECK( cuMemcpyDtoH(&sizeCompact, accelEmit.result, sizeof(size_t)) ); // Synchronous. + + memFree(accelEmit.result); + memFree(d_tmp); + + // Compact the AS only when possible. This can save more than half the memory on RTX boards. + if (sizeCompact < accelBufferSizes.outputSizeInBytes) + { + CUdeviceptr d_gasCompact = memAlloc(sizeCompact, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); // This is the static GAS allocation! + + OPTIX_CHECK( m_api.optixAccelCompact(m_optixContext, m_cudaStream, data.traversable, d_gasCompact, sizeCompact, &data.traversable) ); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); // Must finish accessing data.d_gas source before it can be freed and overridden. + + memFree(data.d_gas); + + data.d_gas = d_gasCompact; + + //std::cout << "Compaction saved " << accelBufferSizes.outputSizeInBytes - sizeCompact << '\n'; // DEBUG + accelBufferSizes.outputSizeInBytes = sizeCompact; // DEBUG for the std::cout below. + } + + // Return the relocation info for this GAS traversable handle from this device's OptiX context. + // It's used to assert that the GAS is compatible across devices which means NVLINK peer-to-peer sharing is allowed. + // (This is more meant as example code, because in NVLINK islands the GPU configuration must be homogeneous and addresses are unique with UVA.) + OPTIX_CHECK( m_api.optixAccelGetRelocationInfo(m_optixContext, data.traversable, &data.info) ); + + //std::cout << "createGeometry() device ordinal = " << m_ordinal << ": attributes = " << attributesSizeInBytes << ", indices = " << indicesSizeInBytes << ", GAS = " << accelBufferSizes.outputSizeInBytes << "\n"; // DEBUG + + return data; +} + +void Device::destroyGeometry(GeometryData& data) +{ + memFree(data.d_gas); + memFree(data.d_indices); + memFree(data.d_attributes); +} + +void Device::createInstance(const GeometryData& geometryData, const InstanceData& instanceData, const float matrix[12]) +{ + activateContext(); + synchronizeStream(); + + // If the GeometryData is owned by a different device, that means it has been created in a different OptiX context. + // Then check if the data is compatible with the OptiX context on this device. + // If yes, it can be shared via peer-to-peer as well because the device pointers are all unique with UVA. It's not actually relocated. + // If not, no instance with this geometry data is created, which means there will be rendering corruption on this device. + if (m_index != geometryData.owner) // No need to check compatibility on the same device. + { + int compatible = 0; + +#if (OPTIX_VERSION >= 70600) + OPTIX_CHECK( m_api.optixCheckRelocationCompatibility(m_optixContext, &geometryData.info, &compatible) ); +#else + OPTIX_CHECK( m_api.optixAccelCheckRelocationCompatibility(m_optixContext, &geometryData.info, &compatible) ); +#endif + + if (compatible == 0) + { + std::cerr << "ERROR: createInstance() device index " << m_index << " is not AS-compatible with the GeometryData owner " << geometryData.owner << '\n'; + MY_ASSERT(!"createInstance() AS incompatible"); + return; // This means this geometry will not actually be present in the OptiX render graph of this device! + } + } + + MY_ASSERT(0 <= instanceData.idMaterial); + + OptixInstance instance = {}; + + const unsigned int id = static_cast(m_instances.size()); + memcpy(instance.transform, matrix, sizeof(float) * 12); + instance.instanceId = id; // User defined instance index, queried with optixGetInstanceId(). + instance.visibilityMask = 255; + instance.sbtOffset = id * NUM_RAYTYPES; // This controls the SBT instance offset! This must be set explicitly when each instance is using a separate BLAS. + instance.flags = OPTIX_INSTANCE_FLAG_NONE; + instance.traversableHandle = geometryData.traversable; // Shared! + + m_instances.push_back(instance); // OptixInstance data + m_instanceData.push_back(instanceData); // SBT record data: idGeometry, idMaterial, idLight +} + + +void Device::createTLAS() +{ + activateContext(); + synchronizeStream(); + + // Construct the TLAS by attaching all flattened instances. + const size_t instancesSizeInBytes = sizeof(OptixInstance) * m_instances.size(); + + CUdeviceptr d_instances = memAlloc(instancesSizeInBytes, OPTIX_INSTANCE_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + CU_CHECK( cuMemcpyHtoDAsync(d_instances, m_instances.data(), instancesSizeInBytes, m_cudaStream) ); + + OptixBuildInput instanceInput = {}; + + instanceInput.type = OPTIX_BUILD_INPUT_TYPE_INSTANCES; + instanceInput.instanceArray.instances = d_instances; + instanceInput.instanceArray.numInstances = static_cast(m_instances.size()); + + OptixAccelBuildOptions accelBuildOptions = {}; + + accelBuildOptions.buildFlags = OPTIX_BUILD_FLAG_NONE; + accelBuildOptions.operation = OPTIX_BUILD_OPERATION_BUILD; + + OptixAccelBufferSizes accelBufferSizes; + + OPTIX_CHECK( m_api.optixAccelComputeMemoryUsage(m_optixContext, &accelBuildOptions, &instanceInput, 1, &accelBufferSizes ) ); + + m_d_ias = memAlloc(accelBufferSizes.outputSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT); + + CUdeviceptr d_tmp = memAlloc(accelBufferSizes.tempSizeInBytes, OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT, cuda::USAGE_TEMP); + + OPTIX_CHECK( m_api.optixAccelBuild(m_optixContext, m_cudaStream, + &accelBuildOptions, &instanceInput, 1, + d_tmp, accelBufferSizes.tempSizeInBytes, + m_d_ias, accelBufferSizes.outputSizeInBytes, + &m_systemData.topObject, nullptr, 0)); + + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); + + memFree(d_tmp); + memFree(d_instances); +} + + +void Device::createHitGroupRecords(const std::vector& geometryData, const unsigned int stride, const unsigned int index) +{ + activateContext(); + synchronizeStream(); + + const unsigned int numInstances = static_cast(m_instances.size()); + + m_sbtRecordGeometryInstanceData.resize(NUM_RAYTYPES * numInstances); + + for (unsigned int i = 0; i < numInstances; ++i) + { + const InstanceData& inst = m_instanceData[i]; + const GeometryData& geom = geometryData[inst.idGeometry * stride + index]; // GeometryData is per island only and shared among all devices in one island. + + const int idx = i * NUM_RAYTYPES; // idx == radiance ray, idx + 1 == shadow ray + + switch (geom.primitiveType) + { + case PT_UNKNOWN: // This is a fatal error and the launch will fail! + default: + std::cerr << "ERROR: createHitGroupRecords() GeometryData.primitiveType unknown.\n"; + MY_ASSERT(!"GeometryData.primitiveType unknown"); + break; + + case PT_TRIANGLES: + if (m_materials[inst.idMaterial].textureCutout == 0) + { + // Only update the header to switch the program hit group. The SBT record inst field doesn't change. + // FIXME Just remember the pointer and have one memcpy() at the end. + memcpy(m_sbtRecordGeometryInstanceData[idx ].header, m_sbtRecordHitRadiance.header, OPTIX_SBT_RECORD_HEADER_SIZE); + memcpy(m_sbtRecordGeometryInstanceData[idx + 1].header, m_sbtRecordHitShadow.header, OPTIX_SBT_RECORD_HEADER_SIZE); + } + else + { + memcpy(m_sbtRecordGeometryInstanceData[idx ].header, m_sbtRecordHitRadianceCutout.header, OPTIX_SBT_RECORD_HEADER_SIZE); + memcpy(m_sbtRecordGeometryInstanceData[idx + 1].header, m_sbtRecordHitShadowCutout.header, OPTIX_SBT_RECORD_HEADER_SIZE); + } + break; + } + + m_sbtRecordGeometryInstanceData[idx ].data.attributes = geom.d_attributes; + m_sbtRecordGeometryInstanceData[idx ].data.indices = geom.d_indices; + m_sbtRecordGeometryInstanceData[idx ].data.idMaterial = inst.idMaterial; + m_sbtRecordGeometryInstanceData[idx ].data.idLight = inst.idLight; + + m_sbtRecordGeometryInstanceData[idx + 1].data.attributes = geom.d_attributes; + m_sbtRecordGeometryInstanceData[idx + 1].data.indices = geom.d_indices; + m_sbtRecordGeometryInstanceData[idx + 1].data.idMaterial = inst.idMaterial; + m_sbtRecordGeometryInstanceData[idx + 1].data.idLight = inst.idLight; + } + + m_d_sbtRecordGeometryInstanceData = reinterpret_cast(memAlloc(sizeof(SbtRecordGeometryInstanceData) * NUM_RAYTYPES * numInstances, OPTIX_SBT_RECORD_ALIGNMENT) ); + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_d_sbtRecordGeometryInstanceData), m_sbtRecordGeometryInstanceData.data(), sizeof(SbtRecordGeometryInstanceData) * NUM_RAYTYPES * numInstances, m_cudaStream) ); + + m_sbt.hitgroupRecordBase = reinterpret_cast(m_d_sbtRecordGeometryInstanceData); + m_sbt.hitgroupRecordStrideInBytes = (unsigned int) sizeof(SbtRecordGeometryInstanceData); + m_sbt.hitgroupRecordCount = NUM_RAYTYPES * numInstances; +} + +// Given an OpenGL UUID find the matching CUDA device. +//bool Device::matchUUID(const char* uuid) +//{ +// for (size_t i = 0; i < 16; ++i) +// { +// if (m_deviceUUID.bytes[i] != uuid[i]) +// { +// return false; +// } +// } +// return true; +//} + +// Given an OpenGL LUID find the matching CUDA device. +//bool Device::matchLUID(const char* luid, const unsigned int nodeMask) +//{ +// if ((m_nodeMask & nodeMask) == 0) +// { +// return false; +// } +// for (size_t i = 0; i < 8; ++i) +// { +// if (m_deviceLUID[i] != luid[i]) +// { +// return false; +// } +// } +// return true; +//} + + +void Device::activateContext() const +{ + CU_CHECK( cuCtxSetCurrent(m_cudaContext) ); +} + +void Device::synchronizeStream() const +{ + CU_CHECK( cuStreamSynchronize(m_cudaStream) ); +} + +void Device::render(const unsigned int iterationIndex, void** buffer) +{ + activateContext(); + + m_systemData.iterationIndex = iterationIndex; + + if (m_isDirtyOutputBuffer) + { + MY_ASSERT(buffer != nullptr); + if (*buffer == nullptr) // The buffer is nullptr for the device which should allocate the full resolution buffers. This device is called first! + { + // Only allocate the host buffer once, not per each device. + m_bufferHost.resize(m_systemData.resolution.x * m_systemData.resolution.y); + + // Note that this requires that all other devices have finished accessing this buffer, but that is automatically the case + // after calling Device::setState() which is the only place which can change the resolution. + memFree(m_systemData.outputBuffer); // This is asynchronous and the pointer can be 0. + m_systemData.outputBuffer = memAlloc(sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, sizeof(float4)); + + *buffer = reinterpret_cast(m_systemData.outputBuffer); // Set the pointer, so that other devices don't allocate it. It's not shared! + + // This is a temporary buffer on the primary board which is used by the compositor. The texelBuffer needs to stay intact for the accumulation. + memFree(m_systemData.tileBuffer); + m_systemData.tileBuffer = memAlloc(sizeof(float4) * m_launchWidth * m_systemData.resolution.y, sizeof(float4)); + + m_d_compositorData = memAlloc(sizeof(CompositorData), 16); // DAR FIXME Check alignment. Could be reduced to 8. + + m_ownsSharedBuffer = true; // Indicate which device owns the m_systemData.outputBuffer and m_bufferHost so that display routines can assert. + + //if (m_cudaGraphicsResource != nullptr) // Need to unregister texture or PBO before resizing it. + //{ + // CU_CHECK( cuGraphicsUnregisterResource(m_cudaGraphicsResource) ); + //} + + //switch (m_interop) + //{ + // case INTEROP_MODE_OFF: + // break; + + // case INTEROP_MODE_TEX: + // // Let the device which is called first resize the OpenGL texture. + // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, (GLvoid*) m_bufferHost.data()); // RGBA32F + // glFinish(); // Synchronize with following CUDA operations. + + // CU_CHECK( cuGraphicsGLRegisterImage(&m_cudaGraphicsResource, m_tex, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD) ); + // break; + + // case INTEROP_MODE_PBO: + // glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); + // glBufferData(GL_PIXEL_UNPACK_BUFFER, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4), nullptr, GL_DYNAMIC_DRAW); + // glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + // CU_CHECK( cuGraphicsGLRegisterBuffer(&m_cudaGraphicsResource, m_pbo, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD) ); + // break; + //} + } + // Allocate a GPU local buffer in the per-device launch size. This is where the accumulation happens. + memFree(m_systemData.texelBuffer); + m_systemData.texelBuffer = memAlloc(sizeof(float4) * m_launchWidth * m_systemData.resolution.y, sizeof(float4)); + + m_isDirtyOutputBuffer = false; // Buffer is allocated with new size. + m_isDirtySystemData = true; // Now the sysData on the device needs to be updated, and that needs a sync! + } + + if (m_isDirtySystemData) // Update the whole SystemData block because more than the iterationIndex changed. This normally means a GUI interaction. Just sync. + { + synchronizeStream(); + + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(m_d_systemData), &m_systemData, sizeof(SystemData), m_cudaStream) ); + m_isDirtySystemData = false; + } + else // Just copy the new iterationIndex. + { + synchronizeStream(); + + // FIXME PERF For really asynchronous copies of the iteration indices, multiple source pointers are required. Good that I know the number of iterations upfront! + CU_CHECK( cuMemcpyHtoDAsync(reinterpret_cast(&m_d_systemData->iterationIndex), &m_systemData.iterationIndex, sizeof(unsigned int), m_cudaStream) ); + } + + // Note the launch width per device to render in tiles. + OPTIX_CHECK( m_api.optixLaunch(m_pipeline, m_cudaStream, reinterpret_cast(m_d_systemData), sizeof(SystemData), &m_sbt, m_launchWidth, m_systemData.resolution.y, /* depth */ 1) ); +} + + +//void Device::updateDisplayTexture() +//{ +// activateContext(); +// +// // Only allow this on the device which owns the shared peer-to-peer buffer which also resized the host buffer to copy this to the host. +// MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer && m_tex != 0); +// +// switch (m_interop) +// { +// case INTEROP_MODE_OFF: +// // Copy the GPU local render buffer into host and update the HDR texture image from there. +// CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); +// synchronizeStream(); // Wait for the buffer to arrive on the host. +// +// glActiveTexture(GL_TEXTURE0); +// glBindTexture(GL_TEXTURE_2D, m_tex); +// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, m_bufferHost.data()); // RGBA32F from host buffer data. +// break; +// +// case INTEROP_MODE_TEX: +// { +// // Map the Texture object directly and copy the output buffer. +// CU_CHECK( cuGraphicsMapResources(1, &m_cudaGraphicsResource, m_cudaStream )); // This is an implicit cuSynchronizeStream(). +// +// CUarray dstArray = nullptr; +// +// CU_CHECK( cuGraphicsSubResourceGetMappedArray(&dstArray, m_cudaGraphicsResource, 0, 0) ); // arrayIndex = 0, mipLevel = 0 +// +// CUDA_MEMCPY3D params = {}; +// +// params.srcMemoryType = CU_MEMORYTYPE_DEVICE; +// params.srcDevice = m_systemData.outputBuffer; +// params.srcPitch = m_systemData.resolution.x * sizeof(float4); +// params.srcHeight = m_systemData.resolution.y; +// +// params.dstMemoryType = CU_MEMORYTYPE_ARRAY; +// params.dstArray = dstArray; +// params.WidthInBytes = m_systemData.resolution.x * sizeof(float4); +// params.Height = m_systemData.resolution.y; +// params.Depth = 1; +// +// CU_CHECK( cuMemcpy3D(¶ms) ); // Copy from linear to array layout. +// +// CU_CHECK( cuGraphicsUnmapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). +// } +// break; +// +// case INTEROP_MODE_PBO: // This contains two device-to-device copies and is just for demonstration. Use INTEROP_MODE_TEX when possible. +// { +// size_t size = 0; +// CUdeviceptr d_ptr; +// +// CU_CHECK( cuGraphicsMapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). +// CU_CHECK( cuGraphicsResourceGetMappedPointer(&d_ptr, &size, m_cudaGraphicsResource) ); // The pointer can change on every map! +// MY_ASSERT(m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4) <= size); +// CU_CHECK( cuMemcpyDtoDAsync(d_ptr, m_systemData.outputBuffer, m_systemData.resolution.x * m_systemData.resolution.y * sizeof(float4), m_cudaStream) ); // PERF PBO interop is kind of moot with a direct texture access. +// CU_CHECK( cuGraphicsUnmapResources(1, &m_cudaGraphicsResource, m_cudaStream) ); // This is an implicit cuSynchronizeStream(). +// +// glActiveTexture(GL_TEXTURE0); +// glBindTexture(GL_TEXTURE_2D, m_tex); +// glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); +// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (GLsizei) m_systemData.resolution.x, (GLsizei) m_systemData.resolution.y, 0, GL_RGBA, GL_FLOAT, (GLvoid*) 0); // RGBA32F from byte offset 0 in the pixel unpack buffer. +// glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +// } +// break; +// } +//} + + +const void* Device::getOutputBufferHost() +{ + activateContext(); + + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer); // Only allow this on the device which owns the shared peer-to-peer buffer and resized the host buffer to copy this to the host. + + // Note that the caller takes care to sync the other devices before calling into here or this image might not be complete! + CU_CHECK( cuMemcpyDtoHAsync(m_bufferHost.data(), m_systemData.outputBuffer, sizeof(float4) * m_systemData.resolution.x * m_systemData.resolution.y, m_cudaStream) ); + + synchronizeStream(); // Wait for the buffer to arrive on the host. + + return m_bufferHost.data(); +} + +// DAR FIXME The focus of this application is multi-GPU peer-to-peer resource sharing. +// While this also works with single-GPU, it's doing an unnecessary device-to-device compositing of the already final image. +// There isn't even a need to have a tileBuffer and texelBuffer in that case. Though this is only called once a second normally. +void Device::compositor(Device* other) +{ + MY_ASSERT(!m_isDirtyOutputBuffer && m_ownsSharedBuffer); + + // The compositor sources the tileBuffer, which is only allocated on the primary device. + // The texelBuffer is a GPU local buffer on all devices and contains the accumulation. + if (this == other) + { + activateContext(); + + CU_CHECK( cuMemcpyDtoDAsync(m_systemData.tileBuffer, m_systemData.texelBuffer, + sizeof(float4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); + } + else + { + // Make sure the other device has finished rendering! Otherwise there can be checkerboard corruption visible. + other->activateContext(); + other->synchronizeStream(); + + activateContext(); + + CU_CHECK( cuMemcpyPeerAsync(m_systemData.tileBuffer, m_cudaContext, other->m_systemData.texelBuffer, other->m_cudaContext, + sizeof(float4) * m_launchWidth * m_systemData.resolution.y, m_cudaStream) ); + } + + CompositorData compositorData; // DAR FIXME This needs to be persistent per Device to allow async copies! + + compositorData.outputBuffer = m_systemData.outputBuffer; + compositorData.tileBuffer = m_systemData.tileBuffer; + compositorData.resolution = m_systemData.resolution; + compositorData.tileSize = m_systemData.tileSize; + compositorData.tileShift = m_systemData.tileShift; + compositorData.launchWidth = m_launchWidth; + compositorData.deviceCount = m_systemData.deviceCount; + compositorData.deviceIndex = other->m_systemData.deviceIndex; // This is the only value which changes per device. + + // Need a synchronous copy here to not overwrite or delete the compositorData above. + CU_CHECK( cuMemcpyHtoD(m_d_compositorData, &compositorData, sizeof(CompositorData)) ); + + void* args[1] = { &m_d_compositorData }; + + const int blockDimX = std::min(compositorData.tileSize.x, 16); + const int blockDimY = std::min(compositorData.tileSize.y, 16); + + const int gridDimX = (m_launchWidth + blockDimX - 1) / blockDimX; + const int gridDimY = (compositorData.resolution.y + blockDimY - 1) / blockDimY; + + MY_ASSERT(gridDimX <= m_deviceAttribute.maxGridDimX && + gridDimY <= m_deviceAttribute.maxGridDimY); + + // Reduction kernel with launch dimension of height blocks with 32 threads. + CU_CHECK( cuLaunchKernel(m_functionCompositor, // CUfunction f, + gridDimX, // unsigned int gridDimX, + gridDimY, // unsigned int gridDimY, + 1, // unsigned int gridDimZ, + blockDimX, // unsigned int blockDimX, + blockDimY, // unsigned int blockDimY, + 1, // unsigned int blockDimZ, + 0, // unsigned int sharedMemBytes, + m_cudaStream, // CUstream hStream, + args, // void **kernelParams, + nullptr) ); // void **extra + + synchronizeStream(); +} + + +// Arena version of cuMemAlloc(), but asynchronous! +CUdeviceptr Device::memAlloc(const size_t size, const size_t alignment, const cuda::Usage usage) +{ + return m_allocator->alloc(size, alignment, usage); +} + +// Arena version of cuMemFree(), but asynchronous! +void Device::memFree(const CUdeviceptr ptr) +{ + m_allocator->free(ptr); +} + +// This is getting the current VRAM situation on the device. +// Means this includes everything running on the GPU and all allocations done for textures and the ArenaAllocator. +// (Currently not used for picking the home device for the next shared allocation, because with the ArenaAlloocator that isn't fine grained.) +size_t Device::getMemoryFree() const +{ + activateContext(); + + size_t sizeFree = 0; + size_t sizeTotal = 0; + + CU_CHECK( cuMemGetInfo(&sizeFree, &sizeTotal) ); + + return sizeFree; +} + +// getMemoryAllocated() returns the sum of all allocated blocks inside arenas and the texture sizes in bytes (without GPU alignment and padding). +// Using this in Raytracer::getDeviceHome() assumes the free VRAM amount is about equal on the devices in an island. +size_t Device::getMemoryAllocated() const +{ + return m_allocator->getSizeMemoryAllocated() + m_sizeMemoryTextureArrays; +} + +Texture* Device::initTexture(const std::string& name, const Picture* picture, const unsigned int flags) +{ + activateContext(); + synchronizeStream(); + + Texture* texture; + + // DAR FIXME Only using the filename as key and not the load flags. This will not support the same image with different flags. + std::map::const_iterator it = m_mapTextures.find(name); + if (it == m_mapTextures.end()) + { + texture = new Texture(this); // This device is the owner of the CUarray or CUmipmappedArray data. + texture->create(picture, flags); + + m_sizeMemoryTextureArrays += texture->getSizeBytes(); // Texture memory tracking. + + m_mapTextures[name] = texture; + + //std::cout << "initTexture() device ordinal = " << m_ordinal << ": name = " << name << '\n'; // DEBUG + } + else + { + texture = it->second; // Return the existing texture under this name. + + //std::cout << "initTexture() Texture " << name << " reused\n"; // DEBUG + } + + return texture; // Not used when not sharing. +} + + +void Device::shareTexture(const std::string& name, const Texture* shared) +{ + activateContext(); + synchronizeStream(); + + std::map::const_iterator it = m_mapTextures.find(name); + + if (it == m_mapTextures.end()) + { + Texture* texture = new Texture(shared->getOwner()); + + texture->create(shared); // No texture memory tracking in this case. Arrays are reused. + + m_mapTextures[name] = texture; + } +} diff --git a/apps/bench_shared_offscreen/src/NVMLImpl.cpp b/apps/bench_shared_offscreen/src/NVMLImpl.cpp new file mode 100644 index 00000000..3afedead --- /dev/null +++ b/apps/bench_shared_offscreen/src/NVMLImpl.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +// The cfgmgr32 header is necessary for interrogating driver information in the registry. +#include +// For convenience the library is also linked in automatically using the #pragma command. +#pragma comment(lib, "Cfgmgr32.lib") +#else +#include +#endif + +#include "inc/NVMLImpl.h" + +#include +#include + +#ifdef _WIN32 + +static void *nvmlLoadFromDriverStore(const char* nvmlDllName) +{ + void* handle = NULL; + + // We are going to look for the OpenGL driver which lives next to nvoptix.dll and nvml.dll. + // 0 (null) will be returned if any errors occured. + + static const char* deviceInstanceIdentifiersGUID = "{4d36e968-e325-11ce-bfc1-08002be10318}"; + const ULONG flags = CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT; + ULONG deviceListSize = 0; + + if (CM_Get_Device_ID_List_SizeA(&deviceListSize, deviceInstanceIdentifiersGUID, flags) != CR_SUCCESS) + { + return NULL; + } + + char* deviceNames = (char*) malloc(deviceListSize); + + if (CM_Get_Device_ID_ListA(deviceInstanceIdentifiersGUID, deviceNames, deviceListSize, flags)) + { + free(deviceNames); + return NULL; + } + + DEVINST devID = 0; + + // Continue to the next device if errors are encountered. + for (char* deviceName = deviceNames; *deviceName; deviceName += strlen(deviceName) + 1) + { + if (CM_Locate_DevNodeA(&devID, deviceName, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) + { + continue; + } + + HKEY regKey = 0; + if (CM_Open_DevNode_Key(devID, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, ®Key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) + { + continue; + } + + const char* valueName = "OpenGLDriverName"; + DWORD valueSize = 0; + + LSTATUS ret = RegQueryValueExA(regKey, valueName, NULL, NULL, NULL, &valueSize); + if (ret != ERROR_SUCCESS) + { + RegCloseKey(regKey); + continue; + } + + char* regValue = (char*) malloc(valueSize); + ret = RegQueryValueExA(regKey, valueName, NULL, NULL, (LPBYTE) regValue, &valueSize); + if (ret != ERROR_SUCCESS) + { + free(regValue); + RegCloseKey(regKey); + continue; + } + + // Strip the OpenGL driver dll name from the string then create a new string with + // the path and the nvoptix.dll name + for (int i = valueSize - 1; i >= 0 && regValue[i] != '\\'; --i) + { + regValue[i] = '\0'; + } + + size_t newPathSize = strlen(regValue) + strlen(nvmlDllName) + 1; + char* dllPath = (char*) malloc(newPathSize); + strcpy(dllPath, regValue); + strcat(dllPath, nvmlDllName); + + free(regValue); + RegCloseKey(regKey); + + handle = LoadLibraryA((LPCSTR) dllPath); + free(dllPath); + + if (handle) + { + break; + } + } + + free(deviceNames); + + return handle; +} + +static void *nvmlLoadFromSystemDirectory(const char* nvmlDllName) +{ + // Get the size of the path first, then allocate. + const unsigned int size = GetSystemDirectoryA(NULL, 0); + if (size == 0) + { + // Couldn't get the system path size, so bail. + return NULL; + } + + // Alloc enough memory to concatenate with "\\nvml.dll". + const size_t pathSize = size + 1 + strlen(nvmlDllName); + + char* systemPath = (char*) malloc(pathSize); + + if (GetSystemDirectoryA(systemPath, size) != size - 1) + { + // Something went wrong. + free(systemPath); + return NULL; + } + + strcat(systemPath, "\\"); + strcat(systemPath, nvmlDllName); + + void* handle = LoadLibraryA(systemPath); + + free(systemPath); + + return handle; +} + +static void* nvmlLoadWindowsDll(void) +{ + const char* nvmlDllName = "nvml.dll"; + + void* handle = nvmlLoadFromDriverStore(nvmlDllName); + + if (!handle) + { + handle = nvmlLoadFromSystemDirectory(nvmlDllName); + // If the nvml.dll is still not found here, something is wrong with the display driver installation. + } + + return handle; +} +#endif + + +NVMLImpl::NVMLImpl() + : m_handle(0) +{ + // Fill all existing NVML function pointers with nullptr. + memset(&m_api, 0, sizeof(NVMLFunctionTable)); +} + +//NVMLImpl::~NVMLImpl() +//{ +//} + + +// Helper function to get the entry point address in a loaded library just to abstract the platform in GET_FUNC macro. +static void* getFunc(void* handle, const char* name) +{ +#ifdef _WIN32 + return GetProcAddress((HMODULE) handle, name); +#else + return dlsym(handle, name); +#endif +} + +bool NVMLImpl::initFunctionTable() +{ +#ifdef _WIN32 + void* handle = nvmlLoadWindowsDll(); + if (!handle) + { + std::cerr << "nvml.dll not found\n"; + return false; + } +#else + void* handle = dlopen("libnvidia-ml.so.1", RTLD_NOW); + if (!handle) + { + std::cerr << "libnvidia-ml.so.1 not found\n"; + return false; + } +#endif + +// Local macro to get the NVML entry point addresses and assign them to the NVMLFunctionTable members with the right type. +// Some of the NVML functions are versioned by a #define adding a version suffix (_v2, _v3) to the name, +// which requires a set of two macros to resolve the unversioned function name to the versioned one. + +#define GET_FUNC_V(name) \ +{ \ + const void* func = getFunc(handle, #name); \ + if (func) { \ + m_api.name = reinterpret_cast(func); \ + } else { \ + std::cerr << "ERROR: " << #name << " is nullptr\n"; \ + success = false; \ + } \ +} + +#define GET_FUNC(name) GET_FUNC_V(name) + + + bool success = true; + + GET_FUNC(nvmlInit); + //GET_FUNC(nvmlInitWithFlags); + GET_FUNC(nvmlShutdown); + //GET_FUNC(nvmlErrorString); + //GET_FUNC(nvmlSystemGetDriverVersion); + //GET_FUNC(nvmlSystemGetNVMLVersion); + //GET_FUNC(nvmlSystemGetCudaDriverVersion); + //GET_FUNC(nvmlSystemGetCudaDriverVersion_v2); + //GET_FUNC(nvmlSystemGetProcessName); + //GET_FUNC(nvmlUnitGetCount); + //GET_FUNC(nvmlUnitGetHandleByIndex); + //GET_FUNC(nvmlUnitGetUnitInfo); + //GET_FUNC(nvmlUnitGetLedState); + //GET_FUNC(nvmlUnitGetPsuInfo); + //GET_FUNC(nvmlUnitGetTemperature); + //GET_FUNC(nvmlUnitGetFanSpeedInfo); + //GET_FUNC(nvmlUnitGetDevices); + //GET_FUNC(nvmlSystemGetHicVersion); + //GET_FUNC(nvmlDeviceGetCount); + //GET_FUNC(nvmlDeviceGetAttributes); + //GET_FUNC(nvmlDeviceGetHandleByIndex); + //GET_FUNC(nvmlDeviceGetHandleBySerial); + //GET_FUNC(nvmlDeviceGetHandleByUUID); + GET_FUNC(nvmlDeviceGetHandleByPciBusId); + //GET_FUNC(nvmlDeviceGetName); + //GET_FUNC(nvmlDeviceGetBrand); + //GET_FUNC(nvmlDeviceGetIndex); + //GET_FUNC(nvmlDeviceGetSerial); + //GET_FUNC(nvmlDeviceGetMemoryAffinity); + //GET_FUNC(nvmlDeviceGetCpuAffinityWithinScope); + //GET_FUNC(nvmlDeviceGetCpuAffinity); + //GET_FUNC(nvmlDeviceSetCpuAffinity); + //GET_FUNC(nvmlDeviceClearCpuAffinity); + //GET_FUNC(nvmlDeviceGetTopologyCommonAncestor); + //GET_FUNC(nvmlDeviceGetTopologyNearestGpus); + //GET_FUNC(nvmlSystemGetTopologyGpuSet); + //GET_FUNC(nvmlDeviceGetP2PStatus); + //GET_FUNC(nvmlDeviceGetUUID); + //GET_FUNC(nvmlVgpuInstanceGetMdevUUID); + //GET_FUNC(nvmlDeviceGetMinorNumber); + //GET_FUNC(nvmlDeviceGetBoardPartNumber); + //GET_FUNC(nvmlDeviceGetInforomVersion); + //GET_FUNC(nvmlDeviceGetInforomImageVersion); + //GET_FUNC(nvmlDeviceGetInforomConfigurationChecksum); + //GET_FUNC(nvmlDeviceValidateInforom); + //GET_FUNC(nvmlDeviceGetDisplayMode); + //GET_FUNC(nvmlDeviceGetDisplayActive); + //GET_FUNC(nvmlDeviceGetPersistenceMode); + //GET_FUNC(nvmlDeviceGetPciInfo); + //GET_FUNC(nvmlDeviceGetMaxPcieLinkGeneration); + //GET_FUNC(nvmlDeviceGetMaxPcieLinkWidth); + //GET_FUNC(nvmlDeviceGetCurrPcieLinkGeneration); + //GET_FUNC(nvmlDeviceGetCurrPcieLinkWidth); + //GET_FUNC(nvmlDeviceGetPcieThroughput); + //GET_FUNC(nvmlDeviceGetPcieReplayCounter); + //GET_FUNC(nvmlDeviceGetClockInfo); + //GET_FUNC(nvmlDeviceGetMaxClockInfo); + //GET_FUNC(nvmlDeviceGetApplicationsClock); + //GET_FUNC(nvmlDeviceGetDefaultApplicationsClock); + //GET_FUNC(nvmlDeviceResetApplicationsClocks); + //GET_FUNC(nvmlDeviceGetClock); + //GET_FUNC(nvmlDeviceGetMaxCustomerBoostClock); + //GET_FUNC(nvmlDeviceGetSupportedMemoryClocks); + //GET_FUNC(nvmlDeviceGetSupportedGraphicsClocks); + //GET_FUNC(nvmlDeviceGetAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceSetAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceSetDefaultAutoBoostedClocksEnabled); + //GET_FUNC(nvmlDeviceGetFanSpeed); + //GET_FUNC(nvmlDeviceGetFanSpeed_v2); + //GET_FUNC(nvmlDeviceGetTemperature); + //GET_FUNC(nvmlDeviceGetTemperatureThreshold); + //GET_FUNC(nvmlDeviceGetPerformanceState); + //GET_FUNC(nvmlDeviceGetCurrentClocksThrottleReasons); + //GET_FUNC(nvmlDeviceGetSupportedClocksThrottleReasons); + //GET_FUNC(nvmlDeviceGetPowerState); + //GET_FUNC(nvmlDeviceGetPowerManagementMode); + //GET_FUNC(nvmlDeviceGetPowerManagementLimit); + //GET_FUNC(nvmlDeviceGetPowerManagementLimitConstraints); + //GET_FUNC(nvmlDeviceGetPowerManagementDefaultLimit); + //GET_FUNC(nvmlDeviceGetPowerUsage); + //GET_FUNC(nvmlDeviceGetTotalEnergyConsumption); + //GET_FUNC(nvmlDeviceGetEnforcedPowerLimit); + //GET_FUNC(nvmlDeviceGetGpuOperationMode); + //GET_FUNC(nvmlDeviceGetMemoryInfo); + //GET_FUNC(nvmlDeviceGetComputeMode); + //GET_FUNC(nvmlDeviceGetCudaComputeCapability); + //GET_FUNC(nvmlDeviceGetEccMode); + //GET_FUNC(nvmlDeviceGetBoardId); + //GET_FUNC(nvmlDeviceGetMultiGpuBoard); + //GET_FUNC(nvmlDeviceGetTotalEccErrors); + //GET_FUNC(nvmlDeviceGetDetailedEccErrors); + //GET_FUNC(nvmlDeviceGetMemoryErrorCounter); + //GET_FUNC(nvmlDeviceGetUtilizationRates); + //GET_FUNC(nvmlDeviceGetEncoderUtilization); + //GET_FUNC(nvmlDeviceGetEncoderCapacity); + //GET_FUNC(nvmlDeviceGetEncoderStats); + //GET_FUNC(nvmlDeviceGetEncoderSessions); + //GET_FUNC(nvmlDeviceGetDecoderUtilization); + //GET_FUNC(nvmlDeviceGetFBCStats); + //GET_FUNC(nvmlDeviceGetFBCSessions); + //GET_FUNC(nvmlDeviceGetDriverModel); + //GET_FUNC(nvmlDeviceGetVbiosVersion); + //GET_FUNC(nvmlDeviceGetBridgeChipInfo); + //GET_FUNC(nvmlDeviceGetComputeRunningProcesses); + //GET_FUNC(nvmlDeviceGetGraphicsRunningProcesses); + //GET_FUNC(nvmlDeviceOnSameBoard); + //GET_FUNC(nvmlDeviceGetAPIRestriction); + //GET_FUNC(nvmlDeviceGetSamples); + //GET_FUNC(nvmlDeviceGetBAR1MemoryInfo); + //GET_FUNC(nvmlDeviceGetViolationStatus); + //GET_FUNC(nvmlDeviceGetAccountingMode); + //GET_FUNC(nvmlDeviceGetAccountingStats); + //GET_FUNC(nvmlDeviceGetAccountingPids); + //GET_FUNC(nvmlDeviceGetAccountingBufferSize); + //GET_FUNC(nvmlDeviceGetRetiredPages); + //GET_FUNC(nvmlDeviceGetRetiredPages_v2); + //GET_FUNC(nvmlDeviceGetRetiredPagesPendingStatus); + //GET_FUNC(nvmlDeviceGetRemappedRows); + //GET_FUNC(nvmlDeviceGetArchitecture); + //GET_FUNC(nvmlUnitSetLedState); + //GET_FUNC(nvmlDeviceSetPersistenceMode); + //GET_FUNC(nvmlDeviceSetComputeMode); + //GET_FUNC(nvmlDeviceSetEccMode); + //GET_FUNC(nvmlDeviceClearEccErrorCounts); + //GET_FUNC(nvmlDeviceSetDriverModel); + //GET_FUNC(nvmlDeviceSetGpuLockedClocks); + //GET_FUNC(nvmlDeviceResetGpuLockedClocks); + //GET_FUNC(nvmlDeviceSetApplicationsClocks); + //GET_FUNC(nvmlDeviceSetPowerManagementLimit); + //GET_FUNC(nvmlDeviceSetGpuOperationMode); + //GET_FUNC(nvmlDeviceSetAPIRestriction); + //GET_FUNC(nvmlDeviceSetAccountingMode); + //GET_FUNC(nvmlDeviceClearAccountingPids); + GET_FUNC(nvmlDeviceGetNvLinkState); + //GET_FUNC(nvmlDeviceGetNvLinkVersion); + GET_FUNC(nvmlDeviceGetNvLinkCapability); + GET_FUNC(nvmlDeviceGetNvLinkRemotePciInfo); + //GET_FUNC(nvmlDeviceGetNvLinkErrorCounter); + //GET_FUNC(nvmlDeviceResetNvLinkErrorCounters); + //GET_FUNC(nvmlDeviceSetNvLinkUtilizationControl); + //GET_FUNC(nvmlDeviceGetNvLinkUtilizationControl); + //GET_FUNC(nvmlDeviceGetNvLinkUtilizationCounter); + //GET_FUNC(nvmlDeviceFreezeNvLinkUtilizationCounter); + //GET_FUNC(nvmlDeviceResetNvLinkUtilizationCounter); + //GET_FUNC(nvmlEventSetCreate); + //GET_FUNC(nvmlDeviceRegisterEvents); + //GET_FUNC(nvmlDeviceGetSupportedEventTypes); + //GET_FUNC(nvmlEventSetWait); + //GET_FUNC(nvmlEventSetFree); + //GET_FUNC(nvmlDeviceModifyDrainState); + //GET_FUNC(nvmlDeviceQueryDrainState); + //GET_FUNC(nvmlDeviceRemoveGpu); + //GET_FUNC(nvmlDeviceDiscoverGpus); + //GET_FUNC(nvmlDeviceGetFieldValues); + //GET_FUNC(nvmlDeviceGetVirtualizationMode); + //GET_FUNC(nvmlDeviceGetHostVgpuMode); + //GET_FUNC(nvmlDeviceSetVirtualizationMode); + //GET_FUNC(nvmlDeviceGetGridLicensableFeatures); + //GET_FUNC(nvmlDeviceGetProcessUtilization); + //GET_FUNC(nvmlDeviceGetSupportedVgpus); + //GET_FUNC(nvmlDeviceGetCreatableVgpus); + //GET_FUNC(nvmlVgpuTypeGetClass); + //GET_FUNC(nvmlVgpuTypeGetName); + //GET_FUNC(nvmlVgpuTypeGetDeviceID); + //GET_FUNC(nvmlVgpuTypeGetFramebufferSize); + //GET_FUNC(nvmlVgpuTypeGetNumDisplayHeads); + //GET_FUNC(nvmlVgpuTypeGetResolution); + //GET_FUNC(nvmlVgpuTypeGetLicense); + //GET_FUNC(nvmlVgpuTypeGetFrameRateLimit); + //GET_FUNC(nvmlVgpuTypeGetMaxInstances); + //GET_FUNC(nvmlVgpuTypeGetMaxInstancesPerVm); + //GET_FUNC(nvmlDeviceGetActiveVgpus); + //GET_FUNC(nvmlVgpuInstanceGetVmID); + //GET_FUNC(nvmlVgpuInstanceGetUUID); + //GET_FUNC(nvmlVgpuInstanceGetVmDriverVersion); + //GET_FUNC(nvmlVgpuInstanceGetFbUsage); + //GET_FUNC(nvmlVgpuInstanceGetLicenseStatus); + //GET_FUNC(nvmlVgpuInstanceGetType); + //GET_FUNC(nvmlVgpuInstanceGetFrameRateLimit); + //GET_FUNC(nvmlVgpuInstanceGetEccMode); + //GET_FUNC(nvmlVgpuInstanceGetEncoderCapacity); + //GET_FUNC(nvmlVgpuInstanceSetEncoderCapacity); + //GET_FUNC(nvmlVgpuInstanceGetEncoderStats); + //GET_FUNC(nvmlVgpuInstanceGetEncoderSessions); + //GET_FUNC(nvmlVgpuInstanceGetFBCStats); + //GET_FUNC(nvmlVgpuInstanceGetFBCSessions); + //GET_FUNC(nvmlVgpuInstanceGetMetadata); + //GET_FUNC(nvmlDeviceGetVgpuMetadata); + //GET_FUNC(nvmlGetVgpuCompatibility); + //GET_FUNC(nvmlDeviceGetPgpuMetadataString); + //GET_FUNC(nvmlGetVgpuVersion); + //GET_FUNC(nvmlSetVgpuVersion); + //GET_FUNC(nvmlDeviceGetVgpuUtilization); + //GET_FUNC(nvmlDeviceGetVgpuProcessUtilization); + //GET_FUNC(nvmlVgpuInstanceGetAccountingMode); + //GET_FUNC(nvmlVgpuInstanceGetAccountingPids); + //GET_FUNC(nvmlVgpuInstanceGetAccountingStats); + //GET_FUNC(nvmlVgpuInstanceClearAccountingPids); + //GET_FUNC(nvmlGetBlacklistDeviceCount); + //GET_FUNC(nvmlGetBlacklistDeviceInfoByIndex); + //GET_FUNC(nvmlDeviceSetMigMode); + //GET_FUNC(nvmlDeviceGetMigMode); + //GET_FUNC(nvmlDeviceGetGpuInstanceProfileInfo); + //GET_FUNC(nvmlDeviceGetGpuInstancePossiblePlacements); + //GET_FUNC(nvmlDeviceGetGpuInstanceRemainingCapacity); + //GET_FUNC(nvmlDeviceCreateGpuInstance); + //GET_FUNC(nvmlGpuInstanceDestroy); + //GET_FUNC(nvmlDeviceGetGpuInstances); + //GET_FUNC(nvmlDeviceGetGpuInstanceById); + //GET_FUNC(nvmlGpuInstanceGetInfo); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceProfileInfo); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceRemainingCapacity); + //GET_FUNC(nvmlGpuInstanceCreateComputeInstance); + //GET_FUNC(nvmlComputeInstanceDestroy); + //GET_FUNC(nvmlGpuInstanceGetComputeInstances); + //GET_FUNC(nvmlGpuInstanceGetComputeInstanceById); + //GET_FUNC(nvmlComputeInstanceGetInfo); + //GET_FUNC(nvmlDeviceIsMigDeviceHandle); + //GET_FUNC(nvmlDeviceGetGpuInstanceId); + //GET_FUNC(nvmlDeviceGetComputeInstanceId); + //GET_FUNC(nvmlDeviceGetMaxMigDeviceCount); + //GET_FUNC(nvmlDeviceGetMigDeviceHandleByIndex); + //GET_FUNC(nvmlDeviceGetDeviceHandleFromMigDeviceHandle); + + return success; +} diff --git a/apps/bench_shared_offscreen/src/Options.cpp b/apps/bench_shared_offscreen/src/Options.cpp new file mode 100644 index 00000000..d0ff4716 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Options.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Options.h" + +#include + +Options::Options() +: m_width(512) +, m_height(512) +, m_mode(0) +, m_optimize(false) +{ +} + +//Options::~Options() +//{ +//} + +bool Options::parseCommandLine(int argc, char *argv[]) +{ + for (int i = 1; i < argc; ++i) + { + const std::string arg(argv[i]); + + if (arg == "?" || arg == "help" || arg == "--help") + { + printUsage(std::string(argv[0])); // Application name. + return false; + } + else if (arg == "-w" || arg == "--width") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_width = atoi(argv[++i]); + } + else if (arg == "-h" || arg == "--height") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_height = atoi(argv[++i]); + } + else if (arg == "-m" || arg == "--mode") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_mode = atoi(argv[++i]); + } + else if (arg == "-o" || arg == "--optimize") + { + m_optimize = true; + } + else if (arg == "-s" || arg == "--system") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_filenameSystem = std::string(argv[++i]); + } + else if (arg == "-d" || arg == "--desc") + { + if (i == argc - 1) + { + std::cerr << "Option '" << arg << "' requires additional argument.\n"; + printUsage(argv[0]); + return false; + } + m_filenameScene = std::string(argv[++i]); + } + else + { + std::cerr << "Unknown option '" << arg << "'\n"; + printUsage(argv[0]); + return false; + } + } + return true; +} + +int Options::getWidth() const +{ + return m_width; +} + +int Options::getHeight() const +{ + return m_height; +} + +int Options::getMode() const +{ + return m_mode; +} + +bool Options::getOptimize() const +{ + return m_optimize; +} + +std::string Options::getSystem() const +{ + return m_filenameSystem; +} + +std::string Options::getScene() const +{ + return m_filenameScene; +} + + +void Options::printUsage(const std::string& argv0) +{ + std::cerr << "\nUsage: " << argv0 << " [options]\n"; + std::cerr << + "App Options:\n" + " ? | help | --help Print this usage message and exit.\n" + " -w | --width Width of the client window (512) \n" + " -h | --height Height of the client window (512)\n" + " -m | --mode 0 = interactive, 1 == benchmark (0)\n" + " -o | --optimize Optimize the assimp scene graph (false)\n" + " -s | --system Filename for system options (empty).\n" + " -d | --desc Filename for scene description (empty).\n" + "App Keystrokes:\n" + " SPACE Toggles GUI display.\n"; +} diff --git a/apps/bench_shared_offscreen/src/Parallelogram.cpp b/apps/bench_shared_offscreen/src/Parallelogram.cpp new file mode 100644 index 00000000..86eab0ed --- /dev/null +++ b/apps/bench_shared_offscreen/src/Parallelogram.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + //#include "inc/Application.h" + // + //#include + //#include + //#include + // + //#include "shaders/shader_common.h" + +#include "inc/SceneGraph.h" + +#include "shaders/vector_math.h" + + +namespace sg +{ + + // Parallelogram from footpoint position, spanned by unnormalized vectors vecU and vecV, normal is normalized and on the CCW frontface. + void Triangles::createParallelogram(const float3& position, const float3& vecU, const float3& vecV, const float3& normal) + { + m_attributes.clear(); + m_indices.clear(); + + TriangleAttributes attrib; + + // Same for all four vertices in this parallelogram. + attrib.tangent = normalize(vecU); + attrib.normal = normal; + + attrib.vertex = position; // left bottom + attrib.texcoord = make_float3(0.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = position + vecU; // right bottom + attrib.texcoord = make_float3(1.0f, 0.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = position + vecU + vecV; // right top + attrib.texcoord = make_float3(1.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + attrib.vertex = position + vecV; // left top + attrib.texcoord = make_float3(0.0f, 1.0f, 0.0f); + m_attributes.push_back(attrib); + + m_indices.push_back(0); + m_indices.push_back(1); + m_indices.push_back(2); + + m_indices.push_back(2); + m_indices.push_back(3); + m_indices.push_back(0); + } + +} // namespace sg diff --git a/apps/bench_shared_offscreen/src/Parser.cpp b/apps/bench_shared_offscreen/src/Parser.cpp new file mode 100644 index 00000000..750ac09e --- /dev/null +++ b/apps/bench_shared_offscreen/src/Parser.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +#include "inc/Parser.h" + +#include +#include +#include + + +Parser::Parser() +: m_index(0) +, m_line(1) +{ +} + +//Parser::~Parser() +//{ +//} + +bool Parser::load(const std::string& filename) +{ + m_source.clear(); + + std::ifstream inputStream(filename); + if (!inputStream) + { + std::cerr << "ERROR: Parser::load() failed to open file " << filename << '\n'; + return false; + } + + std::stringstream data; + + data << inputStream.rdbuf(); + + if (inputStream.fail()) + { + std::cerr << "ERROR: loadString() Failed to read file " << filename << '\n'; + return false; + } + + m_source = data.str(); + return true; +} + +ParserTokenType Parser::getNextToken(std::string& token) +{ + const static std::string whitespace = " \t"; // space, tab + const static std::string value = "+-0123456789.eE"; + const static std::string delimiter = " \t\r\n"; // space, tab, carriage return, linefeed + const static std::string newline = "\n"; + const static std::string quotation = "\""; + + token.clear(); // Make sure the returned token starts empty. + + ParserTokenType type = PTT_UNKNOWN; // This return value indicates an error. + + std::string::size_type first; + std::string::size_type last; + + bool done = false; + while (!done) + { + // Find first character which is not a whitespace. + first = m_source.find_first_not_of(whitespace, m_index); + if (first == std::string::npos) + { + token = std::string(); + type = PTT_EOF; + done = true; + continue; + } + + // The found character indicates how parsing continues. + char c = m_source[first]; + + if (c == '#') // comment until the next newline + { + // m_index = first + 1; // skip '#' // Redundant. + first = m_source.find_first_of(newline, m_index); // Skip everything until the next newline. + if (first == std::string::npos) + { + type = PTT_EOF; + done = true; + } + m_index = first + 1; // skip newline + m_line++; + } + else if (c == '\r') // carriage return 13 + { + m_index = first + 1; + } + else if (c == '\n') // newline (linefeed 10) + { + m_index = first + 1; + m_line++; + } + else if (c == '\"') // Quotation mark delimits strings (filenames or material names with spaces.) + { + ++first; // Skip beginning quotation mark. + last = m_source.find_first_of(quotation, first); // Find the ending quotation mark. Should be in the same line! + if (last == std::string::npos) // Error, no matching end quotation mark found. + { + m_index = first; // Keep scanning behind the quotation mark. + } + else + { + m_index = last + 1; // Skip the ending quotation mark. + token = m_source.substr(first, last - first); + type = PTT_STRING; + done = true; + } + } + else // anything else + { + last = m_source.find_first_of(delimiter, first); + if (last == std::string::npos) + { + last = m_source.size(); + } + m_index = last; + token = m_source.substr(first, last - first); + type = PTT_ID; // Default to general identifier. + // Check if token is only built of characters used for numbers. + // (Not perfectly parsing a floating point number but good enough for most filenames.) + if (isdigit(c) || c == '-' || c == '+' || c == '.') // Legal start characters for a floating point number. + { + last = token.find_first_not_of(value, 0); + if (last == std::string::npos) + { + type = PTT_VAL; + } + } + done = true; + } + } + + return type; +} + +std::string::size_type Parser::getSize() const +{ + return m_source.size(); +} + +std::string::size_type Parser::getIndex() const +{ + return m_index; +} + +unsigned int Parser::getLine() const +{ + return m_line; +} + + + diff --git a/apps/bench_shared_offscreen/src/Picture.cpp b/apps/bench_shared_offscreen/src/Picture.cpp new file mode 100644 index 00000000..15a03889 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Picture.cpp @@ -0,0 +1,744 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +// Code in these classes is based on the ILTexLoader.h/.cpp routines inside the NVIDIA nvpro-pipeline ILTexLoader plugin: +// https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp + + +#include "inc/Picture.h" + +#include +#include +#include +#include + +#include "inc/MyAssert.h" + + +static unsigned int numberOfComponents(int format) +{ + switch (format) + { + case IL_RGB: + case IL_BGR: + return 3; + + case IL_RGBA: + case IL_BGRA: + return 4; + + case IL_LUMINANCE: + case IL_ALPHA: + return 1; + + case IL_LUMINANCE_ALPHA: + return 2; + + default: + MY_ASSERT(!"Unsupported image data format."); + return 0; + } +} + +static unsigned int sizeOfComponents(int type) +{ + switch (type) + { + case IL_BYTE: + case IL_UNSIGNED_BYTE: + return 1; + + case IL_SHORT: + case IL_UNSIGNED_SHORT: + return 2; + + case IL_INT: + case IL_UNSIGNED_INT: + case IL_FLOAT: + return 4; + + default: + MY_ASSERT(!"Unsupported image data type."); + return 0; + } +} + +Image::Image(unsigned int width, + unsigned int height, + unsigned int depth, + int format, + int type) +: m_width(width) +, m_height(height) +, m_depth(depth) +, m_format(format) +, m_type(type) +, m_pixels(nullptr) +{ + m_bpp = numberOfComponents(m_format) * sizeOfComponents(m_type); + m_bpl = m_width * m_bpp; + m_bps = m_height * m_bpl; + m_nob = m_depth * m_bps; +} + +Image::~Image() +{ + if (m_pixels != nullptr) + { + delete[] m_pixels; + m_pixels = nullptr; + } +} + +static int determineFace(int i, bool isCubemapDDS) +{ + int face = i; + + // If this is a cubemap in a DDS file, exchange the z-negative and z-positive images to match OpenGL and what's used here for OptiX. + if (isCubemapDDS) + { + if (i == 4) + { + face = 5; + } + else if (i == 5) + { + face = 4; + } + } + + return face; +} + + +Picture::Picture() +: m_flags(0) +, m_isCube(false) +{ +} + +Picture::~Picture() +{ + clearImages(); +} + +unsigned int Picture::getFlags() const +{ + return m_flags; +} + +unsigned int Picture::getNumberOfImages() const +{ + return static_cast(m_images.size()); +} + +unsigned int Picture::getNumberOfLevels(unsigned int index) const +{ + MY_ASSERT(index < m_images.size()); + return static_cast(m_images[index].size()); +} + +const Image* Picture::getImageLevel(unsigned int index, unsigned int level) const +{ + if (index < m_images.size() && level < m_images[index].size()) + { + return m_images[index][level]; + } + return nullptr; +} + +bool Picture::isCubemap() const +{ + return m_isCube; +} + +void Picture::setIsCubemap(const bool isCube) +{ + m_isCube = isCube; +} + +// Returns true if the input extents were already the smallest possible mipmap level. +static bool calculateNextExtents(unsigned int &w, unsigned int &h, unsigned int& d, const unsigned int flags) +{ + bool done = false; + + // Calculate the expected LOD image extents. + if (flags & IMAGE_FLAG_LAYER) + { + if (flags & IMAGE_FLAG_1D) + { + // 1D layered mipmapped. + done = (w == 1); + w = (1 < w) ? w >> 1 : 1; + // height is 1 + // depth is the number of layers and must not change. + } + else if (flags & (IMAGE_FLAG_2D | IMAGE_FLAG_CUBE)) + { + // 2D or cubemap layered mipmapped + done = (w == 1 && h == 1); + w = (1 < w) ? w >> 1 : 1; + h = (1 < h) ? h >> 1 : 1; + // depth is the number of layers (* 6 for cubemaps) and must not change. + } + } + else + { + // Standard mipmap chain. + done = (w == 1 && h == 1 && d == 1); + w = (1 < w) ? w >> 1 : 1; + h = (1 < h) ? h >> 1 : 1; + d = (1 < d) ? d >> 1 : 1; + } + return done; +} + + +void Picture::clearImages() +{ + for (size_t i = 0; i < m_images.size(); ++i) + { + for (size_t lod = 0; lod < m_images[i].size(); ++lod) + { + delete m_images[i][lod]; + m_images[i][lod] = nullptr; + } + m_images[i].clear(); + } + m_images.clear(); +} + + +bool Picture::load(const std::string& filename, const unsigned int flags) +{ + bool success = false; + + clearImages(); // Each load() wipes previously loaded image data. + + std::string foundFile = filename; // FIXME Search at least the current working directory. + if (foundFile.empty()) + { + std::cerr << "ERROR: Picture::load() " << filename << " not found\n"; + MY_ASSERT(!"Picture::load() File not found"); + return success; + } + + m_flags = flags; // Track the flags with which this picture was loaded. + + std::string ext; + std::string::size_type last = filename.find_last_of('.'); + if (last != std::string::npos) + { + ext = filename.substr(last, std::string::npos); + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + } + + bool isDDS = (ext == std::string(".dds")); // .dds images need special handling + m_isCube = false; + + unsigned int imageID; + + ilGenImages(1, (ILuint *) &imageID); + ilBindImage(imageID); + + // Let DevIL handle the proper orientation during loading. + if (isDDS) + { + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_UPPER_LEFT); // DEBUG What happens when I set IL_ORIGIN_LOWER_LEFT all the time? + } + else + { + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_LOWER_LEFT); + } + + // Load the image from file. This loads all data. + if (ilLoadImage((const ILstring) foundFile.c_str())) + { + std::vector mipmaps; // All mipmaps excluding the LOD 0. + + ilBindImage(imageID); + ilActiveImage(0); // Get the frst image, potential LOD 0. + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // Get the size of the LOD 0 image. + unsigned int w = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int h = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int d = ilGetInteger(IL_IMAGE_DEPTH); + + // Querying for IL_NUM_IMAGES returns the number of images following the current one. Add 1 for the correct image count! + int numImages = ilGetInteger(IL_NUM_IMAGES) + 1; + + int numMipmaps = 0; // Default to no mipmap handling. + + if (flags & IMAGE_FLAG_MIPMAP) // Only handle mipmaps if we actually want to load them. + { + numMipmaps = ilGetInteger(IL_NUM_MIPMAPS); // Excluding the current image which becomes LOD 0. + + // Special check to see if the number of top-level images build a 1D, 2D, or 3D mipmap chain, if there are no mipmaps in the LOD 0 image. + if (1 < numImages && !numMipmaps) + { + bool isMipmapChain = true; // Indicates if the images in this file build a standard mimpmap chain. + + for (int i = 1; i < numImages; ++i) // Start check at LOD 1. + { + ilBindImage(imageID); + ilActiveImage(i); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // Next image extents. + const unsigned int ww = ilGetInteger(IL_IMAGE_WIDTH); + const unsigned int hh = ilGetInteger(IL_IMAGE_HEIGHT); + const unsigned int dd = ilGetInteger(IL_IMAGE_DEPTH); + + calculateNextExtents(w, h, d, flags); // Calculates the extents of the next mipmap level, taking layered textures into account! + + if (ww == w && hh == h && dd == d) // Criteria for next mipmap level match. + { + // Top-level image actually is the i-th mipmap level. Remember the data. + // This doesn't get overwritten below, because the standard mipmap handling is based on numMipmaps != 0. + mipmaps.push_back(ilGetData()); + } + else + { + // Could not identify top-level image as a mipmap level, no further testing required. + // Test failed, means the number of images do not build a mipmap chain. + isMipmapChain = false; + mipmaps.clear(); + break; + } + } + + if (isMipmapChain) + { + // Consider only the very first image in the file in the following code. + numImages = 1; + } + } + } + + m_isCube = (ilGetInteger(IL_IMAGE_CUBEFLAGS) != 0); + + // If the file isn't identified as cubemap already, + // check if there are six square images of the same extents in the file and handle them as cubemap. + if (!m_isCube && numImages == 6) + { + bool isCube = true; + + unsigned int w0 = 0; + unsigned int h0 = 0; + unsigned int d0 = 0; + + for (int image = 0; image < numImages && isCube; ++image) + { + ilBindImage(imageID); + ilActiveImage(image); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + if (image == 0) + { + w0 = ilGetInteger(IL_IMAGE_WIDTH); + h0 = ilGetInteger(IL_IMAGE_HEIGHT); + d0 = ilGetInteger(IL_IMAGE_DEPTH); + + MY_ASSERT(0 < d0); // This case of no image data is handled later. + + if (w0 != h0) + { + isCube = false; // Not square. + } + } + else + { + unsigned int w1 = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int h1 = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int d1 = ilGetInteger(IL_IMAGE_DEPTH); + + // All LOD 0 faces must be the same size. + if (w0 != w1 || h0 != h1) + { + isCube = false; + } + // If this should be interpreted as layered cubemap, all images must have the same number of layers. + if ((flags & IMAGE_FLAG_LAYER) && d0 != d1) + { + isCube = false; + } + } + } + m_isCube = isCube; + } + + for (int image = 0; image < numImages; ++image) + { + // Cubemap faces within DevIL philosophy are organized like this: + // image -> 1st face -> face index 0 + // face1 -> 2nd face -> face index 1 + // ... + // face5 -> 6th face -> face index 5 + + const int numFaces = ilGetInteger(IL_NUM_FACES) + 1; + + for (int f = 0; f < numFaces; ++f) + { + // Need to juggle with the faces to get them aligned with how OpenGL expects cube faces. Using the same layout in OptiX. + const int face = determineFace(f, m_isCube && isDDS); + + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + // pixel format + int format = ilGetInteger(IL_IMAGE_FORMAT); + + if (IL_COLOR_INDEX == format) + { + // Convert color index to whatever the base type of the palette is. + if (!ilConvertImage(ilGetInteger(IL_PALETTE_BASE_TYPE), IL_UNSIGNED_BYTE)) + { + // Free all resources associated with the DevIL image. + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + return false; + } + // Now query format of the converted image. + format = ilGetInteger(IL_IMAGE_FORMAT); + } + + const int type = ilGetInteger(IL_IMAGE_TYPE); + + // Image dimension of the LOD 0 in pixels. + unsigned int width = ilGetInteger(IL_IMAGE_WIDTH); + unsigned int height = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned int depth = ilGetInteger(IL_IMAGE_DEPTH); + + if (width == 0 || height == 0 || depth == 0) // There must be at least a single pixel. + { + std::cerr << "ERROR Picture::load() " << filename << ": image " << image << " face " << f << " extents (" << width << ", " << height << ", " << depth << ")\n"; + MY_ASSERT(!"Picture::load() Image with zero extents."); + + // Free all resources associated with the DevIL image. + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + return false; + } + + // Get the remaining mipmaps for this image. + // Note that the special case handled above where multiple images built a mipmap chain + // will not enter this because that was only checked when numMipmaps == 0. + if (0 < numMipmaps) + { + mipmaps.clear(); // Clear this for currently processed image. + + for (int j = 1; j <= numMipmaps; ++j) // Mipmaps are starting at LOD 1. + { + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + ilActiveMipmap(j); + + // Not checking consistency of the individual LODs here. + mipmaps.push_back(ilGetData()); + } + + // Look at LOD 0 of this image again for the next ilGetData(). + // DevIL frequently loses track of the current state. + ilBindImage(imageID); + ilActiveImage(image); + ilActiveFace(face); + ilActiveMipmap(0); + } + + // Add a a new vector of images with the whole mipmap chain. + // Mind that there will be six of these for a cubemap image! + unsigned int index = addImages(ilGetData(), width, height, depth, format, type, mipmaps, flags); + + if (m_isCube && isDDS) + { + // WARNING: + // This piece of code MUST NOT be visited twice for the same image, + // because this would falsify the desired effect! + // The images at this position are flipped at the x-axis (due to DevIL) + // flipping at x-axis will result in original image + // mirroring at y-axis will result in rotating the image 180 degree + if (face == 0 || face == 1 || face == 4 || face == 5) // px, nx, pz, nz + { + mirrorY(index); // mirror over y-axis + } + else // py, ny + { + mirrorX(index); // flip over x-axis + } + } + + ILint origin = ilGetInteger(IL_IMAGE_ORIGIN); + if (!m_isCube && origin == IL_ORIGIN_UPPER_LEFT) + { + // OpenGL expects origin at lower left, so the image has to be flipped at the x-axis + // for DDS cubemaps we handle the separate face rotations above + // DEBUG This should only happen for DDS images. + // All others are flipped by DevIL because I set the origin to lower left. Handle DDS images the same? + mirrorX(index); // reverse rows + } + } + } + success = true; + } + + if (!success) + { + std::cerr << "ERROR Picture::load(): " << filename << " not loaded\n"; + } + + // Free all resources associated with the DevIL image + ilDeleteImages(1, &imageID); + MY_ASSERT(IL_NO_ERROR == ilGetError()); + + return success; +} + +void Picture::clear() +{ + m_images.clear(); +} + +// Append a new empty vector of images. Returns the new image index. +unsigned int Picture::addImages() +{ + const unsigned int index = static_cast(m_images.size()); + + m_images.push_back(std::vector()); // Append a new empty vector of image pointers. Each vector holds a mipmap chain. + + return index; +} + +// Add a vector of images and fill it with the LOD 0 pixels and the optional mipmap chain. +unsigned int Picture::addImages(const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type, + const std::vector& mipmaps, const unsigned int flags) +{ + const unsigned int index = static_cast(m_images.size()); + + m_images.push_back(std::vector()); // Append a new empty vector of image pointers. + + Image* image = new Image(width, height, depth, format, type); // LOD 0 + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, pixels, image->m_nob); + + m_images[index].push_back(image); // LOD 0 + + unsigned int w = width; + unsigned int h = height; + unsigned int d = depth; + + for (size_t i = 0; i < mipmaps.size(); ++i) + { + MY_ASSERT(mipmaps[i]); // No nullptr expected. + + calculateNextExtents(w, h, d, flags); // Mind that the flags let this work for layered mipmap chains! + + image = new Image(w, h, d, format, type); // LOD 1 to N. + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, mipmaps[i], image->m_nob); + + m_images[index].push_back(image); // LOD 1 - N + } + + return index; +} + +// Append a new image LOD to the images in index. +unsigned int Picture::addLevel(const unsigned int index, const void* pixels, + const unsigned int width, const unsigned int height, const unsigned int depth, + const int format, const int type) +{ + MY_ASSERT(index < m_images.size()); + MY_ASSERT(pixels != nullptr); + MY_ASSERT((0 < width) && (0 < height) && (0 < depth)); + + Image* image = new Image(width, height, depth, format, type); + + image->m_pixels = new unsigned char[image->m_nob]; + memcpy(image->m_pixels, pixels, image->m_nob); + + const unsigned int level = static_cast(m_images[index].size()); + + m_images[index].push_back(image); + + return level; +} + + +void Picture::mirrorX(unsigned int index) +{ + MY_ASSERT(index < m_images.size()); + + // Flip all images upside down. + for (size_t i = 0; i < m_images[index].size(); ++i) + { + Image* image = m_images[index][i]; + + const unsigned char* srcPixels = image->m_pixels; + unsigned char* dstPixels = new unsigned char[image->m_nob]; + + for (unsigned int z = 0; z < image->m_depth; ++z) + { + for (unsigned int y = 0; y < image->m_height; ++y) + { + const unsigned char* srcLine = srcPixels + z * image->m_bps + y * image->m_bpl; + unsigned char* dstLine = dstPixels + z * image->m_bps + (image->m_height - 1 - y) * image->m_bpl; + + memcpy(dstLine, srcLine, image->m_bpl); + } + } + delete[] image->m_pixels; + image->m_pixels = dstPixels; + } +} + +void Picture::mirrorY(unsigned int index) +{ + MY_ASSERT(index < m_images.size()); + + // Mirror all images left to right. + for (size_t i = 0; i < m_images[index].size(); ++i) + { + Image* image = m_images[index][i]; + + const unsigned char* srcPixels = image->m_pixels; + unsigned char* dstPixels = new unsigned char[image->m_nob]; + + for (unsigned int z = 0; z < image->m_depth; ++z) + { + for (unsigned int y = 0; y < image->m_height; ++y) + { + const unsigned char* srcLine = srcPixels + z * image->m_bps + y * image->m_bpl; + unsigned char* dstLine = dstPixels + z * image->m_bps + y * image->m_bpl; + + for (unsigned int x = 0; x < image->m_width; ++x) + { + const unsigned char* srcPixel = srcLine + x * image->m_bpp; + unsigned char* dstPixel = dstLine + (image->m_width - 1 - x) * image->m_bpp; + + memcpy(dstPixel, srcPixel, image->m_bpp); + } + } + } + + delete[] image->m_pixels; + image->m_pixels = dstPixels; + } +} + + +void Picture::generateRGBA8(unsigned int width, unsigned int height, unsigned int depth, const unsigned int flags) +{ + clearImages(); + + const unsigned char colors[14][4] = + { + { 0xFF, 0x00, 0x00, 0xFF }, // red + { 0x00, 0xFF, 0x00, 0xFF }, // green + { 0x00, 0x00, 0xFF, 0xFF }, // blue + { 0xFF, 0xFF, 0x00, 0xFF }, // yellow + { 0x00, 0xFF, 0xFF, 0xFF }, // cyan + { 0xFF, 0x00, 0xFF, 0xFF }, // magenta + { 0xFF, 0xFF, 0xFF, 0xFF }, // white + + { 0x7F, 0x00, 0x00, 0xFF }, // dark red + { 0x00, 0x7F, 0x00, 0xFF }, // dark green + { 0x00, 0x00, 0x7F, 0xFF }, // dark blue + { 0x7F, 0x7F, 0x00, 0xFF }, // dark yellow + { 0x00, 0x7F, 0x7F, 0xFF }, // dark cyan + { 0x7F, 0x00, 0x7F, 0xFF }, // dark magenta + { 0x7F, 0x7F, 0x7F, 0xFF } // grey + }; + + m_isCube = (flags & IMAGE_FLAG_CUBE) != 0; + + unsigned char* rgba = new unsigned char[width * height * depth * 4]; // Enough to hold the LOD 0. + + const unsigned int numFaces = (flags & IMAGE_FLAG_CUBE) ? 6 : 1; // Cubemaps put each face in a new images vector. + + for (unsigned int face = 0; face < numFaces; ++face) + { + const unsigned int index = addImages(); // New mipmap chain. + MY_ASSERT(index == face); + + // calculateNextExtents() changes the w, h, d values. Restore them for each face. + unsigned int w = width; + unsigned int h = height; + unsigned int d = depth; + + bool done = false; // Indicates if one mipmap chain is done. + + unsigned int idx = face; // Color index. Each face gets a different color. + + while (!done) + { + unsigned char* p = rgba; + + for (unsigned int z = 0; z < d; ++z) + { + for (unsigned int y = 0; y < h; ++y) + { + for (unsigned int x = 0; x < w; ++x) + { + p[0] = colors[idx][0]; + p[1] = colors[idx][1]; + p[2] = colors[idx][2]; + p[3] = colors[idx][3]; + p += 4; + } + } + } + + idx = (idx + 1) % 14; // Next color index. Each mipmap level gets a different color. + + const unsigned int level = addLevel(index, rgba, w, h, d, IL_RGBA, IL_UNSIGNED_BYTE); + + if ((flags & IMAGE_FLAG_MIPMAP) == 0) + { + done = true; + } + else + { + done = calculateNextExtents(w, h, d, flags); + } + } + } + + delete[] rgba; +} diff --git a/apps/bench_shared_offscreen/src/Plane.cpp b/apps/bench_shared_offscreen/src/Plane.cpp new file mode 100644 index 00000000..3cc435b7 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Plane.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + void Triangles::createPlane(const unsigned int tessU, const unsigned int tessV, const unsigned int upAxis) + { + MY_ASSERT(1 <= tessU && 1 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + const float uTile = 2.0f / float(tessU); + const float vTile = 2.0f / float(tessV); + + float3 corner; + + TriangleAttributes attrib; + + switch (upAxis) + { + case 0: // Positive x-axis is the geometry normal, create geometry on the yz-plane. + corner = make_float3(0.0f, -1.0f, 1.0f); // Lower front corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(0.0f, 0.0f, -1.0f); + attrib.normal = make_float3(1.0f, 0.0f, 0.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(0.0f, v, -u); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + + case 1: // Positive y-axis is the geometry normal, create geometry on the xz-plane. + corner = make_float3(-1.0f, 0.0f, 1.0f); // left front corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 1.0f, 0.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(u, 0.0f, -v); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + + case 2: // Positive z-axis is the geometry normal, create geometry on the xy-plane. + corner = make_float3(-1.0f, -1.0f, 0.0f); // Lower left corner of the plane. texcoord (0.0f, 0.0f). + + attrib.tangent = make_float3(1.0f, 0.0f, 0.0f); + attrib.normal = make_float3(0.0f, 0.0f, 1.0f); + + for (unsigned int j = 0; j <= tessV; ++j) + { + const float v = float(j) * vTile; + + for (unsigned int i = 0; i <= tessU; ++i) + { + const float u = float(i) * uTile; + + attrib.vertex = corner + make_float3(u, v, 0.0f); + attrib.texcoord = make_float3(u * 0.5f, v * 0.5f, 0.0f); + + m_attributes.push_back(attrib); + } + } + break; + } + + const unsigned int stride = tessU + 1; + for (unsigned int j = 0; j < tessV; ++j) + { + for (unsigned int i = 0; i < tessU; ++i) + { + m_indices.push_back( j * stride + i); + m_indices.push_back( j * stride + i + 1); + m_indices.push_back((j + 1) * stride + i + 1); + + m_indices.push_back((j + 1) * stride + i + 1); + m_indices.push_back((j + 1) * stride + i); + m_indices.push_back( j * stride + i); + } + } + } + +} // namespace sg \ No newline at end of file diff --git a/apps/bench_shared_offscreen/src/Rasterizer.cpp b/apps/bench_shared_offscreen/src/Rasterizer.cpp new file mode 100644 index 00000000..6cab212c --- /dev/null +++ b/apps/bench_shared_offscreen/src/Rasterizer.cpp @@ -0,0 +1,824 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Rasterizer.h" +#include "inc/MyAssert.h" + +#include +#include +#include + + +#if USE_TIME_VIEW +static bool generateColorRamp(const std::vector& definition, int size, float *ramp) +{ + if (definition.size() < 1 || !size || !ramp) + { + return false; + } + + float r; + float g; + float b; + float a = 1.0f; // CUDA doesn't support float3 textures. + + float *p = ramp; + + if (definition.size() == 1) + { + // Special case, only one color in the input, means the whole color ramp is that color. + r = definition[0].c[0]; + g = definition[0].c[1]; + b = definition[0].c[2]; + + for (int i = 0; i < size; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } + return true; + } + + // Here definition.size() is at least 2. + ColorRampElement left; + ColorRampElement right; + size_t entry = 0; + + left = definition[entry]; + if (0.0f < left.u) + { + left.u = 0.0f; + } + else // left.u == 0.0f; + { + entry++; + } + right = definition[entry++]; + + for (int i = 0; i < size; ++i) + { + // The 1D coordinate at which we need to calculate the color. + float u = (float) i / (float) (size - 1); + + // Check if it's in the range [left.u, right.u) + while (!(left.u <= u && u < right.u)) + { + left = right; + if (entry < definition.size()) + { + right = definition[entry++]; + } + else + { + // left is already the last entry, move right.u to the end of the range. + right.u = 1.0001f; // Make sure we pass 1.0 < right.u in the last iteration. + break; + } + } + + float t = (u - left.u) / (right.u - left.u); + r = left.c[0] + t * (right.c[0] - left.c[0]); + g = left.c[1] + t * (right.c[1] - left.c[1]); + b = left.c[2] + t * (right.c[2] - left.c[2]); + + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } + return true; +} +#endif + + +Rasterizer::Rasterizer(const int w, const int h, const int interop) +: m_width(w) +, m_height(h) +, m_interop(interop) +, m_widthResolution(w) +, m_heightResolution(h) +, m_numDevices(0) +, m_nodeMask(0) +, m_hdrTexture(0) +, m_pbo(0) +, m_glslProgram(0) +, m_vboAttributes(0) +, m_vboIndices(0) +, m_locAttrPosition(-1) +, m_locAttrTexCoord(-1) +, m_locProjection(-1) +, m_locSamplerHDR(-1) +, m_colorRampTexture(0) +, m_locSamplerColorRamp(-1) +, m_locInvGamma(-1) +, m_locColorBalance(-1) +, m_locInvWhitePoint(-1) +, m_locBurnHighlights(-1) +, m_locCrushBlacks(-1) +, m_locSaturation(-1) +{ + for (int i = 0; i < 24; ++i) // Hardcoded size of maximum 24 devices expected. + { + memset(m_deviceUUID[0], 0, sizeof(m_deviceUUID[0])); + } + //memset(m_driverUUID, 0, sizeof(m_driverUUID)); // Unused. + memset(m_deviceLUID, 0, sizeof(m_deviceLUID)); + + // Find out which device is running the OpenGL implementation to be able to allocate the PBO peer-to-peer staging buffer on the same device. + // Needs these OpenGL extensions: + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects.txt + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects_win32.txt + // and on CUDA side the CUDA 10.0 Driver API function cuDeviceGetLuid(). + // While the extensions are named EXT_external_objects, the enums and functions are found under name string EXT_memory_object! + if (GLEW_EXT_memory_object) + { + // UUID + // To determine which devices are used by the current context, first call GetIntegerv with set to NUM_DEVICE_UUIDS_EXT, + // then call GetUnsignedBytei_vEXT with set to DEVICE_UUID_EXT, set to a value in the range [0, ), + // and set to point to an array of UUID_SIZE_EXT unsigned bytes. + glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &m_numDevices); // This is normally 1, but not when multicast is enabled! + MY_ASSERT(m_numDevices <= 24); // m_deviceUUID is "only" prepared for 24 devices. + m_numDevices = std::min(m_numDevices, 24); + + for (GLint i = 0; i < m_numDevices; ++i) + { + glGetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, i, m_deviceUUID[i]); + } + //glGetUnsignedBytevEXT(GL_DRIVER_UUID_EXT, m_driverUUID); // Not used here. + + // LUID + // "The devices in use by the current context may also be identified by an (LUID, node) pair. + // To determine the LUID of the current context, call GetUnsignedBytev with set to DEVICE_LUID_EXT and set to point to an array of LUID_SIZE_EXT unsigned bytes. + // Following the call, can be cast to a pointer to an LUID object that will be equal to the locally unique identifier + // of an IDXGIAdapter1 object corresponding to the adapter used by the current context. + // To identify which individual devices within an adapter are used by the current context, call GetIntegerv with set to DEVICE_NODE_MASK_EXT. + // A bitfield is returned with one bit set for each device node used by the current context. + // The bits set will be subset of those available on a Direct3D 12 device created on an adapter with the same LUID as the current context." + if (GLEW_EXT_memory_object_win32) + { + // It is not expected that a single context will be associated with multiple DXGI adapters, so only one LUID is returned. + glGetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, m_deviceLUID); + glGetIntegerv(GL_DEVICE_NODE_MASK_EXT, &m_nodeMask); + } + } + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + glViewport(0, 0, m_width, m_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // default, works for BGRA8, RGBA16F, and RGBA32F. + + glDisable(GL_CULL_FACE); // default + glDisable(GL_DEPTH_TEST); // default + + glGenTextures(1, &m_hdrTexture); + MY_ASSERT(m_hdrTexture != 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + // For batch rendering initialize the texture contents to some default. + const float texel[4] = { 1.0f, 0.0f, 1.0f, 1.0f }; // Magenta to indicate that the texture has not been initialized. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, &texel); // RGBA32F + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (GLEW_NV_gpu_multicast) + { + const char* envMulticast = getenv("GL_NV_GPU_MULTICAST"); + if (envMulticast != nullptr && envMulticast[0] != '0') + { + std::cerr << "WARNING: Rasterizer() GL_NV_GPU_MULTICAST is enabled. Primary device needs to be inside the maskDevices to display correctly.\n"; + glTexParameteri(GL_TEXTURE_2D, GL_PER_GPU_STORAGE_NV, GL_TRUE); + } + } + + glBindTexture(GL_TEXTURE_2D, 0); + + // DAR The local ImGui sources have been changed to push the GL_TEXTURE_BIT so that this works. + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + if (m_interop == INTEROP_MODE_PBO) + { + // PBO for CUDA-OpenGL interop. + glGenBuffers(1, &m_pbo); + MY_ASSERT(m_pbo != 0); + + // Make sure the buffer is not zero size. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(float) * 4, (GLvoid*) 0, GL_DYNAMIC_DRAW); // RGBA32F from byte offset 0 in the pixel unpack buffer. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + // GLSL shaders objects and program. Must be initialized before using any shader variable locations. + initGLSL(); + + // This initialization is just to generate the vertex buffer objects and bind the VertexAttribPointers. + // Two hardcoded triangles in the viewport size projection coordinate system with 2D texture coordinates. + // These get updated to the correct values in reshape() and in setResolution(). The resolution is not known at this point. + const float attributes[16] = + { + // vertex2f, + 0.0f, 0.0f, + 1.0, 0.0f, + 1.0, 1.0, + 0.0f, 1.0, + //texcoord2f + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + const unsigned int indices[6] = + { + 0, 1, 2, + 2, 3, 0 + }; + + glGenBuffers(1, &m_vboAttributes); + MY_ASSERT(m_vboAttributes != 0); + + glGenBuffers(1, &m_vboIndices); + MY_ASSERT(m_vboIndices != 0); + + // Setup the vertex arrays from the vertex attributes. + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) sizeof(float) * 16, (GLvoid const*) attributes, GL_DYNAMIC_DRAW); + // This requires a bound array buffer! + glVertexAttribPointer(m_locAttrPosition, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (GLvoid*) 0); + glVertexAttribPointer(m_locAttrTexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (GLvoid*) (sizeof(float) * 8)); + glBindBuffer(GL_ARRAY_BUFFER, 0); // PERF It should be faster to keep these buffers bound. + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vboIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr) sizeof(unsigned int) * 6, (const GLvoid*) indices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // PERF It should be faster to keep these buffers bound. + + // Synchronize data with the current values. + updateProjectionMatrix(); + updateVertexAttributes(); + +#if USE_TIME_VIEW + // Generate the color ramp definition vector. + std::vector colorRampDefinition; + + ColorRampElement cre; + + // Cold to hot: blue, green, red, yellow, white + cre.u = 0.0f; + cre.c[0] = 0.0f; // blue + cre.c[1] = 0.0f; + cre.c[2] = 1.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.25f; + cre.c[0] = 0.0f; // green + cre.c[1] = 1.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.5f; + cre.c[0] = 1.0f; // red + cre.c[1] = 0.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 0.75f; + cre.c[0] = 1.0f; // yellow + cre.c[1] = 1.0f; + cre.c[2] = 0.0f; + colorRampDefinition.push_back(cre); + cre.u = 1.0f; + cre.c[0] = 1.0f; // white + cre.c[1] = 1.0f; + cre.c[2] = 1.0f; + colorRampDefinition.push_back(cre); + + std::vector texels(256 * 4); + + bool success = generateColorRamp(colorRampDefinition, 256, texels.data()); + if (success) + { + glGenTextures(1, &m_colorRampTexture); + MY_ASSERT(m_colorRampTexture != 0); + + glActiveTexture(GL_TEXTURE1); // It's set to texture image unit 1. + glBindTexture(GL_TEXTURE_1D, m_colorRampTexture); + + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, texels.data()); + + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glActiveTexture(GL_TEXTURE0); + } +#endif +} + +Rasterizer::~Rasterizer() +{ + glDeleteTextures(1, &m_hdrTexture); + +#if USE_TIME_VIEW + glDeleteTextures(1, &m_colorRampTexture); +#endif + + if (m_interop) + { + glDeleteBuffers(1, &m_pbo); + } + + glDeleteBuffers(1, &m_vboAttributes); + glDeleteBuffers(1, &m_vboIndices); + + glDeleteProgram(m_glslProgram); +} + + +void Rasterizer::reshape(const int w, const int h) +{ + // No check for zero sizes needed. That's done in Application::reshape() + if (m_width != w || m_height != h) + { + m_width = w; + m_height = h; + + glViewport(0, 0, m_width, m_height); + + updateProjectionMatrix(); + updateVertexAttributes(); + } +} + +void Rasterizer::display() +{ + glClear(GL_COLOR_BUFFER_BIT); // PERF Do not do this for benchmarks! + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vboIndices); + + glEnableVertexAttribArray(m_locAttrPosition); + glEnableVertexAttribArray(m_locAttrTexCoord); + + glUseProgram(m_glslProgram); + + glDrawElements(GL_TRIANGLES, (GLsizei) 6, GL_UNSIGNED_INT, (const GLvoid*) 0); + + glUseProgram(0); + + glDisableVertexAttribArray(m_locAttrPosition); + glDisableVertexAttribArray(m_locAttrTexCoord); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +const int Rasterizer::getNumDevices() const +{ + return m_numDevices; +} + +const unsigned char* Rasterizer::getUUID(const unsigned int index) const +{ + MY_ASSERT(index < 24); + return m_deviceUUID[index]; +} + +const unsigned char* Rasterizer::getLUID() const +{ + return m_deviceLUID; +} + +int Rasterizer::getNodeMask() const +{ + return m_nodeMask; +} + +unsigned int Rasterizer::getTextureObject() const +{ + return m_hdrTexture; +} + +unsigned int Rasterizer::getPixelBufferObject() const +{ + return m_pbo; +} + +void Rasterizer::setResolution(const int w, const int h) +{ + if (m_widthResolution != w || m_heightResolution != h) + { + m_widthResolution = (0 < w) ? w : 1; + m_heightResolution = (0 < h) ? h : 1; + + updateVertexAttributes(); + + // Cannot resize the PBO while it's registered with cuGraphicsGLRegisterBuffer(). Deferred to the Device::render() calls. + } +} + +void Rasterizer::setTonemapper(const TonemapperGUI& tm) +{ +#if !USE_TIME_VIEW + glUseProgram(m_glslProgram); + + glUniform1f(m_locInvGamma, 1.0f / tm.gamma); + glUniform3f(m_locColorBalance, tm.colorBalance[0], tm.colorBalance[1], tm.colorBalance[2]); + glUniform1f(m_locInvWhitePoint, tm.brightness / tm.whitePoint); + glUniform1f(m_locBurnHighlights, tm.burnHighlights); + glUniform1f(m_locCrushBlacks, tm.crushBlacks + tm.crushBlacks + 1.0f); + glUniform1f(m_locSaturation, tm.saturation); + + glUseProgram(0); +#endif +} + + +// Private functions: + +void Rasterizer::checkInfoLog(const char* /* msg */, GLuint object) +{ + GLint maxLength = 0; + + const GLboolean isShader = glIsShader(object); + + if (isShader) + { + glGetShaderiv(object, GL_INFO_LOG_LENGTH, &maxLength); + } + else + { + glGetProgramiv(object, GL_INFO_LOG_LENGTH, &maxLength); + } + + if (1 < maxLength) + { + GLchar *infoLog = new GLchar[maxLength]; + + if (infoLog != nullptr) + { + GLint length = 0; + + if (isShader) + { + glGetShaderInfoLog(object, maxLength, &length, infoLog); + } + else + { + glGetProgramInfoLog(object, maxLength, &length, infoLog); + } + + //fprintf(fileLog, "-- tried to compile (len=%d): %s\n", (unsigned int)strlen(msg), msg); + //fprintf(fileLog, "--- info log contents (len=%d) ---\n", (int) maxLength); + //fprintf(fileLog, "%s", infoLog); + //fprintf(fileLog, "--- end ---\n"); + std::cout << infoLog << '\n'; + // Look at the info log string here... + + delete [] infoLog; + } + } +} + +void Rasterizer::initGLSL() +{ + static const std::string vsSource = + "#version 330\n" + "layout(location = 0) in vec2 attrPosition;\n" + "layout(location = 1) in vec2 attrTexCoord;\n" + "uniform mat4 projection;\n" + "out vec2 varTexCoord;\n" + "void main()\n" + "{\n" + " gl_Position = projection * vec4(attrPosition, 0.0, 1.0);\n" + " varTexCoord = attrTexCoord;\n" + "}\n"; + + +#if USE_TIME_VIEW + static const std::string fsSource = + "#version 330\n" + "uniform sampler2D samplerHDR;\n" + "uniform sampler1D samplerColorRamp;\n" + "in vec2 varTexCoord;\n" + "layout(location = 0, index = 0) out vec4 outColor;\n" + "void main()\n" + "{\n" + " float alpha = texture(samplerHDR, varTexCoord).a;\n" + " outColor = texture(samplerColorRamp, alpha);\n" + "}\n"; +#else + static const std::string fsSource = + "#version 330\n" + "uniform sampler2D samplerHDR;\n" + "uniform vec3 colorBalance;\n" + "uniform float invWhitePoint;\n" + "uniform float burnHighlights;\n" + "uniform float saturation;\n" + "uniform float crushBlacks;\n" + "uniform float invGamma;\n" + "in vec2 varTexCoord;\n" + "layout(location = 0, index = 0) out vec4 outColor;\n" + "void main()\n" + "{\n" + " vec3 hdrColor = texture(samplerHDR, varTexCoord).rgb;\n" + " vec3 ldrColor = invWhitePoint * colorBalance * hdrColor;\n" + " ldrColor *= (ldrColor * burnHighlights + 1.0) / (ldrColor + 1.0);\n" + " float luminance = dot(ldrColor, vec3(0.3, 0.59, 0.11));\n" + " ldrColor = max(mix(vec3(luminance), ldrColor, saturation), 0.0);\n" + " luminance = dot(ldrColor, vec3(0.3, 0.59, 0.11));\n" + " if (luminance < 1.0)\n" + " {\n" + " ldrColor = max(mix(pow(ldrColor, vec3(crushBlacks)), ldrColor, sqrt(luminance)), 0.0);\n" + " }\n" + " ldrColor = pow(ldrColor, vec3(invGamma));\n" + " outColor = vec4(ldrColor, 1.0);\n" + "}\n"; +#endif + + GLint vsCompiled = 0; + GLint fsCompiled = 0; + + GLuint glslVS = glCreateShader(GL_VERTEX_SHADER); + if (glslVS) + { + GLsizei len = (GLsizei) vsSource.size(); + const GLchar *vs = vsSource.c_str(); + glShaderSource(glslVS, 1, &vs, &len); + glCompileShader(glslVS); + checkInfoLog(vs, glslVS); + + glGetShaderiv(glslVS, GL_COMPILE_STATUS, &vsCompiled); + MY_ASSERT(vsCompiled); + } + + GLuint glslFS = glCreateShader(GL_FRAGMENT_SHADER); + if (glslFS) + { + GLsizei len = (GLsizei) fsSource.size(); + const GLchar *fs = fsSource.c_str(); + glShaderSource(glslFS, 1, &fs, &len); + glCompileShader(glslFS); + checkInfoLog(fs, glslFS); + + glGetShaderiv(glslFS, GL_COMPILE_STATUS, &fsCompiled); + MY_ASSERT(fsCompiled); + } + + m_glslProgram = glCreateProgram(); + if (m_glslProgram) + { + GLint programLinked = 0; + + if (glslVS && vsCompiled) + { + glAttachShader(m_glslProgram, glslVS); + } + if (glslFS && fsCompiled) + { + glAttachShader(m_glslProgram, glslFS); + } + + glLinkProgram(m_glslProgram); + checkInfoLog("m_glslProgram", m_glslProgram); + + glGetProgramiv(m_glslProgram, GL_LINK_STATUS, &programLinked); + MY_ASSERT(programLinked); + + if (programLinked) + { + glUseProgram(m_glslProgram); + + // DAR FIXME Put these into a struct. + m_locAttrPosition = glGetAttribLocation(m_glslProgram, "attrPosition"); + m_locAttrTexCoord = glGetAttribLocation(m_glslProgram, "attrTexCoord"); + m_locProjection = glGetUniformLocation(m_glslProgram, "projection"); + + MY_ASSERT(m_locAttrPosition != -1); + MY_ASSERT(m_locAttrTexCoord != -1); + MY_ASSERT(m_locProjection != -1); + + m_locSamplerHDR = glGetUniformLocation(m_glslProgram, "samplerHDR"); + MY_ASSERT(m_locSamplerHDR != -1); + glUniform1i(m_locSamplerHDR, 0); // The rasterizer uses texture image unit 0 to display the HDR image. + +#if USE_TIME_VIEW + m_locSamplerColorRamp = glGetUniformLocation(m_glslProgram, "samplerColorRamp"); + MY_ASSERT(m_locSamplerColorRamp != -1); + glUniform1i(m_locSamplerColorRamp, 1); // The rasterizer uses texture image unit 1 for the color ramp when USE_TIME_VIEW is enabled. +#else + m_locInvGamma = glGetUniformLocation(m_glslProgram, "invGamma"); + m_locColorBalance = glGetUniformLocation(m_glslProgram, "colorBalance"); + m_locInvWhitePoint = glGetUniformLocation(m_glslProgram, "invWhitePoint"); + m_locBurnHighlights = glGetUniformLocation(m_glslProgram, "burnHighlights"); + m_locCrushBlacks = glGetUniformLocation(m_glslProgram, "crushBlacks"); + m_locSaturation = glGetUniformLocation(m_glslProgram, "saturation"); + + MY_ASSERT(m_locInvGamma != -1); + MY_ASSERT(m_locColorBalance != -1); + MY_ASSERT(m_locInvWhitePoint != -1); + MY_ASSERT(m_locBurnHighlights != -1); + MY_ASSERT(m_locCrushBlacks != -1); + MY_ASSERT(m_locSaturation != -1); + + // Set neutral Tonemapper defaults. This will show the linear HDR image. + glUniform1f(m_locInvGamma, 1.0f); + glUniform3f(m_locColorBalance, 1.0f, 1.0f, 1.0f); + glUniform1f(m_locInvWhitePoint, 1.0f); + glUniform1f(m_locBurnHighlights, 1.0f); + glUniform1f(m_locCrushBlacks, 1.0f); + glUniform1f(m_locSaturation, 1.0f); +#endif + + glUseProgram(0); + } + } + + if (glslVS) + { + glDeleteShader(glslVS); + } + if (glslFS) + { + glDeleteShader(glslFS); + } +} + + +void Rasterizer::updateProjectionMatrix() +{ + // No need to set this when using shaders only. + //glMatrixMode(GL_PROJECTION); + //glLoadIdentity(); + //glOrtho(0.0, GLdouble(m_width), 0.0, GLdouble(m_height), -1.0, 1.0); + + //glMatrixMode(GL_MODELVIEW); + + // Full projection matrix calculation: + //const float l = 0.0f; + const float r = float(m_width); + //const float b = 0.0f; + const float t = float(m_height); + //const float n = -1.0f; + //const float f = 1.0; + + //const float m00 = 2.0f / (r - l); // == 2.0f / r with l == 0.0f + //const float m11 = 2.0f / (t - b); // == 2.0f / t with b == 0.0f + //const float m22 = -2.0f / (f - n); // Always -1.0f with f == 1.0f and n == -1.0f + //const float tx = -(r + l) / (r - l); // Always -1.0f with l == 0.0f + //const float ty = -(t + b) / (t - b); // Always -1.0f with b == 0.0f + //const float tz = -(f + n) / (f - n); // Always 0.0f with f = -n + + // Row-major layout, needs transpose in glUniformMatrix4fv. + //const float projection[16] = + //{ + // m00, 0.0f, 0.0f, tx, + // 0.0f, m11, 0.0f, ty, + // 0.0f, 0.0f, m22, tz, + // 0.0f, 0.0f, 0.0f, 1.0f + //}; + + // Optimized version and colum-major layout: + const float projection[16] = + { + 2.0f / r, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / t, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 1.0f + }; + + glUseProgram(m_glslProgram); + glUniformMatrix4fv(m_locProjection, 1, GL_FALSE, projection); // Column-major memory layout, no transpose. + glUseProgram(0); +} + + +void Rasterizer::updateVertexAttributes() +{ + // This routine calculates the vertex attributes for the diplay routine. + // It calculates screen space vertex coordinates to display the full rendered image + // in the correct aspect ratio independently of the window client size. + // The image gets scaled down when it's bigger than the client window. + + // The final screen space vertex coordinates for the texture blit. + float x0; + float y0; + float x1; + float y1; + + // This routine picks the required filtering mode for this texture. + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_hdrTexture); + + if (m_widthResolution <= m_width && m_heightResolution <= m_height) + { + // Texture fits into viewport without scaling. + // Calculate the amount of cleared border pixels. + int w1 = m_width - m_widthResolution; + int h1 = m_height - m_heightResolution; + // Halve the border size to get the lower left offset + int w0 = w1 >> 1; + int h0 = h1 >> 1; + // Subtract from the full border to get the right top offset. + w1 -= w0; + h1 -= h0; + // Calculate the texture blit screen space coordinates. + x0 = float(w0); + y0 = float(h0); + x1 = float(m_width - w1); + y1 = float(m_height - h1); + + // Fill the background with black to indicate that all pixels are visible without scaling. + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + // Use nearest filtering to display the pixels exactly. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else // Case + { + // Texture needs to be scaled down to fit into client window. + // Check which extent defines the necessary scaling factor. + const float wC = float(m_width); + const float hC = float(m_height); + const float wR = float(m_widthResolution); + const float hR = float(m_heightResolution); + + const float scale = std::min(wC / wR, hC / hR); + + const float swR = scale * wR; + const float shR = scale * hR; + + x0 = 0.5f * (wC - swR); + y0 = 0.5f * (hC - shR); + x1 = x0 + swR; + y1 = y0 + shR; + + // Render surrounding pixels in dark red to indicate that the image is scaled down. + glClearColor(0.2f, 0.0f, 0.0f, 0.0f); + + // Use linear filtering to smooth the downscaling. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + // Update the vertex attributes with the new texture blit screen space coordinates. + const float attributes[16] = + { + // vertex2f + x0, y0, + x1, y0, + x1, y1, + x0, y1, + // texcoord2f + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + glBindBuffer(GL_ARRAY_BUFFER, m_vboAttributes); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) sizeof(float) * 16, (GLvoid const*) attributes, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); // PERF It should be faster to keep them bound. +} diff --git a/apps/bench_shared_offscreen/src/Raytracer.cpp b/apps/bench_shared_offscreen/src/Raytracer.cpp new file mode 100644 index 00000000..5b9fc7f1 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Raytracer.cpp @@ -0,0 +1,912 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/Raytracer.h" + +#include "inc/CheckMacros.h" + +#include +#include +#include +#include +#include + +Raytracer::Raytracer(const int maskDevices, + const int miss, + //const int interop, + //const unsigned int tex, + //const unsigned int pbo, + const size_t sizeArena, + const int p2p) +: m_maskDevices(maskDevices) +, m_miss(miss) +//, m_interop(interop) +//, m_tex(tex) +//, m_pbo(pbo) +, m_sizeArena(sizeArena) +, m_peerToPeer(p2p) +, m_isValid(false) +, m_numDevicesVisible(0) +//, m_indexDeviceOGL(-1) +, m_maskDevicesActive(0) +, m_iterationIndex(0) +, m_samplesPerPixel(1) +{ + CU_CHECK( cuInit(0) ); // Initialize CUDA driver API. + + int versionDriver = 0; + CU_CHECK( cuDriverGetVersion(&versionDriver) ); + + // The version is returned as (1000 * major + 10 * minor). + int major = versionDriver / 1000; + int minor = (versionDriver - 1000 * major) / 10; + std::cout << "CUDA Driver Version = " << major << "." << minor << '\n'; + + CU_CHECK( cuDeviceGetCount(&m_numDevicesVisible) ); + std::cout << "CUDA Device Count = " << m_numDevicesVisible << '\n'; + + // Match user defined m_maskDevices with the number of visible devices. + // Builds m_maskActiveDevices and fills m_devicesActive which defines the device count. + selectDevices(); + + // This Raytracer is all about sharing data in peer-to-peer islands on multi-GPU setups. + // While that can be individually enabled for texture array and/or GAS and vertex attribute data sharing, + // the compositing of the final image is also done with peer-to-peer copies. + (void) enablePeerAccess(); + + m_isValid = !m_devicesActive.empty(); +} + + +Raytracer::~Raytracer() +{ + try + { + // This function contains throw() calls. + disablePeerAccess(); // Just for cleanliness, the Devices are destroyed anyway after this. + + // The GeometryData is either created on each device or only on one device of an NVLINK island. + // In any case GeometryData is unique and must only be destroyed by the device owning the data. + for (auto& data : m_geometryData) + { + m_devicesActive[data.owner]->destroyGeometry(data); + } + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + delete m_devicesActive[i]; + } + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + +} + +//int Raytracer::matchUUID(const char* uuid) +//{ +// const int size = static_cast(m_devicesActive.size()); +// +// for (int i = 0; i < size; ++i) +// { +// if (m_devicesActive[i]->matchUUID(uuid)) +// { +// // Use the first device which matches with the OpenGL UUID. +// // DEBUG This might not be the right thing to do with multicast enabled. +// m_indexDeviceOGL = i; +// break; +// } +// } +// +// std::cout << "OpenGL on active device index " << m_indexDeviceOGL << '\n'; // DEBUG +// +// return m_indexDeviceOGL; // If this stays -1, the active devices do not contain the one running the OpenGL implementation. +//} +// +//int Raytracer::matchLUID(const char* luid, const unsigned int nodeMask) +//{ +// const int size = static_cast(m_devicesActive.size()); +// +// for (int i = 0; i < size; ++i) +// { +// if (m_devicesActive[i]->matchLUID(luid, nodeMask)) +// { +// // Use the first device which matches with the OpenGL LUID and test of the node mask bit. +// // DEBUG This might not be the right thing to do with multicast enabled. +// m_indexDeviceOGL = i; +// break; +// } +// } +// +// std::cout << "OpenGL on active device index " << m_indexDeviceOGL << '\n'; // DEBUG +// +// return m_indexDeviceOGL; // If this stays -1, the active devices do not contain the one running the OpenGL implementation. +//} + + +int Raytracer::findActiveDevice(const unsigned int domain, const unsigned int bus, const unsigned int device) const +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + const DeviceAttribute& attribute = m_devicesActive[i]->m_deviceAttribute; + + if (attribute.pciDomainId == domain && + attribute.pciBusId == bus && + attribute.pciDeviceId == device) + { + return static_cast(i); + } + } + + return -1; +} + + +bool Raytracer::activeNVLINK(const int home, const int peer) const +{ + // All NVML calls related to NVLINK are only supported by Pascal (SM 6.0) and newer. + if (m_devicesActive[home]->m_deviceAttribute.computeCapabilityMajor < 6) + { + return false; + } + + nvmlDevice_t deviceHome; + + if (m_nvml.m_api.nvmlDeviceGetHandleByPciBusId(m_devicesActive[home]->m_devicePciBusId.c_str(), &deviceHome) != NVML_SUCCESS) + { + return false; + } + + // The NVML deviceHome is part of the active devices at index "home". + for (unsigned int link = 0; link < NVML_NVLINK_MAX_LINKS; ++link) + { + // First check if this link is supported at all and if it's active. + nvmlEnableState_t enableState = NVML_FEATURE_DISABLED; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkState(deviceHome, link, &enableState) != NVML_SUCCESS) + { + continue; + } + if (enableState != NVML_FEATURE_ENABLED) + { + continue; + } + + // Is peer-to-peer over NVLINK supported by this link? + // The requirement for peer-to-peer over NVLINK under Windows is Windows 10 (WDDM2), 64-bit, SLI enabled. + unsigned int capP2P = 0; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkCapability(deviceHome, link, NVML_NVLINK_CAP_P2P_SUPPORTED, &capP2P) != NVML_SUCCESS) + { + continue; + } + if (capP2P == 0) + { + continue; + } + + nvmlPciInfo_t pciInfoPeer; + + if (m_nvml.m_api.nvmlDeviceGetNvLinkRemotePciInfo(deviceHome, link, &pciInfoPeer) != NVML_SUCCESS) + { + continue; + } + + // Check if the NVML remote device matches the desired peer devcice. + if (peer == findActiveDevice(pciInfoPeer.domain, pciInfoPeer.bus, pciInfoPeer.device)) + { + return true; + } + } + + return false; +} + + +bool Raytracer::enablePeerAccess() +{ + bool success = true; + + // Build a peer-to-peer connection matrix which only allows peer-to-peer access over NVLINK bridges. + const int size = static_cast(m_devicesActive.size()); + MY_ASSERT(size <= 32); + + // Peer-to-peer access is encoded in a bitfield of uint32 entries. + // Indexed by [home] device and peer devices are the bit indices, accessed with (1 << peer) masks. + m_peerConnections.resize(size); + + // Initialize the connection matrix diagonal with the trivial case (home == peer). + // This let's building the islands still work if there are any exceptions. + for (int home = 0; home < size; ++home) + { + m_peerConnections[home] = (1 << home); + } + + // Check if the system configuration option "peerToPeer" allowed peer-to-peer via PCI-E irrespective of the NVLINK topology. + // In that case the activeNVLINK() function is not called below. + // DAR FIXME PERF In that case NVML wouldn't be needed at all. + const bool allowPCI = ((m_peerToPeer & P2P_PCI) != 0); + + // The NVML_CHECK and CU_CHECK macros can throw exceptions. + // Keep them local in this routine because not having NVLINK islands with peer-to-peer access + // is not a fatal condition for the renderer. It just won't be able to share resources. + try + { + if (m_nvml.initFunctionTable()) + { + NVML_CHECK( m_nvml.m_api.nvmlInit() ); + + for (int home = 0; home < size; ++home) // Home device index. + { + for (int peer = 0; peer < size; ++peer) // Peer device index. + { + if (home != peer && (allowPCI || activeNVLINK(home, peer))) + { + int canAccessPeer = 0; + + CU_CHECK( cuDeviceCanAccessPeer(&canAccessPeer, + m_devicesActive[home]->m_cudaDevice, // If this current home device + m_devicesActive[peer]->m_cudaDevice) ); // can access the peer device's memory. + if (canAccessPeer != 0) + { + // Note that this function changes the current context! + CU_CHECK( cuCtxSetCurrent(m_devicesActive[home]->m_cudaContext) ); + + CUresult result = cuCtxEnablePeerAccess(m_devicesActive[peer]->m_cudaContext, 0); // Flags must be 0! + if (result == CUDA_SUCCESS) + { + m_peerConnections[home] |= (1 << peer); // Set the connection bit if the enable succeeded. + } + else + { + // Print the ordinal here to be consistent with the other output about used devices. + std::cerr << "WARNING: cuCtxEnablePeerAccess() between device ordinals (" + << m_devicesActive[home]->m_ordinal << ", " + << m_devicesActive[peer]->m_ordinal << ") failed with CUresult " << result << '\n'; + } + } + } + } + } + + NVML_CHECK( m_nvml.m_api.nvmlShutdown() ); + } + } + catch (const std::exception& e) + { + // FIXME Reaching this from CU_CHECK macros above means nvmlShutdown() hasn't been called. + std::cerr << e.what() << '\n'; + // No return here. Always build the m_islands from the existing connection matrix information. + success = false; + } + + // Now use the peer-to-peer connection matrix to build peer-to-peer islands. + // First fill a vector with all device indices which have not been assigned to an island. + std::vector unassigned(size); + + for (int i = 0; i < size; ++i) + { + unassigned[i] = i; + } + + while (!unassigned.empty()) + { + std::vector island; + std::vector::const_iterator it = unassigned.begin(); + + island.push_back(*it); + unassigned.erase(it); // This device has been assigned to an island. + + it = unassigned.begin(); // The next unassigned device. + while (it != unassigned.end()) + { + bool isAccessible = true; + + const int peer = *it; + + // Check if this peer device is accessible by all other devices in the island. + for (size_t i = 0; i < island.size(); ++i) + { + const int home = island[i]; + + if ((m_peerConnections[home] & (1 << peer)) == 0 || + (m_peerConnections[peer] & (1 << home)) == 0) + { + isAccessible = false; + } + } + + if (isAccessible) + { + island.push_back(*it); + unassigned.erase(it); // This device has been assigned to an island. + + it = unassigned.begin(); // The next unassigned device. + } + else + { + ++it; // The next unassigned device, without erase in between. + } + } + m_islands.push_back(island); + } + + std::ostringstream text; + + text << m_islands.size() << " peer-to-peer island"; + if (1 < m_islands.size()) + { + text << 's'; + } + text << ": "; + for (size_t i = 0; i < m_islands.size(); ++i) + { + const std::vector& island = m_islands[i]; + + text << "("; + for (size_t j = 0; j < island.size(); ++j) + { + // Print the ordinal here to be consistent with the other output about used devices. + text << m_devicesActive[island[j]]->m_ordinal; + if (j + 1 < island.size()) + { + text << ", "; + } + } + text << ")"; + if (i + 1 < m_islands.size()) + { + text << " + "; + } + } + std::cout << text.str() << '\n'; + + return success; +} + +void Raytracer::disablePeerAccess() +{ + const int size = static_cast(m_devicesActive.size()); + MY_ASSERT(size <= 32); + + // Peer-to-peer access is encoded in a bitfield of uint32 entries. + for (int home = 0; home < size; ++home) // Home device index. + { + for (int peer = 0; peer < size; ++peer) // Peer device index. + { + if (home != peer && (m_peerConnections[home] & (1 << peer)) != 0) + { + // Note that this function changes the current context! + CU_CHECK( cuCtxSetCurrent(m_devicesActive[home]->m_cudaContext) ); // Home context. + CU_CHECK( cuCtxDisablePeerAccess(m_devicesActive[peer]->m_cudaContext) ); // Peer context. + + m_peerConnections[home] &= ~(1 << peer); + } + } + } + + m_islands.clear(); // No peer-to-peer islands anymore. + + // Each device is its own island now. + for (int i = 0; i < size; ++i) + { + std::vector island; + + island.push_back(i); + + m_islands.push_back(island); + + m_peerConnections[i] |= (1 << i); // Should still be set from above. + } +} + +void Raytracer::synchronize() +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->activateContext(); + m_devicesActive[i]->synchronizeStream(); + } +} + +// DAR FIXME This cannot handle cases where the same Picture would be used for different texture objects, but that is not happening in this example. +void Raytracer::initTextures(const std::map& mapPictures) +{ + const bool allowSharingTex = ((m_peerToPeer & P2P_TEX) != 0); // Material texture sharing (very cheap). + const bool allowSharingEnv = ((m_peerToPeer & P2P_ENV) != 0); // HDR Environment and CDF sharing (CDF binary search is expensive). + + for (std::map::const_iterator it = mapPictures.begin(); it != mapPictures.end(); ++it) + { + const Picture* picture = it->second; + + const bool isEnv = ((picture->getFlags() & IMAGE_FLAG_ENV) != 0); + + if ((allowSharingTex && !isEnv) || (allowSharingEnv && isEnv)) + { + for (const auto& island : m_islands) // Resource sharing only works across devices inside a peer-to-peer island. + { + const int deviceHome = getDeviceHome(island); + + const Texture* texture = m_devicesActive[deviceHome]->initTexture(it->first, picture, picture->getFlags()); + + for (auto device : island) + { + if (device != deviceHome) + { + m_devicesActive[device]->shareTexture(it->first, texture); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + (void) m_devicesActive[device]->initTexture(it->first, picture, picture->getFlags()); + } + } + } +} + + +void Raytracer::initCameras(const std::vector& cameras) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initCameras(cameras); + } +} + +void Raytracer::initLights(const std::vector& lights) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initLights(lights); + } +} + +void Raytracer::initMaterials(const std::vector& materialsGUI) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->initMaterials(materialsGUI); + } +} + +// Traverse the SceneGraph and store Groups, Instances and Triangles nodes in the raytracer representation. +void Raytracer::initScene(std::shared_ptr root, const unsigned int numGeometries) +{ + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); // GAS and vertex attribute sharing (GAS sharing is very expensive). + + if (allowSharingGas) + { + // Allocate the number of GeometryData per island. + m_geometryData.resize(numGeometries * m_islands.size()); // Sharing data per island. + } + else + { + // Allocate the number of GeometryData per active device. + m_geometryData.resize(numGeometries * m_devicesActive.size()); // Not sharing, all devices hold all geometry data. + } + + InstanceData instanceData(~0u, -1, -1); + + float matrix[12]; + + // Set the affine matrix to identity by default. + memset(matrix, 0, sizeof(float) * 12); + matrix[ 0] = 1.0f; + matrix[ 5] = 1.0f; + matrix[10] = 1.0f; + + traverseNode(root, instanceData, matrix); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + for (auto device : island) // Device index in this island. + { + // The IAS and SBT are not shared in this example. + m_devicesActive[device]->createTLAS(); + m_devicesActive[device]->createHitGroupRecords(m_geometryData, numIslands, indexIsland); + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + m_devicesActive[device]->createTLAS(); + m_devicesActive[device]->createHitGroupRecords(m_geometryData, numDevices, device); + } + } +} + + +void Raytracer::initState(const DeviceState& state) +{ + m_samplesPerPixel = (unsigned int)(state.samplesSqrt * state.samplesSqrt); + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->setState(state); + } +} + +void Raytracer::updateCamera(const int idCamera, const CameraDefinition& camera) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateCamera(idCamera, camera); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateLight(const int idLight, const LightDefinition& light) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateLight(idLight, light); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateMaterial(const int idMaterial, const MaterialGUI& materialGUI) +{ + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->updateMaterial(idMaterial, materialGUI); + } + m_iterationIndex = 0; // Restart accumulation. +} + +void Raytracer::updateState(const DeviceState& state) +{ + m_samplesPerPixel = (unsigned int)(state.samplesSqrt * state.samplesSqrt); + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + m_devicesActive[i]->setState(state); + } + m_iterationIndex = 0; // Restart accumulation. +} + + +// The public function which does the multi-GPU wrapping. +// Returns the count of renderered iterations (m_iterationIndex after it has been incremented). +unsigned int Raytracer::render() +{ + // Continue manual accumulation rendering if the samples per pixel have not been reached. + if (m_iterationIndex < m_samplesPerPixel) + { + void* buffer = nullptr; + + // Make sure the OpenGL device is allocating the full resolution backing storage. + //const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. + + // This is the device which needs to allocate the peer-to-peer buffer to reside on the same device as the PBO or Texture + //m_devicesActive[index]->render(m_iterationIndex, &buffer); // Interactive rendering. All devices work on the same iteration index. + + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + //if (index != static_cast(i)) + //{ + // If buffer is still nullptr here, the first device will allocate the full resolution buffer. + m_devicesActive[i]->render(m_iterationIndex, &buffer); + //} + } + + ++m_iterationIndex; + } + return m_iterationIndex; +} + +//void Raytracer::updateDisplayTexture() +//{ +// const int index = (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : 0; // Destination device. +// +// // First, copy the texelBuffer of the primary device into its tileBuffer and then place the tiles into the outputBuffer. +// m_devicesActive[index]->compositor(m_devicesActive[index]); +// +// // Now copy the other devices' texelBuffers over to the main tileBuffer and repeat the compositing for that other device. +// // The cuMemcpyPeerAsync done in that case is fast when the devices are in the same peer island, otherwise it's copied via PCI-E, but only N-1 copies of 1/N size are done. +// // The saving here is no peer-to-peer read-modify-write when rendering, because everything is happening in GPU local buffers, which are also tightly packed. +// // The final compositing is just a kernel implementing a tiled memcpy. +// // PERF If all tiles are copied to the main device at once, such kernel would only need to be called once. +// for (size_t i = 0; i < m_devicesActive.size(); ++i) +// { +// if (m_indexDeviceOGL != i) +// { +// m_devicesActive[index]->compositor(m_devicesActive[i]); +// } +// } +// +// // Finally copy the primary device outputBuffer to the display texture. +// // FIXME DEBUG Does that work when m_indexDeviceOGL is not in the list of active devices? +// m_devicesActive[index]->updateDisplayTexture(); +//} + +const void* Raytracer::getOutputBufferHost() +{ + // Same initial steps to fill the outputBuffer on the primary device as in updateDisplayTexture() + const int index = /* (m_indexDeviceOGL != -1) ? m_indexDeviceOGL : */ 0; // Destination device. + + // First, copy the texelBuffer of the primary device into its tileBuffer and then place the tiles into the outputBuffer. + //m_devicesActive[index]->compositor(m_devicesActive[index]); + + // Now copy the other devices' texelBuffers over to the main tileBuffer and repeat the compositing for that other device. + for (size_t i = 0; i < m_devicesActive.size(); ++i) + { + //if (m_indexDeviceOGL != i) // DAR BUG Should have been be index. + //{ + m_devicesActive[index]->compositor(m_devicesActive[i]); + //} + } + + // The full outputBuffer resides on device "index" and the host buffer is also only resized by that device. + return m_devicesActive[index]->getOutputBufferHost(); +} + +// Private functions. + +void Raytracer::selectDevices() +{ + // Need to determine the number of active devices first to have it available as device constructor argument. + int count = 0; + int ordinal = 0; + + while (ordinal < m_numDevicesVisible) // Don't try to enable more devices than visible to CUDA. + { + const unsigned int mask = (1 << ordinal); + + if (m_maskDevices & mask) + { + // Track which and how many devices have actually been enabled. + m_maskDevicesActive |= mask; + ++count; + } + + ++ordinal; + } + + // Now really construct the Device objects. + ordinal = 0; + + while (ordinal < m_numDevicesVisible) + { + const unsigned int mask = (1 << ordinal); + + if (m_maskDevicesActive & mask) + { + const int index = static_cast(m_devicesActive.size()); + + Device* device = new Device(ordinal, index, count, m_miss, /* m_interop, m_tex, m_pbo, */ m_sizeArena); + + m_devicesActive.push_back(device); + + std::cout << "Device ordinal " << ordinal << ": " << device->m_deviceName << " selected as active device index " << index << '\n'; + } + + ++ordinal; + } + + if (m_devicesActive.size() == 1) + { + std::cerr << "WARNING: selectDevices() Only one device active! This renderer is designed for multi-GPU with NVLINK.\n"; + } +} + +#if 1 +// This implementation does not consider the actually free amount of VRAM on the individual devices in an island, but assumes they are equally loaded. +// This method works more fine grained with the arena allocator. +int Raytracer::getDeviceHome(const std::vector& island) const +{ + // Find the device inside each island which has the least amount of allocated memory. + size_t sizeMin = ~0ull; // Biggest unsigned 64-bit number. + int deviceHome = 0; // Default to zero if all devices are OOM. That will fail in CU_CHECK later. + + for (auto device : island) + { + const size_t size = m_devicesActive[device]->getMemoryAllocated(); + + if (size < sizeMin) + { + sizeMin = size; + deviceHome = device; + } + } + + //std::cout << "deviceHome = " << deviceHome << ", allocated [MiB] = " << double(sizeMin) / (1024.0 * 1024.0) << '\n'; // DEBUG + + return deviceHome; +} + +#else + +// This implementation uses the actual free amount of VRAM on the individual devices in an NVLINK island. +// With the arena allocator this will result in less fine grained distribution of resources because the free memory only changes when a new arena is allocated. +// Using a smaller arena size would switch allocations between devices more often in this case. +int Raytracer::getDeviceHome(const std::vector& island) const +{ + // Find the device inside each island which has the most free memory. + size_t sizeMax = 0; + int deviceHome = 0; // Default to zero if all devices are OOM. That will fail in CU_CHECK later. + + for (auto device : island) + { + const size_t size = m_devicesActive[device]->getMemoryFree(); // Actual free VRAM overall. + + if (sizeMax < size) + { + sizeMax = size; + deviceHome = device; + } + } + + //std::cout << "deviceHome = " << deviceHome << ", free [MiB] = " << double(sizeMax) / (1024.0 * 1024.0) << '\n'; // DEBUG + + return deviceHome; +} +#endif + +// m = a * b; +static void multiplyMatrix(float* m, const float* a, const float* b) +{ + m[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8]; // + a[3] * 0 + m[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9]; // + a[3] * 0 + m[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10]; // + a[3] * 0 + m[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[3]; // * 1 + + m[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8]; // + a[7] * 0 + m[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9]; // + a[7] * 0 + m[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10]; // + a[7] * 0 + m[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[7]; // * 1 + + m[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; // + a[11] * 0 + m[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; // + a[11] * 0 + m[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; // + a[11] * 0 + m[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; // * 1 +} + + +// Depth-first traversal of the scene graph to flatten all unique paths to a geometry node to one-level instancing inside the OptiX render graph. +void Raytracer::traverseNode(std::shared_ptr node, InstanceData instanceData, float matrix[12]) +{ + switch (node->getType()) + { + case sg::NodeType::NT_GROUP: + { + std::shared_ptr group = std::dynamic_pointer_cast(node); + + for (size_t i = 0; i < group->getNumChildren(); ++i) + { + traverseNode(group->getChild(i), instanceData, matrix); + } + } + break; + + case sg::NodeType::NT_INSTANCE: + { + std::shared_ptr instance = std::dynamic_pointer_cast(node); + + // Track the assigned material and light indices. The bottom-most node wins. + const int idMaterial = instance->getMaterial(); + if (0 <= idMaterial) + { + instanceData.idMaterial = idMaterial; + } + + const int idLight = instance->getLight(); + if (0 <= idLight) + { + instanceData.idLight = idLight; + } + + // Concatenate the transformations along the path. + float trafo[12]; + + multiplyMatrix(trafo, matrix, instance->getTransform()); + + traverseNode(instance->getChild(), instanceData, trafo); + } + break; + + case sg::NodeType::NT_TRIANGLES: + { + std::shared_ptr geometry = std::dynamic_pointer_cast(node); + + instanceData.idGeometry = geometry->getId(); + + const bool allowSharingGas = ((m_peerToPeer & P2P_GAS) != 0); + + if (allowSharingGas) + { + const unsigned int numIslands = static_cast(m_islands.size()); + + for (unsigned int indexIsland = 0; indexIsland < numIslands; ++indexIsland) + { + const auto& island = m_islands[indexIsland]; // Vector of device indices. + + const int deviceHome = getDeviceHome(island); + + // GeometryData is always shared and tracked per island. + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numIslands + indexIsland]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry in this island, try to create one on the home device. + { + geometryData = m_devicesActive[deviceHome]->createGeometry(geometry); + } + else + { + std::cout << "traverseNode() Geometry " << instanceData.idGeometry << " reused\n"; // DEBUG + } + + m_devicesActive[deviceHome]->createInstance(geometryData, instanceData, matrix); + + // Now share the GeometryData on the other devices in this island. + for (const auto device : island) + { + if (device != deviceHome) + { + // Create the instance referencing the shared GAS traversable on the peer device in this island. + // This is only host data. The IAS is created after gathering all flattened instances in the scene. + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + } + else + { + const unsigned int numDevices = static_cast(m_devicesActive.size()); + + for (unsigned int device = 0; device < numDevices; ++device) + { + GeometryData& geometryData = m_geometryData[instanceData.idGeometry * numDevices + device]; + + if (geometryData.traversable == 0) // If there is no traversable handle for this geometry on this device, try to create one. + { + geometryData = m_devicesActive[device]->createGeometry(geometry); + } + + m_devicesActive[device]->createInstance(geometryData, instanceData, matrix); + } + } + } + break; + } +} diff --git a/apps/bench_shared_offscreen/src/SceneGraph.cpp b/apps/bench_shared_offscreen/src/SceneGraph.cpp new file mode 100644 index 00000000..2cc34ee8 --- /dev/null +++ b/apps/bench_shared_offscreen/src/SceneGraph.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2019-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/SceneGraph.h" + +#include +#include +#include + +#include "inc/MyAssert.h" + +namespace sg +{ + // ========== Node + Node::Node(const unsigned int id) + : m_id(id) + { + } + + //Node::~Node() + //{ + //} + + // ========== Group + Group::Group(const unsigned int id) + : Node(id) + { + } + + //Group::~Group() + //{ + //} + + sg::NodeType Group::getType() const + { + return NT_GROUP; + } + + void Group::addChild(std::shared_ptr instance) + { + m_children.push_back(instance); + } + + size_t Group::getNumChildren() const + { + return m_children.size(); + } + + std::shared_ptr Group::getChild(const size_t index) + { + MY_ASSERT(index < m_children.size()); + return m_children[index]; + } + + + // ========== Instance + Instance::Instance(const unsigned int id) + : Node(id) + , m_material(-1) // No material index set by default. Last one >= 0 along a path wins. + , m_light(-1) // No light index set by default. Not a light. + { + // Set the affine matrix to identity by default. + memset(m_matrix, 0, sizeof(float) * 12); + m_matrix[ 0] = 1.0f; + m_matrix[ 5] = 1.0f; + m_matrix[10] = 1.0f; + } + + //Instance::~Instance() + //{ + //} + + sg::NodeType Instance::getType() const + { + return NT_INSTANCE; + } + + void Instance::setTransform(const float m[12]) + { + memcpy(m_matrix, m, sizeof(float) * 12); + } + + const float* Instance::getTransform() const + { + return m_matrix; + } + + void Instance::setChild(std::shared_ptr node) // Instances can hold all other groups. + { + m_child = node; + } + + std::shared_ptr Instance::getChild() + { + return m_child; + } + + void Instance::setMaterial(const int index) + { + m_material = index; + } + + int Instance::getMaterial() const + { + return m_material; + } + + void Instance::setLight(const int index) + { + m_light = index; + } + + int Instance::getLight() const + { + return m_light; + } + + // ========== Triangles + Triangles::Triangles(const unsigned int id) + : Node(id) + { + } + + //Triangles::~Triangles() + //{ + //} + + sg::NodeType Triangles::getType() const + { + return NT_TRIANGLES; + } + + void Triangles::setAttributes(const std::vector& attributes) + { + m_attributes.resize(attributes.size()); + memcpy(m_attributes.data(), attributes.data(), sizeof(TriangleAttributes) * attributes.size()); + } + + const std::vector& Triangles::getAttributes() const + { + return m_attributes; + } + + void Triangles::setIndices(const std::vector& indices) + { + m_indices.resize(indices.size()); + memcpy(m_indices.data(), indices.data(), sizeof(unsigned int) * indices.size()); + } + + const std::vector& Triangles::getIndices() const + { + return m_indices; + } + + +} // namespace sg + diff --git a/apps/bench_shared_offscreen/src/Sphere.cpp b/apps/bench_shared_offscreen/src/Sphere.cpp new file mode 100644 index 00000000..1fd3391f --- /dev/null +++ b/apps/bench_shared_offscreen/src/Sphere.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + void Triangles::createSphere(const unsigned int tessU, const unsigned int tessV, const float radius, const float maxTheta) + { + MY_ASSERT(3 <= tessU && 3 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + m_attributes.reserve((tessU + 1) * tessV); + m_indices.reserve(6 * tessU * (tessV - 1)); + + float phi_step = 2.0f * M_PIf / (float) tessU; + float theta_step = maxTheta / (float) (tessV - 1); + + // Latitudinal rings. + // Starting at the south pole going upwards on the y-axis. + for (unsigned int latitude = 0; latitude < tessV; ++latitude) // theta angle + { + float theta = (float) latitude * theta_step; + float sinTheta = sinf(theta); + float cosTheta = cosf(theta); + + float texv = (float) latitude / (float) (tessV - 1); // Range [0.0f, 1.0f] + + // Generate vertices along the latitudinal rings. + // On each latitude there are tessU + 1 vertices. + // The last one and the first one are on identical positions, but have different texture coordinates! + // FIXME Note that each second triangle connected to the two poles has zero area! + for (unsigned int longitude = 0; longitude <= tessU; ++longitude) // phi angle + { + float phi = (float) longitude * phi_step; + float sinPhi = sinf(phi); + float cosPhi = cosf(phi); + + float texu = (float) longitude / (float) tessU; // Range [0.0f, 1.0f] + + // Unit sphere coordinates are the normals. + float3 normal = make_float3(cosPhi * sinTheta, + -cosTheta, // -y to start at the south pole. + -sinPhi * sinTheta); + TriangleAttributes attrib; + + attrib.vertex = normal * radius; + attrib.tangent = make_float3(-sinPhi, 0.0f, -cosPhi); + attrib.normal = normal; + attrib.texcoord = make_float3(texu, texv, 0.0f); + + m_attributes.push_back(attrib); + } + } + + // We have generated tessU + 1 vertices per latitude. + const unsigned int columns = tessU + 1; + + // Calculate m_indices. + for (unsigned int latitude = 0; latitude < tessV - 1; ++latitude) + { + for (unsigned int longitude = 0; longitude < tessU; ++longitude) + { + m_indices.push_back( latitude * columns + longitude); // lower left + m_indices.push_back( latitude * columns + longitude + 1); // lower right + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + m_indices.push_back((latitude + 1) * columns + longitude); // upper left + m_indices.push_back( latitude * columns + longitude); // lower left + } + } + } + +} // namespace sg diff --git a/apps/bench_shared_offscreen/src/Texture.cpp b/apps/bench_shared_offscreen/src/Texture.cpp new file mode 100644 index 00000000..afb468cb --- /dev/null +++ b/apps/bench_shared_offscreen/src/Texture.cpp @@ -0,0 +1,2150 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "shaders/vector_math.h" + +#include "inc/Device.h" +//#include "inc/Texture.h" // Device.h includes Texture.h +#include "inc/CheckMacros.h" +#include "inc/MyAssert.h" + +#include +#include +#include +#include + + +// The ENC_RED|GREEN|BLUE|ALPHA|LUM codes define from which source channel is read when writing R, G, B, A or L. +static unsigned int determineHostEncoding(int format, int type) // format and type are DevIL defines.. +{ + unsigned int encoding; + + switch (format) + { + case IL_RGB: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_3; + break; + case IL_RGBA: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; + break; + case IL_BGR: + encoding = ENC_RED_2 | ENC_GREEN_1 | ENC_BLUE_0 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_3; + break; + case IL_BGRA: + encoding = ENC_RED_2 | ENC_GREEN_1 | ENC_BLUE_0 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; + break; + case IL_LUMINANCE: + // encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_0 | ENC_CHANNELS_1; + encoding = ENC_RED_0 | ENC_GREEN_0 | ENC_BLUE_0 | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_CHANNELS_1; // Source RGB from L to expand to (L, L, L, 1). + break; + case IL_ALPHA: + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_0 | ENC_LUM_NONE | ENC_CHANNELS_1; + break; + case IL_LUMINANCE_ALPHA: + // encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_1 | ENC_LUM_0 | ENC_CHANNELS_2; + encoding = ENC_RED_0 | ENC_GREEN_0 | ENC_BLUE_0 | ENC_ALPHA_1 | ENC_LUM_NONE | ENC_CHANNELS_2; // Source RGB from L to expand to (L, L, L, A). + break; + default: + MY_ASSERT(!"Unsupported user pixel format."); + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_INVALID; // Error! Invalid encoding. + break; + } + + switch (type) + { + case IL_UNSIGNED_BYTE: + encoding |= ENC_TYPE_UNSIGNED_CHAR; + break; + case IL_UNSIGNED_SHORT: + encoding |= ENC_TYPE_UNSIGNED_SHORT; + break; + case IL_UNSIGNED_INT: + encoding |= ENC_TYPE_UNSIGNED_INT; + break; + case IL_BYTE: + encoding |= ENC_TYPE_CHAR; + break; + case IL_SHORT: + encoding |= ENC_TYPE_SHORT; + break; + case IL_INT: + encoding |= ENC_TYPE_INT; + break; + case IL_FLOAT: + encoding |= ENC_TYPE_FLOAT; + break; + default: + MY_ASSERT(!"Unsupported user data format."); + encoding |= ENC_INVALID; // Error! Invalid encoding. + break; + } + + return encoding; +} + +// For OpenGL interop these formats are supported by CUDA according to the current manual on cudaGraphicsGLRegisterImage: +// GL_RED, GL_RG, GL_RGBA, GL_LUMINANCE, GL_ALPHA, GL_LUMINANCE_ALPHA, GL_INTENSITY +// {GL_R, GL_RG, GL_RGBA} x {8, 16, 16F, 32F, 8UI, 16UI, 32UI, 8I, 16I, 32I} +// {GL_LUMINANCE, GL_ALPHA, GL_LUMINANCE_ALPHA, GL_INTENSITY} x {8, 16, 16F_ARB, 32F_ARB, 8UI_EXT, 16UI_EXT, 32UI_EXT, 8I_EXT, 16I_EXT, 32I_EXT} + +// The following mapping is done for host textures. RGB formats will be expanded to RGBA. + +// While single and dual channel textures can easily be uploaded, the texture doesn't know what the destination format actually is, +// that is, a LUMINANCE_ALPHA texture returns the luminance in the red channel and the alpha in the green channel. +// That doesn't work the same way as OpenGL which copies luminance to all three RGB channels automatically. +// DAR DEBUG Check how the tex*<>(obj, ...) templates react when asking for more data than in the texture. +static unsigned int determineEncodingDevice(int format, int type) // format and type are DevIL defines. +{ + unsigned int encoding; + + switch (format) + { + case IL_RGB: + case IL_BGR: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE; // (R, G, B, 1) + break; + case IL_RGBA: + case IL_BGRA: + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // (R, G, B, A) + break; + case IL_LUMINANCE: + //encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_0 | ENC_CHANNELS_1; // L in R + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE; // Expands to (L, L, L, 1) + break; + case IL_ALPHA: + //encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_0 | ENC_LUM_NONE | ENC_CHANNELS_1; // A in R + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // Expands to (0, 0, 0, A) + break; + case IL_LUMINANCE_ALPHA: + //encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_1 | ENC_LUM_0 | ENC_CHANNELS_2; // LA in RG + encoding = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4; // Expands to (L, L, L, A) + break; + default: + MY_ASSERT(!"Unsupported user pixel format."); + encoding = ENC_RED_NONE | ENC_GREEN_NONE | ENC_BLUE_NONE | ENC_ALPHA_NONE | ENC_LUM_NONE | ENC_INVALID; // Error! Invalid encoding. + break; + } + + switch (type) + { + case IL_BYTE: + encoding |= ENC_TYPE_CHAR | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_BYTE: + encoding |= ENC_TYPE_UNSIGNED_CHAR | ENC_FIXED_POINT; + break; + case IL_SHORT: + encoding |= ENC_TYPE_SHORT | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_SHORT: + encoding |= ENC_TYPE_UNSIGNED_SHORT | ENC_FIXED_POINT; + break; + case IL_INT: + encoding |= ENC_TYPE_INT | ENC_FIXED_POINT; + break; + case IL_UNSIGNED_INT: + encoding |= ENC_TYPE_UNSIGNED_INT | ENC_FIXED_POINT; + break; + case IL_FLOAT: + encoding |= ENC_TYPE_FLOAT; + break; + // FIXME Add IL_HALF for EXR images. Why are they loaded as IL_FLOAT (in DevIL 1.7.8)? + default: + MY_ASSERT(!"Unsupported user data format."); + encoding |= ENC_INVALID; // Error! Invalid encoding. + break; + } + + return encoding; +} + +// Helper function calculating the CUarray_format +static void determineFormatChannels(const unsigned int encodingDevice, CUarray_format& format, unsigned int& numChannels) +{ + const unsigned int type = encodingDevice & (ENC_MASK << ENC_TYPE_SHIFT); + + switch (type) + { + case ENC_TYPE_CHAR: + format = CU_AD_FORMAT_SIGNED_INT8; + break; + case ENC_TYPE_UNSIGNED_CHAR: + format = CU_AD_FORMAT_UNSIGNED_INT8; + break; + case ENC_TYPE_SHORT: + format = CU_AD_FORMAT_SIGNED_INT16; + break; + case ENC_TYPE_UNSIGNED_SHORT: + format = CU_AD_FORMAT_UNSIGNED_INT16; + break; + case ENC_TYPE_INT: + format = CU_AD_FORMAT_SIGNED_INT32; + break; + case ENC_TYPE_UNSIGNED_INT: + format = CU_AD_FORMAT_UNSIGNED_INT32; + break; + //case ENC_TYPE_HALF: // FIXME Implement. + // format = CU_AD_FORMAT_HALF; + // break; + case ENC_TYPE_FLOAT: + format = CU_AD_FORMAT_FLOAT; + break; + default: + MY_ASSERT(!"determineFormatChannels() Unexpected data type."); + break; + } + + numChannels = (encodingDevice >> ENC_CHANNELS_SHIFT) & ENC_MASK; +} + + +static unsigned int getElementSize(const unsigned int encodingDevice) +{ + unsigned int bytes = 0; + + const unsigned int type = encodingDevice & (ENC_MASK << ENC_TYPE_SHIFT); + switch (type) + { + case ENC_TYPE_CHAR: + case ENC_TYPE_UNSIGNED_CHAR: + bytes = 1; + break; + case ENC_TYPE_SHORT: + case ENC_TYPE_UNSIGNED_SHORT: + //case ENC_TYPE_HALF: // FIXME Implement. + bytes = 2; + break; + case ENC_TYPE_INT: + case ENC_TYPE_UNSIGNED_INT: + case ENC_TYPE_FLOAT: + bytes = 4; + break; + default: + MY_ASSERT(!"getElementSize() Unexpected data type."); + break; + } + + const unsigned int numChannels = (encodingDevice >> ENC_CHANNELS_SHIFT) & ENC_MASK; + + return bytes * numChannels; +} + + +// Texture format conversion routines. + +template +T getAlphaOne() +{ + return (std::numeric_limits::is_integer ? std::numeric_limits::max() : T(1)); +} + +// Fixed point adjustment for integer data D and S. +template +D adjust(S value) +{ + int dstBits = int(sizeof(D)) * 8; + int srcBits = int(sizeof(S)) * 8; + + D result = D(0); // Clear bits to allow OR operations. + + if (std::numeric_limits::is_signed) + { + if (std::numeric_limits::is_signed) + { + // D signed, S signed + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits)); + } + else + { + // Shift value into the most significant bits of result and replicate value into the lower bits until all are touched. + int shifts = dstBits - srcBits; + result = D(value << shifts); // This sets the destination sign bit as well. + value &= std::numeric_limits::max(); // Clear the sign bit inside the source value. + srcBits--; // Reduce the number of srcBits used to replicate the remaining data. + shifts -= srcBits; // Subtracting the now one smaller srcBits from shifts means the next shift will fill up with the remaining non-sign bits as intended. + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) // There can be one to three empty bits left blank in the result now. + { + result |= D(value >> -shifts); // Shift to the right to get the most significant bits of value into the least significant destination bits. + } + } + } + else + { + // D signed, S unsigned + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits + 1)); // + 1 because the destination is signed and the value needs to remain positive. + } + else + { + // Shift value into the most significant bits of result, keep the sign clear, and replicate value into the lower bits until all are touched. + int shifts = dstBits - srcBits - 1; // - 1 because the destination is signed and the value needs to remain positive. + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) + { + result |= D(value >> -shifts); + } + } + } + } + else + { + if (std::numeric_limits::is_signed) + { + // D unsigned, S signed + value = std::max(S(0), value); // Only the positive values will be transferred. + srcBits--; // Skip the sign bit. Means equal bit size won't happen here. + if (dstBits <= srcBits) // When it's really bigger it has at least 7 bits more, no need to care for dangling bits + { + result = D(value >> (srcBits - dstBits)); + } + else + { + int shifts = dstBits - srcBits; + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + if (shifts < 0) + { + result |= D(value >> -shifts); + } + } + } + else + { + // D unsigned, S unsigned + if (dstBits <= srcBits) + { + // More bits provided than needed. Use the most significant bits of value. + result = D(value >> (srcBits - dstBits)); + } + else + { + // Shift value into the most significant bits of result and replicate into the lower ones until all bits are touched. + int shifts = dstBits - srcBits; + while (0 <= shifts) + { + result |= D(value << shifts); + shifts -= srcBits; + } + // Both bit sizes are even multiples of 8, there are no trailing bits here. + MY_ASSERT(shifts == -srcBits); + } + } + } + return result; +} + + +template +void remapAdjust(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const S *psrc = reinterpret_cast(src); + D *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool fixedPoint = !!(dstEncoding & ENC_FIXED_POINT); + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) // Five possible channels: R, G, B, A, L + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + // If destination alpha was added to support this format or if no source data is given for alpha, fill it with 1. + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + if (s < 4) // There is data for this channel inside the source. (This could be a luminance to RGB mapping as well). + { + S value = psrc[s]; + pdst[d] = (fixedPoint) ? adjust(value) : D(value); + } + else // no value provided + { + pdst[d] = D(0); + } + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +// Straight channel copy with no adjustment. Since the data types match, fixed point doesn't matter. +template +void remapCopy(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const T *psrc = reinterpret_cast(src); + T *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) // Five possible channels: R, G, B, A, L + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + pdst[d] = (s < 4) ? psrc[s] : T(0); + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +template +void remapFromFloat(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const float *psrc = reinterpret_cast(src); + D *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool fixedPoint = !!(dstEncoding & ENC_FIXED_POINT); + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = getAlphaOne(); + } + else + { + if (s < 4) // This data channel exists inside the source. + { + float value = psrc[s]; + if (fixedPoint) + { + MY_ASSERT(std::numeric_limits::is_integer); // Destination with float format cannot be fixed point. + + float minimum = (std::numeric_limits::is_signed) ? -1.0f : 0.0f; + value = std::min(std::max(minimum, value), 1.0f); + pdst[d] = D(std::numeric_limits::max() * value); // Scaled copy. + } + else // element type, clamped copy. + { + float maximum = float(std::numeric_limits::max()); // This will run out of precision for int and unsigned int. + float minimum = -maximum; + pdst[d] = D(std::min(std::max(minimum, value), maximum)); + } + } + else // no value provided + { + pdst[d] = D(0); + } + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + +template +void remapToFloat(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count) +{ + const S *psrc = reinterpret_cast(src); + float *pdst = reinterpret_cast(dst); + unsigned int dstChannels = (dstEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + unsigned int srcChannels = (srcEncoding >> ENC_CHANNELS_SHIFT) & ENC_MASK; + bool alphaOne = !!(dstEncoding & ENC_ALPHA_ONE); + + while (count--) + { + unsigned int shift = ENC_RED_SHIFT; + for (unsigned int i = 0; i < 5; ++i, shift += 4) + { + unsigned int d = (dstEncoding >> shift) & ENC_MASK; + if (d < 4) // This data channel exists inside the destination. + { + unsigned int s = (srcEncoding >> shift) & ENC_MASK; + if (shift == ENC_ALPHA_SHIFT && (alphaOne || 4 <= s)) + { + pdst[d] = 1.0f; + } + else + { + // If there is data for this channel just cast it straight in. + // This will run out of precision for int and unsigned int source data. + pdst[d] = (s < 4) ? float(psrc[s]) : 0.0f; + } + } + } + pdst += dstChannels; + psrc += srcChannels; + } +} + + +typedef void (*PFNREMAP)(void *dst, unsigned int dstEncoding, const void *src, unsigned int srcEncoding, size_t count); + +// Function table with 49 texture format conversion routines from loaded image data to supported CUDA texture formats. +// Index is [destination type][source type] +PFNREMAP remappers[7][7] = +{ + { + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapAdjust, + remapFromFloat + }, + { + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapAdjust, + remapCopy, + remapFromFloat + }, + { + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapToFloat, + remapCopy + } +}; + + +// Finally the function which converts any loaded image into a texture format supported by CUDA (1, 2, 4 channels only). +static void convert(void *dst, unsigned int encodingDevice, const void *src, unsigned int hostEncoding, size_t elements) +{ + // Only destination encoding knows about the fixed-point encoding. For straight data memcpy() cases that is irrelevant. + // FIXME PERF Avoid this conversion altogether when it's just a memcpy(). + if ((encodingDevice & ~ENC_FIXED_POINT) == hostEncoding) + { + memcpy(dst, src, elements * getElementSize(encodingDevice)); // The fastest path. + } + else + { + unsigned int dstType = (encodingDevice >> ENC_TYPE_SHIFT) & ENC_MASK; + unsigned int srcType = (hostEncoding >> ENC_TYPE_SHIFT) & ENC_MASK; + MY_ASSERT(dstType < 7 && srcType < 7); + + PFNREMAP pfn = remappers[dstType][srcType]; + + (*pfn)(dst, encodingDevice, src, hostEncoding, elements); + } +} + +Texture::Texture(Device* device) +: m_owner(device) +, m_width(0) +, m_height(0) +, m_depth(0) +, m_flags(0) +, m_encodingHost(ENC_INVALID) +, m_encodingDevice(ENC_INVALID) +, m_sizeBytesPerElement(0) +, m_textureObject(0) +, m_d_array(0) +, m_d_mipmappedArray(0) +, m_sizeBytesArray(0) +, m_d_envCDF_U(0) +, m_d_envCDF_V(0) +, m_integral(1.0f) +{ + m_descArray3D.Width = 0; + m_descArray3D.Height = 0; + m_descArray3D.Depth = 0; + m_descArray3D.Format = CU_AD_FORMAT_UNSIGNED_INT8; + m_descArray3D.NumChannels = 0; + m_descArray3D.Flags = 0; // Things like CUDA_ARRAY3D_SURFACE_LDST, ... + + // Clear the resource description once. They will be set inside the individual Texture::create*() functions. + memset(&m_resourceDescription, 0, sizeof(CUDA_RESOURCE_DESC)); + + // Note that cuTexObjectCreate() fails if the "int reserved[12]" data is not zero! Not mentioned in the CUDA documentation. + memset(&m_textureDescription, 0, sizeof(CUDA_TEXTURE_DESC)); + + // Setup CUDA_TEXTURE_DESC defaults. + // The developer can override these at will before calling Texture::create(). Afterwards they are immutable + // If the flag CU_TRSF_NORMALIZED_COORDINATES is not set, the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP. + m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_WRAP; + + m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + + // Possible flags: CU_TRSF_READ_AS_INTEGER, CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_SRGB + m_textureDescription.flags = CU_TRSF_NORMALIZED_COORDINATES; + + m_textureDescription.maxAnisotropy = 1; + + // LOD 0 only by default. + // This means when using mipmaps it's the developer's responsibility to set at least + // maxMipmapLevelClamp > 0.0f before calling Texture::create() to make sure mipmaps can be sampled! + m_textureDescription.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + m_textureDescription.mipmapLevelBias = 0.0f; + m_textureDescription.minMipmapLevelClamp = 0.0f; + m_textureDescription.maxMipmapLevelClamp = 0.0f; // This should be set to Picture::getNumberOfLevels() when using mipmaps. + + m_textureDescription.borderColor[0] = 0.0f; + m_textureDescription.borderColor[1] = 0.0f; + m_textureDescription.borderColor[2] = 0.0f; + m_textureDescription.borderColor[3] = 0.0f; +} + +Texture::~Texture() +{ + // Note that destroy() handles the shared data destruction. + if (m_textureObject) + { + CU_CHECK_NO_THROW( cuTexObjectDestroy(m_textureObject) ); + } +} + +size_t Texture::destroy(Device* device) +{ + if (m_owner == device) + { + if (m_d_array) + { + CU_CHECK_NO_THROW( cuArrayDestroy(m_d_array) ); + } + if (m_d_mipmappedArray) + { + CU_CHECK_NO_THROW( cuMipmappedArrayDestroy(m_d_mipmappedArray) ); + } + + // DAR FIXME Move these into a derived TextureEnvironment class. + if (m_d_envCDF_U) + { + m_owner->memFree(m_d_envCDF_U); + } + if (m_d_envCDF_V) + { + m_owner->memFree(m_d_envCDF_V); + } + + return m_sizeBytesArray; // Return the size of the CUarray or CUmipmappedArray data in bytes for memory tracking. + } + + return 0; +} + +// For all functions changing the m_textureDescription values, +// make sure they are called before the texture object has been created, +// otherwise the texture object would need to be recreated. + +// Set the whole description. +void Texture::setTextureDescription(const CUDA_TEXTURE_DESC& descr) +{ + m_textureDescription = descr; +} + +void Texture::setAddressMode(CUaddress_mode s, CUaddress_mode t, CUaddress_mode r) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.addressMode[0] = s; + m_textureDescription.addressMode[1] = t; + m_textureDescription.addressMode[2] = r; +} + +void Texture::setFilterMode(CUfilter_mode filter, CUfilter_mode filterMipmap) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.filterMode = filter; + m_textureDescription.mipmapFilterMode = filterMipmap; +} + +void Texture::setBorderColor(float r, float g, float b, float a) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.borderColor[0] = r; + m_textureDescription.borderColor[1] = g; + m_textureDescription.borderColor[2] = b; + m_textureDescription.borderColor[3] = a; +} + +void Texture::setMaxAnisotropy(unsigned int aniso) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.maxAnisotropy = aniso; +} + +void Texture::setMipmapLevelBiasMinMax(float bias, float minimum, float maximum) +{ + MY_ASSERT(m_textureObject == 0); + + m_textureDescription.mipmapLevelBias = bias; + m_textureDescription.minMipmapLevelClamp = minimum; + m_textureDescription.maxMipmapLevelClamp = maximum; +} + +void Texture::setReadMode(bool asInteger) +{ + MY_ASSERT(m_textureObject == 0); + + if (asInteger) + { + m_textureDescription.flags |= CU_TRSF_READ_AS_INTEGER; + } + else + { + m_textureDescription.flags &= ~CU_TRSF_READ_AS_INTEGER; + } +} + +void Texture::setSRGB(bool srgb) +{ + MY_ASSERT(m_textureObject == 0); + + if (srgb) + { + m_textureDescription.flags |= CU_TRSF_SRGB; + } + else + { + m_textureDescription.flags &= ~CU_TRSF_SRGB; + } +} + +void Texture::setNormalizedCoords(bool normalized) +{ + MY_ASSERT(m_textureObject == 0); + + if (normalized) + { + m_textureDescription.flags |= CU_TRSF_NORMALIZED_COORDINATES; // Default in this app. + } + else + { + // Note that if the flag CU_TRSF_NORMALIZED_COORDINATES is not set, + // the only supported address mode is CU_TR_ADDRESS_MODE_CLAMP! + m_textureDescription.flags &= ~CU_TRSF_NORMALIZED_COORDINATES; + } +} + + +bool Texture::create1D(const Picture* picture) +{ + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = 0; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Depth = m_depth; // Mind that the layers are always defined via the depth extent. + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + // I don't know of a program which creates image files with 1D layered mipmapped textures, but the Picture class handles that just fine. + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 1D (layered) mipmapped texture. + { + // A 1D mipmapped array is allocated if Height and Depth extents are both zero. + // A 1D layered CUDA mipmapped array is allocated if only Height is zero and the CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 1D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 1D (layered) texture. + { + // A 1D array is allocated if the height and depth extents are both zero. + // A 1D layered CUDA array is allocated if only the height extent is zero and the cudaArrayLayered flag is set. + // Each layer is a 1D array. The number of layers is determined by the depth extent. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +bool Texture::create2D(const Picture* picture) +{ + // Default initialization for a 2D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Depth = m_depth; // Mind that the layers are always defined via the depth extent. + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 2D (layered) mipmapped texture // FIXME Add a mechanism to generate mipmaps if there are none. + { + // A 2D mipmapped array is allocated if only Depth extent is zero. + // A 2D layered CUDA mipmapped array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 2D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 2D (layered) texture. + { + // A 2D array is allocated if only Depth extent is zero. + // A 2D layered CUDA array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_LAYERED flag is set. + // Each layer is a 2D array. The number of layers is determined by the Depth extent. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::create3D(const Picture* picture) +{ + MY_ASSERT((m_flags & IMAGE_FLAG_LAYER) == 0); // There are no layered 3D textures. The flag is ignored. + + // Default initialization for a 3D texture. There are no layers! + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = m_depth; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 3D mipmapped texture + { + // A 3D mipmapped array is allocated if all three extents are non-zero. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // 3D texture. + { + // A 3D array is allocated if all three extents are non-zero. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_height * m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytes; // Memory tracking. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::createCube(const Picture* picture) +{ + if (!picture->isCubemap()) // This implies picture->getNumberOfImages() == 6. + { + std::cerr << "ERROR: Texture::createCube() picture is not a cubemap.\n"; + return false; + } + + if (m_width != m_height || m_depth % 6 != 0) + { + std::cerr << "ERROR: Texture::createCube() invalid cubemap image dimensions (" << m_width << ", " << m_height << ", " << m_depth << ")\n"; + return false; + } + + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = m_depth; // depth == 6 * layers. + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = CUDA_ARRAY3D_CUBEMAP; + + const unsigned int numLayers = m_depth / 6; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags |= CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; // LOD 0 size in bytes. + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // cubemap (layered) mipmapped texture + { + // A cubemap CUDA mipmapped array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_CUBEMAP flag is set. + // Width must be equal to \p Height, and Depth must be six. + // A cubemap is a special type of 2D layered CUDA array, where the six layers represent the six faces of a cube. + // The order of the six layers in memory is the same as that listed in ::CUarray_cubemap_face. (+x, -x, +y, -y, +z, -z) + // A cubemap layered CUDA mipmapped array is allocated if all three extents are non-zero, and both, ::CUDA_ARRAY3D_CUBEMAP and ::CUDA_ARRAY3D_LAYERED flags are set. + // Width must be equal to Height, and Depth must be a multiple of six. + // A cubemap layered CUDA array is a special type of 2D layered CUDA array that consists of a collection of cubemaps. + // The first six layers represent the first cubemap, the next six layers form the second cubemap, and so on. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + const Image* image; // The last image of each level defines the extent. + + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + for (unsigned int face = 0; face < 6; ++face) + { + image = picture->getImageLevel(face, level); // image face, LOD level + + const size_t sizeElementsLayer = image->m_width * image->m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytesLayer * numLayers; // Memory tracking. + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + m_resourceDescription.resType = CU_RESOURCE_TYPE_MIPMAPPED_ARRAY; + m_resourceDescription.res.mipmap.hMipmappedArray = m_d_mipmappedArray; + } + else // cubemap (layered) texture. + { + // A cubemap CUDA array is allocated if all three extents are non-zero and the ::CUDA_ARRAY3D_CUBEMAP flag is set. + // Width must be equal to Height, and Depth must be six. + // A cubemap is a special type of 2D layered CUDA array, where the six layers represent the six faces of a cube. + // The order of the six layers in memory is the same as that listed in ::CUarray_cubemap_face. (+x, -x, +y, -y, +z, -z) + // A cubemap layered CUDA array is allocated if all three extents are non-zero, and both, ::CUDA_ARRAY3D_CUBEMAP and ::CUDA_ARRAY3D_LAYERED flags are set. + // Width must be equal to Height, and Depth must be a multiple of six. + // A cubemap layered CUDA array is a special type of 2D layered CUDA array that consists of a collection of cubemaps. + // The first six layers represent the first cubemap, the next six layers form the second cubemap, and so on. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + for (unsigned int face = 0; face < 6; ++face) + { + const Image* image = picture->getImageLevel(face, 0); // image face, LOD 0 + + const size_t sizeElementsLayer = m_width * m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeBytesLayer * numLayers; // Memory tracking. + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + } + + delete[] data; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +bool Texture::createEnv(const Picture* picture) +{ + // Default initialization for a 1D texture without layers. + m_descArray3D.Width = m_width; + m_descArray3D.Height = m_height; + m_descArray3D.Depth = 0; + determineFormatChannels(m_encodingDevice, m_descArray3D.Format, m_descArray3D.NumChannels); + m_descArray3D.Flags = 0; + + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + //size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + m_sizeBytesArray += sizeElements * m_sizeBytesPerElement; // Memory tracking. + + float* data = new float[sizeElements * 4]; // RGBA32F + + // A 2D array is allocated if only Depth extent is zero. + CU_CHECK( cuArray3DCreate(&m_d_array, &m_descArray3D) ); + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + + // Generate the CDFs for direct environment lighting and the environment texture sampler itself. + calculateSphericalCDF(data); + + delete[] data; + + // Setup CUDA_TEXTURE_DESC for the spherical environment. + // The defaults are set for a bilinear filtered 2D texture already. + // The spherical environment texture only needs to change the addessmode[1] to clamp. + //m_textureDescription.addressMode[0] = CU_TR_ADDRESS_MODE_WRAP; + m_textureDescription.addressMode[1] = CU_TR_ADDRESS_MODE_CLAMP; + //m_textureDescription.addressMode[2] = CU_TR_ADDRESS_MODE_WRAP; + + //m_textureDescription.filterMode = CU_TR_FILTER_MODE_LINEAR; // Bilinear filtering by default. + + //m_textureDescription.flags = CU_TRSF_NORMALIZED_COORDINATES; + + //m_textureDescription.maxAnisotropy = 1; + + //m_textureDescription.mipmapFilterMode = CU_TR_FILTER_MODE_POINT; + //m_textureDescription.mipmapLevelBias = 0.0f; + //m_textureDescription.minMipmapLevelClamp = 0.0f; + //m_textureDescription.maxMipmapLevelClamp = 0.0f; // This should be set to Picture::getNumberOfLevels() when using mipmaps. + + //m_textureDescription.borderColor[0] = 0.0f; + //m_textureDescription.borderColor[1] = 0.0f; + //m_textureDescription.borderColor[2] = 0.0f; + //m_textureDescription.borderColor[3] = 0.0f; + + m_textureObject = 0; + + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + +// The Texture::create() functions set up all immutable members. +// The Texture::update() functions expect the exact same input and only upload new CUDA array data. +bool Texture::create(const Picture* picture, const unsigned int flags) +{ + bool success = false; + + if (m_textureObject != 0) + { + std::cerr << "ERROR: Texture::create() texture object already created.\n"; + return success; + } + + if (picture == nullptr) + { + std::cerr << "ERROR: Texture::create() called with nullptr picture.\n"; + return success; + } + + // The LOD 0 image of the first face defines the basic settings, including the texture m_width, m_height, m_depth. + // This returns nullptr when this image doesn't exist. Everything else in this function relies on it. + const Image* image = picture->getImageLevel(0, 0); + + if (image == nullptr) + { + std::cerr << "ERROR: Texture::create() Picture doesn't contain image 0 level 0.\n"; + return success; + } + + // Precalculate some values which are required for all create*() functions. + m_encodingHost = determineHostEncoding(image->m_format, image->m_type); + + m_flags = flags; + + if (m_flags & IMAGE_FLAG_ENV) + { + // Hardcode the device encoding to a floating point HDR image. The input is expected to be an HDR or EXR image. + // Fixed point LDR textures will remain unnormalized, e.g. unsigned byte 255 will be converted to float 255.0f. + // (Just because there is no suitable conversion routine implemented for that.) + m_encodingDevice = ENC_RED_0 | ENC_GREEN_1 | ENC_BLUE_2 | ENC_ALPHA_3 | ENC_LUM_NONE | ENC_CHANNELS_4 | ENC_ALPHA_ONE | ENC_TYPE_FLOAT; + } + else + { + m_encodingDevice = determineEncodingDevice(image->m_format, image->m_type); + } + + if ((m_encodingHost | m_encodingDevice) & ENC_INVALID) // If either of the encodings is invalid, bail out. + { + return false; + } + + m_sizeBytesPerElement = getElementSize(m_encodingDevice); + + if (m_flags & IMAGE_FLAG_1D) + { + m_width = image->m_width; + m_height = 1; + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth : 1; + success = create1D(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == IMAGE_FLAG_2D) // Standard 2D texture. + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth : 1; + success = create2D(picture); + } + else if (m_flags & IMAGE_FLAG_3D) + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = image->m_depth; + success = create3D(picture); + } + else if (m_flags & IMAGE_FLAG_CUBE) + { + m_width = image->m_width; + m_height = image->m_height; + // Note that the Picture class holds the six cubemap faces in six separate mipmap chains! + // The LOD 0 image depth is the number of layers. The resulting 3D image is six times that depth. + m_depth = (m_flags & IMAGE_FLAG_LAYER) ? image->m_depth * 6 : 6; + success = createCube(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) // 2D spherical environment texture. + { + m_width = image->m_width; + m_height = image->m_height; + m_depth = 1; // No layers for the environment map. + success = createEnv(picture); + } + + MY_ASSERT(success); + return success; +} + + +// Texture peer-to-peer sharing! +// Note that the m_owner field indicates which device originally allocated the memory! +// Use all other data of the shared texture on the peer device. Only the m_textureObject is per device! +bool Texture::create(const Texture* shared) +{ + // Copy all elements from the existing shared texture. + // This includes the owner which is the peer device, not the currently active one! + *this = *shared; + + m_textureObject = 0; // Clear the copied texture object. + + // Only create the m_textureObject per Device by using the resource and texture descriptions + // just copied from the shared texture which contains the device pointers to the shared array data. + CU_CHECK( cuTexObjectCreate(&m_textureObject, &m_resourceDescription, &m_textureDescription, nullptr) ); + + return (m_textureObject != 0); +} + + +Device* Texture::getOwner() const +{ + return m_owner; +} + +unsigned int Texture::getWidth() const +{ + return m_width; +} + +unsigned int Texture::getHeight() const +{ + return m_height; +} + +unsigned int Texture::getDepth() const +{ + return m_depth; +} + +cudaTextureObject_t Texture::getTextureObject() const +{ + return m_textureObject; +} + +size_t Texture::getSizeBytes() const +{ + return m_sizeBytesArray; +} + +// The following functions are used to build the data needed for an importance sampled spherical HDR environment map. + +// Implement a simple Gaussian 3x3 filter with sigma = 0.5 +// Needed for the CDF generation of the importance sampled HDR environment texture light. +static float gaussianFilter(const float* rgba, unsigned int width, unsigned int height, unsigned int x, unsigned int y) +{ + // Lookup is repeated in x and clamped to edge in y. + unsigned int left = (0 < x) ? x - 1 : width - 1; // repeat + unsigned int right = (x < width - 1) ? x + 1 : 0; // repeat + unsigned int bottom = (0 < y) ? y - 1 : y; // clamp + unsigned int top = (y < height - 1) ? y + 1 : y; // clamp + + // Center + const float *p = rgba + (width * y + x) * 4; + float intensity = (p[0] + p[1] + p[2]) * 0.619347f; + + // 4-neighbours + p = rgba + (width * bottom + x) * 4; + float f = p[0] + p[1] + p[2]; + p = rgba + (width * y + left) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * y + right) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + x) * 4; + f += p[0] + p[1] + p[2]; + intensity += f * 0.0838195f; + + // 8-neighbours corners + p = rgba + (width * bottom + left) * 4; + f = p[0] + p[1] + p[2]; + p = rgba + (width * bottom + right) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + left) * 4; + f += p[0] + p[1] + p[2]; + p = rgba + (width * top + right) * 4; + f += p[0] + p[1] + p[2]; + intensity += f * 0.0113437f; + + return intensity / 3.0f; +} + +// Create cumulative distribution function for importance sampling of spherical environment lights. +// This is a textbook implementation for the CDF generation of a spherical HDR environment. +// See "Physically Based Rendering" v2, chapter 14.6.5 on Infinite Area Lights. +void Texture::calculateSphericalCDF(const float* rgba) +{ + // The original data needs to be retained to calculate the PDF. + float *funcU = new float[m_width * m_height]; + float *funcV = new float[m_height + 1]; + + float sum = 0.0f; + // First generate the function data. + for (unsigned int y = 0; y < m_height; ++y) + { + // Scale distibution by the sine to get the sampling uniform. (Avoid sampling more values near the poles.) + // See Physically Based Rendering v2, chapter 14.6.5 on Infinite Area Lights, page 728. + float sinTheta = float(sin(M_PI * (double(y) + 0.5) / double(m_height))); // Make this as accurate as possible. + + for (unsigned int x = 0; x < m_width; ++x) + { + // Filter to keep the piecewise linear function intact for samples with zero value next to non-zero values. + const float value = gaussianFilter(rgba, m_width, m_height, x, y); + funcU[y * m_width + x] = value * sinTheta; + + // Compute integral over the actual function. + const float *p = rgba + (y * m_width + x) * 4; + const float intensity = (p[0] + p[1] + p[2]) / 3.0f; + sum += intensity * sinTheta; + } + } + + // This integral is used inside the light sampling function (see sysData.envIntegral). + m_integral = sum * 2.0f * M_PIf * M_PIf / float(m_width * m_height); + + // Now generate the CDF data. + // Normalized 1D distributions in the rows of the 2D buffer, and the marginal CDF in the 1D buffer. + // Include the starting 0.0f and the ending 1.0f to avoid special cases during the continuous sampling. + float *cdfU = new float[(m_width + 1) * m_height]; + float *cdfV = new float[m_height + 1]; + + for (unsigned int y = 0; y < m_height; ++y) + { + unsigned int row = y * (m_width + 1); // Watch the stride! + cdfU[row + 0] = 0.0f; // CDF starts at 0.0f. + + for (unsigned int x = 1; x <= m_width; ++x) + { + unsigned int i = row + x; + cdfU[i] = cdfU[i - 1] + funcU[y * m_width + x - 1]; // Attention, funcU is only m_width wide! + } + + const float integral = cdfU[row + m_width]; // The integral over this row is in the last element. + funcV[y] = integral; // Store this as function values of the marginal CDF. + + if (integral != 0.0f) + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] /= integral; + } + } + else // All texels were black in this row. Generate an equal distribution. + { + for (unsigned int x = 1; x <= m_width; ++x) + { + cdfU[row + x] = float(x) / float(m_width); + } + } + } + + // Now do the same thing with the marginal CDF. + cdfV[0] = 0.0f; // CDF starts at 0.0f. + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = cdfV[y - 1] + funcV[y - 1]; + } + + const float integral = cdfV[m_height]; // The integral over this marginal CDF is in the last element. + funcV[m_height] = integral; // For completeness, actually unused. + + if (integral != 0.0f) + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] /= integral; + } + } + else // All texels were black in the whole image. Seriously? :-) Generate an equal distribution. + { + for (unsigned int y = 1; y <= m_height; ++y) + { + cdfV[y] = float(y) / float(m_height); + } + } + + // Upload the CDFs into CUDA buffers. + size_t sizeBytes = (m_width + 1) * m_height * sizeof(float); + m_d_envCDF_U = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_envCDF_U, cdfU, sizeBytes) ); + + sizeBytes = (m_height + 1) * sizeof(float); + m_d_envCDF_V = m_owner->memAlloc(sizeBytes, sizeof(float)); + CU_CHECK( cuMemcpyHtoD(m_d_envCDF_V, cdfV, sizeBytes) ); + + delete[] cdfV; + delete[] cdfU; + + delete[] funcV; + delete[] funcU; +} + +CUdeviceptr Texture::getCDF_U() const +{ + return m_d_envCDF_U; +} + +CUdeviceptr Texture::getCDF_V() const +{ + return m_d_envCDF_V; +} + +float Texture::getIntegral() const +{ + // This is the sum of the piecewise linear function values (roughly the texels' intensity) divided by the number of texels m_width * m_height. + return m_integral; +} + + +bool Texture::update1D(const Picture* picture) +{ + size_t sizeElements = m_width; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 1D (layered) mipmapped texture. + { + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 1D (layered) texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = 1; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = 1; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + + +bool Texture::update2D(const Picture* picture) +{ + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags = CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + sizeElements *= m_depth; // The size for the LOD 0 with layers in elements. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 2D (layered) mipmapped texture // FIXME Add a mechanism to generate mipmaps if there are none. + { + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 2D (layered) texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only + + sizeElements = m_width * m_height * m_depth; + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::update3D(const Picture* picture) +{ + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // 3D mipmapped texture + { + // A 3D mipmapped array is allocated if all three extents are non-zero. + CU_CHECK( cuMipmappedArrayCreate(&m_d_mipmappedArray, &m_descArray3D, numLevels) ); + + for (unsigned int level = 0; level < numLevels; ++level) + { + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + const Image* image = picture->getImageLevel(0, level); // Get the image 0 LOD level. + + sizeElements = image->m_width * image->m_height * image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = image->m_depth; // Really the image->m_depth here, no layers in 3D textures. + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // 3D texture. + { + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + sizeElements = m_width * m_height * m_depth; // Really the image->m_depth here, no layers in 3D textures. + sizeBytes = sizeElements * m_sizeBytesPerElement; + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::updateCube(const Picture* picture) +{ + if (!picture->isCubemap()) // isCubemap() implies picture->getNumberOfImages() == 6. + { + std::cerr << "ERROR: Texture::updateCube() picture is not a cubemap.\n"; + return false; + } + + if (m_width != m_height || m_depth % 6 != 0) + { + std::cerr << "ERROR: Texture::updateCube() invalid cubemap image dimensions (" << m_width << ", " << m_height << ", " << m_depth << ")\n"; + return false; + } + + const unsigned int numLayers = m_depth / 6; + + size_t sizeElements = m_width * m_height * m_depth; // The size for the LOD 0 in elements. + + if (m_flags & IMAGE_FLAG_LAYER) + { + m_descArray3D.Flags |= CUDA_ARRAY3D_LAYERED; // Set the array allocation flag. + } + + size_t sizeBytes = sizeElements * m_sizeBytesPerElement; // LOD 0 size in bytes. + + unsigned char* data = new unsigned char[sizeBytes]; // Allocate enough scratch memory for the conversion to hold the biggest LOD. + + const unsigned int numLevels = picture->getNumberOfLevels(0); // This is the number of mipmap levels including LOD 0. + + if (1 < numLevels && (m_flags & IMAGE_FLAG_MIPMAP)) // cubemap (layered) mipmapped texture + { + for (unsigned int level = 0; level < numLevels; ++level) + { + const Image* image; // The last image of each level defines the extent. + + CUarray d_levelArray; + + CU_CHECK( cuMipmappedArrayGetLevel(&d_levelArray, m_d_mipmappedArray, level) ); + + for (unsigned int face = 0; face < 6; ++face) + { + image = picture->getImageLevel(face, level); // image face, LOD level + + const size_t sizeElementsLayer = image->m_width * image->m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = image->m_width * m_sizeBytesPerElement; + params.srcHeight = image->m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = d_levelArray; + + params.WidthInBytes = params.srcPitch; + params.Height = image->m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + } + else // cubemap (layered) texture. + { + for (unsigned int face = 0; face < 6; ++face) + { + const Image* image = picture->getImageLevel(face, 0); // image face, LOD 0 + + const size_t sizeElementsLayer = m_width * m_height; + const size_t sizeBytesLayer = sizeElementsLayer * m_sizeBytesPerElement; + + for (unsigned int layer = 0; layer < numLayers; ++layer) + { + // Place the cubemap faces in blocks of 6 times number of layers. Each 6 slices are one cubemap layer. + void* src = image->m_pixels + sizeBytesLayer * layer; + void* dst = data + sizeBytesLayer * (layer * 6 + face); + + convert(dst, m_encodingDevice, src, m_encodingHost, sizeElementsLayer); + } + } + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + } + + delete[] data; + + return (m_textureObject != 0); +} + +bool Texture::updateEnv(const Picture* picture) +{ + size_t sizeElements = m_width * m_height; // The size for the LOD 0 in elements. + //size_t sizeBytes = sizeElements * m_sizeBytesPerElement; + + float* data = new float[sizeElements * 4]; // RGBA32F + + const Image* image = picture->getImageLevel(0, 0); // LOD 0 only. + + convert(data, m_encodingDevice, image->m_pixels, m_encodingHost, sizeElements); + + CUDA_MEMCPY3D params = {}; + + params.srcMemoryType = CU_MEMORYTYPE_HOST; + params.srcHost = data; + params.srcPitch = m_width * m_sizeBytesPerElement; + params.srcHeight = m_height; + + params.dstMemoryType = CU_MEMORYTYPE_ARRAY; + params.dstArray = m_d_array; + + params.WidthInBytes = params.srcPitch; + params.Height = m_height; + params.Depth = m_depth; + + CU_CHECK( cuMemcpy3D(¶ms) ); + + m_resourceDescription.resType = CU_RESOURCE_TYPE_ARRAY; + m_resourceDescription.res.array.hArray = m_d_array; + + // Generate the CDFs for direct environment lighting and the environment texture sampler itself. + calculateSphericalCDF(data); + + delete[] data; + + return (m_textureObject != 0); +} + + +bool Texture::update(const Picture* picture) +{ + bool success = false; + + if (m_textureObject == 0) + { + std::cerr << "ERROR: Texture::update() texture object not reated.\n"; + return success; + } + + if (picture == nullptr) + { + std::cerr << "ERROR: Texture::update() called with nullptr picture.\n"; + return success; + } + + // The LOD 0 image of the first face defines the basic settings, including the texture m_width, m_height, m_depth. + // This returns nullptr when this image doesn't exist. Everything else in this function relies on it. + const Image* image = picture->getImageLevel(0, 0); + + if (image == nullptr) + { + std::cerr << "ERROR: Texture::update() Picture doesn't contain image 0 level 0.\n"; + return success; + } + + const unsigned int hostEncoding = determineHostEncoding(image->m_format, image->m_type); + + if (m_encodingHost != hostEncoding) + { + std::cerr << "ERROR: Texture::update() Picture host encoding doesn't match existing texture\n"; + return success; + } + + if (m_flags & IMAGE_FLAG_1D) + { + success = update1D(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == IMAGE_FLAG_2D) // Standard 2D texture. + { + success = update2D(picture); + } + else if (m_flags & IMAGE_FLAG_3D) + { + success = update3D(picture); + } + else if (m_flags & IMAGE_FLAG_CUBE) + { + success = updateCube(picture); + } + else if ((m_flags & (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) == (IMAGE_FLAG_2D | IMAGE_FLAG_ENV)) // 2D spherical environment texture. + { + success = updateEnv(picture); + } + + MY_ASSERT(success); + return success; +} diff --git a/apps/bench_shared_offscreen/src/Timer.cpp b/apps/bench_shared_offscreen/src/Timer.cpp new file mode 100644 index 00000000..2a2c0451 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Timer.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2011-2019, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + + +// This code is part of the NVIDIA nvpro-pipeline https://github.com/nvpro-pipeline/pipeline + +#include "inc/Timer.h" + +#if defined(_WIN32) +# define GETTIME(x) QueryPerformanceCounter(x) +#else +# define GETTIME(x) gettimeofday( x, 0 ) +#endif + +Timer::Timer() + : m_running(false) + , m_seconds(0) +{ +#if defined(_WIN32) + QueryPerformanceFrequency(&m_freq); +#endif +} + +Timer::~Timer() +{ +} + +void Timer::start() +{ + if( !m_running ) + { + m_running = true; + // starting a timer: store starting time last + GETTIME( &m_begin ); + } +} + +void Timer::stop() +{ + // stopping a timer: store stopping time first + Time tmp; + GETTIME( &tmp ); + if( m_running ) + { + m_seconds += calcDuration(m_begin, tmp); + m_running = false; + } +} + +void Timer::reset() +{ + m_running = false; + m_seconds = 0; +} + +void Timer::restart() +{ + reset(); + start(); +} + +double Timer::getTime() const +{ + Time tmp; + GETTIME( &tmp ); + if( m_running ) + { + return m_seconds + calcDuration(m_begin, tmp); + } + else + { + return m_seconds; + } +} + +double Timer::calcDuration(Time begin, Time end) const +{ + double seconds; +#if defined(_WIN32) + LARGE_INTEGER diff; + diff.QuadPart = (end.QuadPart - begin.QuadPart); + seconds = (double)diff.QuadPart / (double)m_freq.QuadPart; +#else + timeval diff; + if( begin.tv_usec <= end.tv_usec ) + { + diff.tv_sec = end.tv_sec - begin.tv_sec; + diff.tv_usec = end.tv_usec - begin.tv_usec; + } + else + { + diff.tv_sec = end.tv_sec - begin.tv_sec - 1; + diff.tv_usec = end.tv_usec - begin.tv_usec + (int)1e6; + } + seconds = diff.tv_sec + diff.tv_usec/1e6; +#endif + return seconds; +} diff --git a/apps/bench_shared_offscreen/src/Torus.cpp b/apps/bench_shared_offscreen/src/Torus.cpp new file mode 100644 index 00000000..d92b21a9 --- /dev/null +++ b/apps/bench_shared_offscreen/src/Torus.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "inc/SceneGraph.h" +#include "inc/MyAssert.h" + +#include "shaders/vector_math.h" + +namespace sg +{ + + // The torus is a ring with radius outerRadius rotated around the y-axis along the circle with innerRadius. + /* +y + ___ | ___ + / \ / \ + | | | | | + | | | | + \ ___ / | \ ___ / + <---> + outerRadius + <-------> + innerRadius + */ + void Triangles::createTorus(const unsigned int tessU, const unsigned int tessV, const float innerRadius, const float outerRadius) + { + MY_ASSERT(3 <= tessU && 3 <= tessV); + + m_attributes.clear(); + m_indices.clear(); + + m_attributes.reserve((tessU + 1) * (tessV + 1)); + m_indices.reserve(8 * tessU * tessV); + + const float u = (float) tessU; + const float v = (float) tessV; + + float phi_step = 2.0f * M_PIf / u; + float theta_step = 2.0f * M_PIf / v; + + // Setup vertices and normals. + // Generate the torus exactly like the sphere with rings around the origin along the latitudes. + for (unsigned int latitude = 0; latitude <= tessV; ++latitude) // theta angle + { + const float theta = (float) latitude * theta_step; + const float sinTheta = sinf(theta); + const float cosTheta = cosf(theta); + + const float radius = innerRadius + outerRadius * cosTheta; + + for (unsigned int longitude = 0; longitude <= tessU; ++longitude) // phi angle + { + const float phi = (float) longitude * phi_step; + const float sinPhi = sinf(phi); + const float cosPhi = cosf(phi); + + TriangleAttributes attrib; + + attrib.vertex = make_float3(radius * cosPhi, outerRadius * sinTheta, radius * -sinPhi); + attrib.tangent = make_float3(-sinPhi, 0.0f, -cosPhi); + attrib.normal = make_float3(cosPhi * cosTheta, sinTheta, -sinPhi * cosTheta); + attrib.texcoord = make_float3((float) longitude / u, (float) latitude / v, 0.0f); + + m_attributes.push_back(attrib); + } + } + + // We have generated tessU + 1 vertices per latitude. + const int columns = tessU + 1; + + // Setup m_indices + for (unsigned int latitude = 0; latitude < tessV; ++latitude) + { + for (unsigned int longitude = 0; longitude < tessU; ++longitude) + { + m_indices.push_back(latitude * columns + longitude); // lower left + m_indices.push_back(latitude * columns + longitude + 1); // lower right + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + + m_indices.push_back((latitude + 1) * columns + longitude + 1); // upper right + m_indices.push_back((latitude + 1) * columns + longitude); // upper left + m_indices.push_back(latitude * columns + longitude); // lower left + } + } + } + +} // namespace sg diff --git a/apps/bench_shared_offscreen/src/main.cpp b/apps/bench_shared_offscreen/src/main.cpp new file mode 100644 index 00000000..a6352c06 --- /dev/null +++ b/apps/bench_shared_offscreen/src/main.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013-2020, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 ``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. + */ + +#include "shaders/config.h" + +#include "inc/Application.h" + +#include + +#include +#include + + +static Application* g_app = nullptr; + +//static void callbackError(int error, const char* description) +//{ +// std::cerr << "ERROR: "<< error << ": " << description << '\n'; +//} + + +static int runApp(const Options& options) +{ + int width = std::max(1, options.getWidth()); + int height = std::max(1, options.getHeight()); + + //GLFWwindow* window = glfwCreateWindow(width, height, "bench_shared_offscreen - Copyright (c) 2021 NVIDIA Corporation", NULL, NULL); + //if (!window) + //{ + // callbackError(APP_ERROR_CREATE_WINDOW, "glfwCreateWindow() failed."); + // return APP_ERROR_CREATE_WINDOW; + //} + + //glfwMakeContextCurrent(window); + + //if (glewInit() != GL_NO_ERROR) + //{ + // callbackError(APP_ERROR_GLEW_INIT, "GLEW failed to initialize."); + // return APP_ERROR_GLEW_INIT; + //} + + ilInit(); // Initialize DevIL once. + + g_app = new Application(/* window, */ options); + + if (!g_app->isValid()) + { + std::cerr << "ERROR: Application() failed to initialize successfully.\n"; + ilShutDown(); + return APP_ERROR_APP_INIT; + } + + //const int mode = std::max(0, options.getMode()); + + //if (mode == 0) // Interactive, default. + //{ + // // Main loop + // bool finish = false; + // while (!finish /* && !glfwWindowShouldClose(window) */ ) + // { + // //glfwPollEvents(); // Render continuously. Battery drainer! + + // //glfwGetFramebufferSize(window, &width, &height); + + // g_app->reshape(width, height); + // //g_app->guiNewFrame(); + // //g_app->guiReferenceManual(); // HACK The ImGUI "Programming Manual" as example code. + // //g_app->guiWindow(); // This application's GUI window rendering commands. + // //g_app->guiEventHandler(); // SPACE to toggle the GUI windows and all mouse tracking via GuiState. + // //finish = g_app->render(); // OptiX rendering, returns true when benchmark is enabled and the samples per pixel have been rendered. + // //g_app->display(); // OpenGL display always required to lay the background for the GUI. + // //g_app->guiRender(); // Render all ImGUI elements at last. + + // //glfwSwapBuffers(window); + + // //glfwWaitEvents(); // Render only when an event is happening. Needs some glfwPostEmptyEvent() to prevent GUI lagging one frame behind when ending an action. + // } + //} + //else if (mode == 1) // Batched benchmark single shot. // FIXME When not using anything OpenGL, the whole window and OpenGL setup could be removed. + //{ + g_app->benchmark(); + //} + + delete g_app; + + ilShutDown(); + + return APP_EXIT_SUCCESS; +} + + +int main(int argc, char *argv[]) +{ + //glfwSetErrorCallback(callbackError); + + //if (!glfwInit()) + //{ + // callbackError(APP_ERROR_GLFW_INIT, "GLFW failed to initialize."); + // return APP_ERROR_GLFW_INIT; + //} + + int result = APP_ERROR_UNKNOWN; + + Options options; + + if (options.parseCommandLine(argc, argv)) + { + result = runApp(options); + } + + //glfwTerminate(); + + return result; +} diff --git a/apps/intro_runtime/src/Texture.cpp b/apps/intro_runtime/src/Texture.cpp index 97c970c9..09e2a8b1 100644 --- a/apps/intro_runtime/src/Texture.cpp +++ b/apps/intro_runtime/src/Texture.cpp @@ -671,6 +671,8 @@ Texture::Texture() // Setup cudaTextureDesc defaults. // Initialize all structure members to zero. + // (Required with CUDA 11.7 because of an incorrect interface change in cudaTextureDesc + // which will be fixed in a future CUDA toolkit with a separate versioned cudaTextureDesc.) m_textureDescription = {}; // The developer can override these at will before calling Texture::create(). diff --git a/apps/rtigo12/src/Device.cpp b/apps/rtigo12/src/Device.cpp index ce1b71e7..6dc68b4c 100644 --- a/apps/rtigo12/src/Device.cpp +++ b/apps/rtigo12/src/Device.cpp @@ -589,7 +589,8 @@ void Device::initPipeline() mco.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_3; // All optimizations, is the default. // Keep generated line info for Nsight Compute profiling. (NVCC_OPTIONS use --generate-line-info in CMakeLists.txt) #if (OPTIX_VERSION >= 70400) - mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; + mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MINIMAL; + // mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_MODERATE; // this is for NSight. Some performance loss. #else mco.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO; #endif