From 1e63864c99bc5c3a19ce5b1c5272810263864736 Mon Sep 17 00:00:00 2001 From: Michael Migliore Date: Mon, 16 Jan 2023 21:31:40 +0100 Subject: [PATCH] Cache baked HDRI When using a HDRI, the PBR engine is generating 3 data: - a LUT 2D images (static texture) - 9 RGB spherical harmonics values (related to the HDRI) - a mipmap cubemap (related to the HDRI) These data are quite expensive to bake depending on the user GPU, so this PR copy the result to cache files in order to reuse them if the user uses the same HDRI again which is likely to happen. --- .github/actions/vtk_commit_sha | 2 +- cmake/testing.cmake | 4 +- .../VTKExtensions/Rendering/CMakeLists.txt | 2 + .../Rendering/Testing/CMakeLists.txt | 1 + .../Testing/TestF3DCachedTexturesPrint.cxx | 16 ++ library/VTKExtensions/Rendering/vtk.module | 1 + .../Rendering/vtkF3DCachedLUTTexture.cxx | 68 +++++++ .../Rendering/vtkF3DCachedLUTTexture.h | 39 ++++ .../Rendering/vtkF3DCachedSpecularTexture.cxx | 87 ++++++++ .../Rendering/vtkF3DCachedSpecularTexture.h | 39 ++++ .../Rendering/vtkF3DRenderer.cxx | 185 +++++++++++++++++- .../VTKExtensions/Rendering/vtkF3DRenderer.h | 7 + library/private/window_impl.h | 6 + library/public/engine.h | 10 + library/src/engine.cxx | 22 +++ library/src/window_impl.cxx | 18 ++ library/testing/CMakeLists.txt | 6 +- library/testing/TestSDKDynamicHDRI.cxx | 33 +++- python/F3DPythonBindings.cxx | 1 + 19 files changed, 532 insertions(+), 15 deletions(-) create mode 100644 library/VTKExtensions/Rendering/Testing/TestF3DCachedTexturesPrint.cxx create mode 100644 library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.cxx create mode 100644 library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.h create mode 100644 library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.cxx create mode 100644 library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.h diff --git a/.github/actions/vtk_commit_sha b/.github/actions/vtk_commit_sha index 14aae02a29..626270c60e 100644 --- a/.github/actions/vtk_commit_sha +++ b/.github/actions/vtk_commit_sha @@ -1 +1 @@ -ea2b6b5ad1516f65684eff0972a198af873ca1c1 +7ae47ffa119c6feaea43ef021fb48a3056abbec2 diff --git a/cmake/testing.cmake b/cmake/testing.cmake index 06626bf450..7515f90196 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -228,8 +228,8 @@ if(VTK_VERSION VERSION_GREATER_EQUAL 9.1.20211007) f3d_test(NAME TestThumbnailConfigFileUp DATA suzanne.stl CONFIG ${CMAKE_SOURCE_DIR}/resources/thumbnail.json DEFAULT_LIGHTS) endif() -# HDRI test needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/8825 -if(VTK_VERSION VERSION_GREATER_EQUAL 9.1.20220117) +# HDRI test needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/9767 +if(VTK_VERSION VERSION_GREATER_EQUAL 9.2.20221220) f3d_test(NAME TestHDRI HDRI DATA suzanne.ply ARGS --hdri=${CMAKE_SOURCE_DIR}/testing/data/palermo_park_1k.hdr DEFAULT_LIGHTS) f3d_test(NAME TestHDRIBlur HDRI DATA suzanne.ply ARGS -u --hdri=${CMAKE_SOURCE_DIR}/testing/data/palermo_park_1k.hdr DEFAULT_LIGHTS) f3d_test(NAME TestHDRIBlurRatio HDRI DATA suzanne.ply RESOLUTION 600,100 ARGS -u --hdri=${CMAKE_SOURCE_DIR}/testing/data/palermo_park_1k.hdr DEFAULT_LIGHTS) diff --git a/library/VTKExtensions/Rendering/CMakeLists.txt b/library/VTKExtensions/Rendering/CMakeLists.txt index aff01c841c..e1f88fb311 100644 --- a/library/VTKExtensions/Rendering/CMakeLists.txt +++ b/library/VTKExtensions/Rendering/CMakeLists.txt @@ -1,4 +1,6 @@ set(classes + vtkF3DCachedLUTTexture + vtkF3DCachedSpecularTexture vtkF3DInteractorEventRecorder vtkF3DInteractorStyle vtkF3DNoRenderWindow diff --git a/library/VTKExtensions/Rendering/Testing/CMakeLists.txt b/library/VTKExtensions/Rendering/Testing/CMakeLists.txt index cf8d153a4b..9dcacc0555 100644 --- a/library/VTKExtensions/Rendering/Testing/CMakeLists.txt +++ b/library/VTKExtensions/Rendering/Testing/CMakeLists.txt @@ -8,5 +8,6 @@ vtk_add_test_cxx(VTKExtensionsRenderingTests tests TestF3DOpenGLGridMapper.cxx TestF3DRenderPass.cxx TestF3DRenderer.cxx + TestF3DCachedTexturesPrint.cxx ${CMAKE_SOURCE_DIR}/testing/ ${CMAKE_BINARY_DIR}/Testing/Temporary/) vtk_test_cxx_executable(VTKExtensionsRenderingTests tests) diff --git a/library/VTKExtensions/Rendering/Testing/TestF3DCachedTexturesPrint.cxx b/library/VTKExtensions/Rendering/Testing/TestF3DCachedTexturesPrint.cxx new file mode 100644 index 0000000000..85242af046 --- /dev/null +++ b/library/VTKExtensions/Rendering/Testing/TestF3DCachedTexturesPrint.cxx @@ -0,0 +1,16 @@ +#include +#include + +#include "vtkF3DCachedLUTTexture.h" +#include "vtkF3DCachedSpecularTexture.h" + +int TestF3DCachedTexturesPrint(int argc, char* argv[]) +{ + vtkNew lut; + vtkNew specular; + + lut->Print(cout); + specular->Print(cout); + + return EXIT_SUCCESS; +} diff --git a/library/VTKExtensions/Rendering/vtk.module b/library/VTKExtensions/Rendering/vtk.module index a0465a7b87..562b1f0c3a 100644 --- a/library/VTKExtensions/Rendering/vtk.module +++ b/library/VTKExtensions/Rendering/vtk.module @@ -10,6 +10,7 @@ DEPENDS VTK::opengl PRIVATE_DEPENDS VTK::IOImage + VTK::IOXML VTK::InteractionWidgets VTK::RenderingCore f3d::VTKExtensionsCore diff --git a/library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.cxx b/library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.cxx new file mode 100644 index 0000000000..033e5020a7 --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.cxx @@ -0,0 +1,68 @@ +#include "vtkF3DCachedLUTTexture.h" + +#include "vtkImageData.h" +#include "vtkObjectFactory.h" +#include "vtkOpenGLRenderWindow.h" +#include "vtkRenderer.h" +#include "vtkTextureObject.h" +#include "vtkXMLImageDataReader.h" + +#include + +vtkStandardNewMacro(vtkF3DCachedLUTTexture); + +//------------------------------------------------------------------------------ +void vtkF3DCachedLUTTexture::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "FileName: " << this->FileName << endl; +} + +//------------------------------------------------------------------------------ +void vtkF3DCachedLUTTexture::Load(vtkRenderer* ren) +{ + if (this->GetMTime() > this->LoadTime.GetMTime()) + { + vtkOpenGLRenderWindow* renWin = vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow()); + + if (this->TextureObject == nullptr) + { + this->TextureObject = vtkTextureObject::New(); + } + + this->TextureObject->SetContext(renWin); + this->TextureObject->SetFormat(GL_RG); +#ifdef GL_ES_VERSION_3_0 + this->TextureObject->SetInternalFormat(GL_RG8); + this->TextureObject->SetDataType(GL_UNSIGNED_BYTE); +#else + this->TextureObject->SetInternalFormat(GL_RG16); + this->TextureObject->SetDataType(GL_UNSIGNED_SHORT); +#endif + this->TextureObject->SetWrapS(vtkTextureObject::ClampToEdge); + this->TextureObject->SetWrapT(vtkTextureObject::ClampToEdge); + this->TextureObject->SetMinificationFilter(vtkTextureObject::Linear); + this->TextureObject->SetMagnificationFilter(vtkTextureObject::Linear); + + vtkNew reader; + reader->SetFileName(this->FileName.c_str()); + reader->Update(); + + vtkImageData* img = reader->GetOutput(); + int dims[3]; + img->GetDimensions(dims); + +#ifdef GL_ES_VERSION_3_0 + this->TextureObject->Create2DFromRaw( + dims[0], dims[1], 2, VTK_UNSIGNED_CHAR, img->GetScalarPointer()); +#else + this->TextureObject->Create2DFromRaw( + dims[0], dims[1], 2, VTK_UNSIGNED_SHORT, img->GetScalarPointer()); +#endif + + this->RenderWindow = renWin; + this->LoadTime.Modified(); + } + + this->TextureObject->Activate(); +} diff --git a/library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.h b/library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.h new file mode 100644 index 0000000000..be9a877a1b --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DCachedLUTTexture.h @@ -0,0 +1,39 @@ +/** + * @class vtkF3DCachedLUTTexture + * @brief create a LUT texture from a vti file + */ + +#ifndef vtkF3DCachedLUTTexture_h +#define vtkF3DCachedLUTTexture_h + +#include "vtkPBRLUTTexture.h" + +class vtkF3DCachedLUTTexture : public vtkPBRLUTTexture +{ +public: + static vtkF3DCachedLUTTexture* New(); + vtkTypeMacro(vtkF3DCachedLUTTexture, vtkPBRLUTTexture); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /** + * Set the image file name. + */ + vtkSetMacro(FileName, std::string); + + /** + * Implement base class method. + */ + void Load(vtkRenderer*) override; + +protected: + vtkF3DCachedLUTTexture() = default; + ~vtkF3DCachedLUTTexture() override = default; + + std::string FileName; + +private: + vtkF3DCachedLUTTexture(const vtkF3DCachedLUTTexture&) = delete; + void operator=(const vtkF3DCachedLUTTexture&) = delete; +}; + +#endif diff --git a/library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.cxx b/library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.cxx new file mode 100644 index 0000000000..448cdb2dbc --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.cxx @@ -0,0 +1,87 @@ +#include "vtkF3DCachedSpecularTexture.h" + +#include "vtkImageData.h" +#include "vtkMultiBlockDataSet.h" +#include "vtkObjectFactory.h" +#include "vtkOpenGLRenderWindow.h" +#include "vtkRenderer.h" +#include "vtkTextureObject.h" +#include "vtkXMLMultiBlockDataReader.h" + +#include + +vtkStandardNewMacro(vtkF3DCachedSpecularTexture); + +//------------------------------------------------------------------------------ +void vtkF3DCachedSpecularTexture::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "FileName: " << this->FileName << endl; +} + +//------------------------------------------------------------------------------ +void vtkF3DCachedSpecularTexture::Load(vtkRenderer* ren) +{ + if (this->GetMTime() > this->LoadTime.GetMTime()) + { + vtkOpenGLRenderWindow* renWin = vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow()); + + if (this->TextureObject == nullptr) + { + this->TextureObject = vtkTextureObject::New(); + } + + this->TextureObject->SetContext(renWin); + this->TextureObject->SetFormat(GL_RGB); + this->TextureObject->SetInternalFormat(GL_RGB32F); + this->TextureObject->SetDataType(GL_FLOAT); + this->TextureObject->SetWrapS(vtkTextureObject::ClampToEdge); + this->TextureObject->SetWrapT(vtkTextureObject::ClampToEdge); + this->TextureObject->SetWrapR(vtkTextureObject::ClampToEdge); + this->TextureObject->SetMinificationFilter(vtkTextureObject::LinearMipmapLinear); + this->TextureObject->SetMagnificationFilter(vtkTextureObject::Linear); + this->TextureObject->SetGenerateMipmap(true); + + this->RenderWindow = renWin; + + vtkNew reader; + reader->SetFileName(this->FileName.c_str()); + reader->Update(); + + vtkMultiBlockDataSet* mb = vtkMultiBlockDataSet::SafeDownCast(reader->GetOutput()); + + unsigned int nbLevels = mb->GetNumberOfBlocks(); + + this->TextureObject->SetMaxLevel(nbLevels - 1); + + vtkImageData* firstImg = vtkImageData::SafeDownCast(mb->GetBlock(0)); + + void* data[6]; + for (int i = 0; i < 6; i++) + { + data[i] = firstImg->GetScalarPointer(0, 0, i); + } + + int* firstDims = firstImg->GetDimensions(); + this->TextureObject->CreateCubeFromRaw(firstDims[0], firstDims[1], 3, VTK_FLOAT, data); + + // the mip levels are manually uploaded because there is no abstraction in VTK + for (unsigned int i = 1; i < nbLevels; i++) + { + vtkImageData* img = vtkImageData::SafeDownCast(mb->GetBlock(i)); + int* dims = img->GetDimensions(); + + for (int j = 0; j < 6; j++) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, i, + this->TextureObject->GetInternalFormat(VTK_FLOAT, 3, false), dims[0], dims[1], 0, + this->TextureObject->GetFormat(VTK_FLOAT, 3, false), + this->TextureObject->GetDataType(VTK_FLOAT), img->GetScalarPointer(0, 0, j)); + } + } + + this->LoadTime.Modified(); + } + + this->TextureObject->Activate(); +} diff --git a/library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.h b/library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.h new file mode 100644 index 0000000000..60901de132 --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DCachedSpecularTexture.h @@ -0,0 +1,39 @@ +/** + * @class vtkF3DCachedSpecularTexture + * @brief create a prefiltered specular texture from a vtm file + */ + +#ifndef vtkF3DCachedSpecularTexture_h +#define vtkF3DCachedSpecularTexture_h + +#include "vtkPBRPrefilterTexture.h" + +class vtkF3DCachedSpecularTexture : public vtkPBRPrefilterTexture +{ +public: + static vtkF3DCachedSpecularTexture* New(); + vtkTypeMacro(vtkF3DCachedSpecularTexture, vtkPBRPrefilterTexture); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /** + * Set the image file name. + */ + vtkSetMacro(FileName, std::string); + + /** + * Implement base class method. + */ + void Load(vtkRenderer*) override; + +protected: + vtkF3DCachedSpecularTexture() = default; + ~vtkF3DCachedSpecularTexture() override = default; + + std::string FileName; + +private: + vtkF3DCachedSpecularTexture(const vtkF3DCachedSpecularTexture&) = delete; + void operator=(const vtkF3DCachedSpecularTexture&) = delete; +}; + +#endif diff --git a/library/VTKExtensions/Rendering/vtkF3DRenderer.cxx b/library/VTKExtensions/Rendering/vtkF3DRenderer.cxx index 0fa1841c87..8bd1890df9 100644 --- a/library/VTKExtensions/Rendering/vtkF3DRenderer.cxx +++ b/library/VTKExtensions/Rendering/vtkF3DRenderer.cxx @@ -1,32 +1,46 @@ #include "vtkF3DRenderer.h" #include "F3DLog.h" +#include "vtkF3DCachedLUTTexture.h" +#include "vtkF3DCachedSpecularTexture.h" #include "vtkF3DConfigure.h" #include "vtkF3DOpenGLGridMapper.h" #include "vtkF3DRenderPass.h" -#include "vtkLight.h" -#include "vtkLightCollection.h" #include #include #include #include #include +#include #include #include #include +#include +#include #include +#include #include #include #include #include +#include +#include #include #include #include +#include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include #include #if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 20220907) @@ -47,6 +61,59 @@ vtkStandardNewMacro(vtkF3DRenderer); +namespace +{ +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 20221220) +//---------------------------------------------------------------------------- +// Compute the MD5 hash of the scalar field of an image data +std::string ComputeImageHash(vtkImageData* image) +{ + unsigned char digest[16]; + char md5Hash[33]; + md5Hash[32] = '\0'; + + unsigned char* content = reinterpret_cast(image->GetScalarPointer()); + int* dims = image->GetDimensions(); + int nbComp = image->GetNumberOfScalarComponents(); + int scalarSize = image->GetScalarSize(); + int size = nbComp * scalarSize * dims[0] * dims[1] * dims[2]; + + vtksysMD5* md5 = vtksysMD5_New(); + vtksysMD5_Initialize(md5); + vtksysMD5_Append(md5, content, size); + vtksysMD5_Finalize(md5, digest); + vtksysMD5_DigestToHex(digest, md5Hash); + vtksysMD5_Delete(md5); + + return md5Hash; +} + +//---------------------------------------------------------------------------- +// Download texture from the GPU to a vtkImageData +vtkSmartPointer SaveTextureToImage( + vtkTextureObject* tex, unsigned int target, unsigned int level, unsigned int size, int type) +{ + unsigned int dims[2] = { size, size }; + vtkIdType incr[2] = { 0, 0 }; + + unsigned int nbFaces = tex->GetTarget() == GL_TEXTURE_CUBE_MAP ? 6 : 1; + + vtkNew img; + img->SetDimensions(size, size, nbFaces); + img->AllocateScalars(type, tex->GetComponents()); + + for (unsigned int i = 0; i < nbFaces; i++) + { + vtkPixelBufferObject* pbo = tex->Download(target + i, level); + + pbo->Download2D(type, img->GetScalarPointer(0, 0, i), dims, tex->GetComponents(), incr); + } + + return img; +} +#endif +} + //---------------------------------------------------------------------------- vtkF3DRenderer::vtkF3DRenderer() { @@ -354,9 +421,15 @@ void vtkF3DRenderer::ShowGrid(bool show) void vtkF3DRenderer::SetHDRIFile(const std::string& hdriFile) { // Check HDRI is different than current one - if (this->HDRIFile != hdriFile) + std::string collapsedHdriFile; + if (!hdriFile.empty()) + { + collapsedHdriFile = vtksys::SystemTools::CollapseFullPath(hdriFile); + } + + if (this->HDRIFile != collapsedHdriFile) { - this->HDRIFile = hdriFile; + this->HDRIFile = collapsedHdriFile; // Read HDRI when needed vtkNew hdriTexture; @@ -402,6 +475,51 @@ void vtkF3DRenderer::SetHDRIFile(const std::string& hdriFile) // Dynamic HDRI if (this->HasHDRI) { +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 20221220) + // Check LUT cache + std::string lutCachePath = this->CachePath + "/lut.vti"; + bool lutCacheExists = vtksys::SystemTools::FileExists(lutCachePath, true); + if (lutCacheExists) + { + vtkF3DCachedLUTTexture* lut = vtkF3DCachedLUTTexture::New(); + lut->SetFileName(lutCachePath.c_str()); + this->EnvMapLookupTable = lut; + } + + // Compute HDRI MD5 + std::string hash = ::ComputeImageHash(hdriTexture->GetInput()); + + // Cache folder for this HDRI + std::string currentCachePath = this->CachePath + "/" + hash; + + // Create the folder if it does not exists + vtksys::SystemTools::MakeDirectory(currentCachePath); + + // Check spherical harmonics cache + std::string shCachePath = this->CachePath + "/" + hash + "/sh.vtt"; + bool shCacheExists = vtksys::SystemTools::FileExists(shCachePath, true); + if (shCacheExists) + { + vtkNew reader; + reader->SetFileName(shCachePath.c_str()); + reader->Update(); + + this->SphericalHarmonics = vtkFloatArray::SafeDownCast(reader->GetOutput()->GetColumn(0)); + } + + // Check specular cache + std::string specCachePath = this->CachePath + "/" + hash + "/specular.vtm"; + bool specCacheExists = vtksys::SystemTools::FileExists(specCachePath, true); + if (specCacheExists) + { + vtkF3DCachedSpecularTexture* spec = vtkF3DCachedSpecularTexture::New(); + spec->SetFileName(specCachePath.c_str()); + this->EnvMapPrefiltered = spec; + } + + this->GetEnvMapPrefiltered()->HalfPrecisionOff(); +#endif + // HDRI OpenGL this->UseImageBasedLightingOn(); this->SetEnvironmentTexture(hdriTexture); @@ -423,6 +541,65 @@ void vtkF3DRenderer::SetHDRIFile(const std::string& hdriFile) this->AddActor(this->Skybox); this->AutomaticLightCreationOff(); + + // build HDRI textures and spherical harmonics + this->Render(); + +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 20221220) + // Create LUT cache file + if (!lutCacheExists) + { + vtkPBRLUTTexture* lut = this->GetEnvMapLookupTable(); + + vtkSmartPointer img = ::SaveTextureToImage( + lut->GetTextureObject(), GL_TEXTURE_2D, 0, lut->GetLUTSize(), VTK_UNSIGNED_SHORT); + + if (img) + { + vtkNew writer; + writer->SetFileName(lutCachePath.c_str()); + writer->SetInputData(img); + writer->Write(); + } + } + + // Create spherical harmonics cache file + if (!shCacheExists) + { + vtkNew table; + table->AddColumn(this->SphericalHarmonics); + + vtkNew writer; + writer->SetInputData(table); + writer->SetFileName(shCachePath.c_str()); + writer->Write(); + } + + // Create specular cache file + if (!specCacheExists) + { + vtkPBRPrefilterTexture* spec = this->GetEnvMapPrefiltered(); + + unsigned int nbLevels = spec->GetPrefilterLevels(); + unsigned int size = spec->GetPrefilterSize(); + + vtkNew mb; + mb->SetNumberOfBlocks(6 * nbLevels); + + for (unsigned int i = 0; i < nbLevels; i++) + { + vtkSmartPointer img = ::SaveTextureToImage( + spec->GetTextureObject(), GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, size >> i, VTK_FLOAT); + + mb->SetBlock(i, img); + } + + vtkNew writer; + writer->SetFileName(specCachePath.c_str()); + writer->SetInputData(mb); + writer->Write(); + } +#endif } else { diff --git a/library/VTKExtensions/Rendering/vtkF3DRenderer.h b/library/VTKExtensions/Rendering/vtkF3DRenderer.h index 2e313ca243..89732d4d8d 100644 --- a/library/VTKExtensions/Rendering/vtkF3DRenderer.h +++ b/library/VTKExtensions/Rendering/vtkF3DRenderer.h @@ -112,6 +112,11 @@ class vtkF3DRenderer : public vtkOpenGLRenderer */ vtkGetVector3Macro(RightVector, double); + /** + * Set cache path + */ + vtkSetMacro(CachePath, std::string); + protected: vtkF3DRenderer(); ~vtkF3DRenderer() override; @@ -184,6 +189,8 @@ class vtkF3DRenderer : public vtkOpenGLRenderer std::string CurrentGridInfo; std::string GridInfo; + + std::string CachePath; }; #endif diff --git a/library/private/window_impl.h b/library/private/window_impl.h index 483e4d1df7..66a8514a22 100644 --- a/library/private/window_impl.h +++ b/library/private/window_impl.h @@ -97,6 +97,12 @@ class window_impl : public window */ virtual vtkRenderWindow* GetRenderWindow(); + /** + * Implementation only API. + * Set the cache path. + */ + void SetCachePath(const std::string& cachePath); + private: class internals; std::unique_ptr Internals; diff --git a/library/public/engine.h b/library/public/engine.h index c894c2b312..cd5fb456bd 100644 --- a/library/public/engine.h +++ b/library/public/engine.h @@ -53,6 +53,16 @@ class F3D_EXPORT engine */ ~engine(); + /** + * Set the cache path. Must be an absolute path. + * Currently, it's only used to store HDRI baked textures. + * By default, the cache path is: + * - Windows: %LOCALAPPDATA%\f3d + * - Linux: ~/.cache/f3d + * - macOS: ~/Library/Caches/f3d + */ + void setCachePath(const std::string& cachePath); + /** * Engine provide a default options that you can use using engine::getOptions(). * But you can use this setter to use other options directly. diff --git a/library/src/engine.cxx b/library/src/engine.cxx index b425ef5f2f..c0d40a0bff 100644 --- a/library/src/engine.cxx +++ b/library/src/engine.cxx @@ -33,10 +33,26 @@ class engine::internals engine::engine(window::Type windowType) : Internals(new engine::internals) { + // build default cache path +#if defined(_WIN32) + std::string cachePath = vtksys::SystemTools::GetEnv("LOCALAPPDATA"); + cachePath = cachePath + "/f3d"; +#else + std::string cachePath = vtksys::SystemTools::GetEnv("HOME"); +#if defined(__APPLE__) + cachePath = cachePath + "/Library/Caches/f3d"; +#elif defined(__unix__) + cachePath = cachePath + "/.cache/f3d"; +#else +#error "Unsupported platform" +#endif +#endif + this->Internals->Options = std::make_unique(); this->Internals->Window = std::make_unique(*this->Internals->Options, windowType); + this->Internals->Window->SetCachePath(cachePath); this->Internals->Loader = std::make_unique(*this->Internals->Options, *this->Internals->Window); @@ -232,6 +248,12 @@ std::vector engine::getReadersInfo() return readersInfo; } +//---------------------------------------------------------------------------- +void engine::setCachePath(const std::string& cachePath) +{ + this->Internals->Window->SetCachePath(cachePath); +} + //---------------------------------------------------------------------------- engine::no_window_exception::no_window_exception(const std::string& what) : exception(what) diff --git a/library/src/window_impl.cxx b/library/src/window_impl.cxx index aa9f91158d..dc09505b6b 100644 --- a/library/src/window_impl.cxx +++ b/library/src/window_impl.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #if F3D_MODULE_EXTERNAL_RENDERING #include @@ -35,12 +36,21 @@ class window_impl::internals { } + std::string GetCachePath() + { + // create directories if they do not exist + vtksys::SystemTools::MakeDirectory(this->CachePath); + + return this->CachePath; + } + std::unique_ptr Camera; vtkSmartPointer RenWin; vtkSmartPointer Renderer; Type WindowType; const options& Options; bool Initialized = false; + std::string CachePath; }; //---------------------------------------------------------------------------- @@ -225,6 +235,8 @@ void window_impl::Initialize(bool withColoring, std::string fileInfo) this->Internals->Renderer = vtkSmartPointer::New(); } + this->Internals->Renderer->SetCachePath(this->Internals->GetCachePath()); + this->Internals->Camera->SetVTKRenderer(this->Internals->Renderer); this->Internals->RenWin->AddRenderer(this->Internals->Renderer); this->Internals->Renderer->Initialize( @@ -392,4 +404,10 @@ void window_impl::InitializeRendererWithColoring(vtkF3DGenericImporter* importer importer->GetPointDataForColoring(), importer->GetCellDataForColoring()); } } + +//---------------------------------------------------------------------------- +void window_impl::SetCachePath(const std::string& cachePath) +{ + this->Internals->CachePath = cachePath; +} }; diff --git a/library/testing/CMakeLists.txt b/library/testing/CMakeLists.txt index 45411aef3b..a576ed726c 100644 --- a/library/testing/CMakeLists.txt +++ b/library/testing/CMakeLists.txt @@ -28,8 +28,8 @@ if(VTK_VERSION VERSION_GREATER_EQUAL 9.0.20201016) ) endif() -# HDRI test needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/8825 -if(VTK_VERSION VERSION_GREATER_EQUAL 9.1.20220117) +# HDRI test needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/9767 +if(VTK_VERSION VERSION_GREATER_EQUAL 9.2.20221220) list(APPEND libf3dSDKTests_list TestSDKDynamicHDRI.cxx ) @@ -105,7 +105,7 @@ Test Warning\nTest Error\nTest Error\n\ Test Print Debug\nTest Print Info\nTest Print Warning\nTest Print Error\n\ Test Debug Coloring") -if(VTK_VERSION VERSION_GREATER_EQUAL 9.1.20220519) +if(VTK_VERSION VERSION_GREATER_EQUAL 9.2.20221220) set_tests_properties(libf3d::TestSDKDynamicHDRI PROPERTIES TIMEOUT 90) if(NOT F3D_TESTING_ENABLE_HDRI_TESTS OR NOT F3D_TESTING_ENABLE_LONG_TIMEOUT_TESTS) set_tests_properties(libf3d::TestSDKDynamicHDRI PROPERTIES DISABLED ON) diff --git a/library/testing/TestSDKDynamicHDRI.cxx b/library/testing/TestSDKDynamicHDRI.cxx index 0576b9e047..91e72ff231 100644 --- a/library/testing/TestSDKDynamicHDRI.cxx +++ b/library/testing/TestSDKDynamicHDRI.cxx @@ -9,6 +9,8 @@ int TestSDKDynamicHDRI(int argc, char* argv[]) { f3d::engine eng(f3d::window::Type::NATIVE_OFFSCREEN); + eng.setCachePath(std::string(argv[2]) + "/cache"); + f3d::loader& load = eng.getLoader(); f3d::window& win = eng.getWindow(); f3d::options& opt = eng.getOptions(); @@ -17,13 +19,34 @@ int TestSDKDynamicHDRI(int argc, char* argv[]) load.addFile(std::string(argv[1]) + "/data/cow.vtp").loadFile(); - win.render(); + bool ret = win.render(); + + if (!ret) + { + std::cerr << "First render failed" << std::endl; + return EXIT_FAILURE; + } // Change the hdri and make sure it is taken into account opt.set("render.background.hdri", std::string(argv[1]) + "data/palermo_park_1k.hdr"); - return TestSDKHelpers::RenderTest(eng.getWindow(), std::string(argv[1]) + "baselines/", - std::string(argv[2]), "TestSDKDynamicHDRI", 50) - ? EXIT_SUCCESS - : EXIT_FAILURE; + ret = TestSDKHelpers::RenderTest(eng.getWindow(), std::string(argv[1]) + "baselines/", + std::string(argv[2]), "TestSDKDynamicHDRI", 50); + + if (!ret) + { + std::cerr << "Second render with HDRI failed" << std::endl; + return EXIT_FAILURE; + } + + // Check caching is working + std::ifstream lutFile(std::string(argv[2]) + "/cache/lut.vti"); + + if (!lutFile.is_open()) + { + std::cerr << "LUT cache file not found" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/python/F3DPythonBindings.cxx b/python/F3DPythonBindings.cxx index 6d0d90c6aa..ba3afb3c7b 100644 --- a/python/F3DPythonBindings.cxx +++ b/python/F3DPythonBindings.cxx @@ -187,6 +187,7 @@ PYBIND11_MODULE(f3d, module) .def("getOptions", &f3d::engine::getOptions, py::return_value_policy::reference) .def("setOptions", py::overload_cast(&f3d::engine::setOptions)) .def("getWindow", &f3d::engine::getWindow, py::return_value_policy::reference) + .def("setCachePath", &f3d::engine::setCachePath, "Set the cache path directory") .def_static("loadPlugin", &f3d::engine::loadPlugin, "Load a plugin") .def_static( "autoloadPlugins", &f3d::engine::autoloadPlugins, "Automatically load internal plugins");