mirror of
https://github.com/mmp/pbrt-v4
synced 2026-09-26 16:20:07 +03:00
Running on GPU
This commit is contained in:
parent
cc18de9c0a
commit
c08d5c7d8a
7 changed files with 148 additions and 20 deletions
118
CMakeLists.txt
118
CMakeLists.txt
|
|
@ -151,6 +151,8 @@ add_library (pbrt_warnings INTERFACE)
|
|||
target_compile_options (
|
||||
pbrt_warnings
|
||||
INTERFACE
|
||||
# CCCL headers (CUDA 13.x): MSVC must use the conforming preprocessor when nvcc forwards to cl.exe.
|
||||
"$<$<CXX_COMPILER_ID:MSVC>:$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-Xcompiler >/Zc:preprocessor>"
|
||||
"$<$<CXX_COMPILER_ID:MSVC>:$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-Xcompiler >/wd4244>" # int -> float conversion
|
||||
"$<$<CXX_COMPILER_ID:MSVC>:$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-Xcompiler >/wd4267>" # size_t -> int conversion
|
||||
"$<$<CXX_COMPILER_ID:MSVC>:$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-Xcompiler >/wd4305>" # double constant assigned to float
|
||||
|
|
@ -211,6 +213,9 @@ of CUDA installed, please update your PATH.")
|
|||
message (WARNING "Found CUDA but PBRT_OPTIX_PATH is not set. Disabling GPU compilation.")
|
||||
else ()
|
||||
enable_language (CUDA)
|
||||
if (POLICY CMP0104)
|
||||
cmake_policy (SET CMP0104 NEW)
|
||||
endif ()
|
||||
list (APPEND PBRT_DEFINITIONS "PBRT_BUILD_GPU_RENDERER")
|
||||
if (PBRT_NVTX)
|
||||
list (APPEND PBRT_DEFINITIONS "NVTX")
|
||||
|
|
@ -220,6 +225,16 @@ of CUDA installed, please update your PATH.")
|
|||
endif ()
|
||||
set (PBRT_CUDA_ENABLED ON)
|
||||
|
||||
# CUDA 13+ nvcc emits Nvvm IR (e.g. v114) newer than OptiX 9.0/9.1's embedded compiler
|
||||
# (expects ~v107), so optixModuleCreate fails with 7200 / COMPILE ERROR. Embed PTX instead.
|
||||
set (_pbrt_embed_optix_ir_default ON)
|
||||
if (CUDA_VERSION_MAJOR GREATER_EQUAL 13)
|
||||
set (_pbrt_embed_optix_ir_default OFF)
|
||||
endif ()
|
||||
option (PBRT_EMBED_OPTIX_IR
|
||||
"Embed OptiX IR for optix.cu (OFF: PTX; set ON only with an OptiX/SDK version that matches your CUDA nvcc IR)"
|
||||
${_pbrt_embed_optix_ir_default})
|
||||
|
||||
# FIXME
|
||||
include_directories (${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}) # for regular c++ compiles
|
||||
|
||||
|
|
@ -242,9 +257,13 @@ of CUDA installed, please update your PATH.")
|
|||
target_compile_options (
|
||||
cuda_build_configuration
|
||||
INTERFACE
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:--std=c++17;--use_fast_math;--expt-relaxed-constexpr;--extended-lambda;--forward-unknown-to-host-compiler>"
|
||||
# The "$<NOT:$<BOOL:$<TARGET_PROPERTY:CUDA_PTX_COMPILATION>>>" part is to not add debugging symbols when generating PTX files for OptiX; see https://github.com/mmp/pbrt-v4/issues/69#issuecomment-715499748.
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:$<IF:$<AND:$<CONFIG:Debug>,$<NOT:$<BOOL:$<TARGET_PROPERTY:CUDA_PTX_COMPILATION>>>>,-G;-g,-lineinfo;-maxrregcount;128>>"
|
||||
# Use SHELL: so MSVC CUDA targets do not split on ';' and splice cl flags (/EHsc /MP)
|
||||
# into the middle of the nvcc line (nvcc then errors: single input file required).
|
||||
# C++ standard comes from CMAKE_CUDA_STANDARD; only extra device flags here.
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:SHELL:--use_fast_math --expt-relaxed-constexpr --extended-lambda --forward-unknown-to-host-compiler>"
|
||||
# Skip device debug flags when emitting OptiX PTX or OptiX IR; see https://github.com/mmp/pbrt-v4/issues/69#issuecomment-715499748.
|
||||
# Commas inside $<IF:cond,then,else> must be escaped (\,) when cond uses $<AND:...> with multiple args.
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:$<IF:$<AND:$<CONFIG:Debug>\,$<NOT:$<BOOL:$<TARGET_PROPERTY:CUDA_PTX_COMPILATION>>>\,$<NOT:$<BOOL:$<TARGET_PROPERTY:CUDA_OPTIX_COMPILATION>>>>,SHELL:-G -g,SHELL:-lineinfo -maxrregcount 128>>"
|
||||
)
|
||||
|
||||
if (PBRT_GPU_SHADER_MODEL STREQUAL "")
|
||||
|
|
@ -270,14 +289,42 @@ of CUDA installed, please update your PATH.")
|
|||
if (NOT ${CUDA_RETURN_CODE} EQUAL 0)
|
||||
message (SEND_ERROR ${CHECK_CUDA_OUTPUT})
|
||||
else ()
|
||||
set(ARCH "${CHECK_CUDA_OUTPUT}")
|
||||
set (ARCH "${CHECK_CUDA_OUTPUT}")
|
||||
message (STATUS "Detected CUDA Architecture: ${ARCH}")
|
||||
string (APPEND CMAKE_CUDA_FLAGS " --gpu-architecture=${ARCH}")
|
||||
endif ()
|
||||
else ()
|
||||
set(ARCH "${PBRT_GPU_SHADER_MODEL}")
|
||||
set (ARCH "${PBRT_GPU_SHADER_MODEL}")
|
||||
message (STATUS "Specified CUDA Architecture: ${ARCH}")
|
||||
string (APPEND CMAKE_CUDA_FLAGS " --gpu-architecture=${ARCH}")
|
||||
endif ()
|
||||
|
||||
# OptiX device code: CUDA 11.7+ can compile to OptiX IR (CMake CUDA_OPTIX_COMPILATION,
|
||||
# nvcc --optix-ir), which avoids ptxas on PTX entirely. Newer nvcc still runs ptxas on
|
||||
# PTX when using --generate-code=...,code=[compute_XX], which breaks on _optix_*.
|
||||
unset (PBRT_CUDA_ARCH_NUMBER)
|
||||
if (ARCH MATCHES "^sm_([0-9]+)$")
|
||||
set (PBRT_CUDA_ARCH_NUMBER "${CMAKE_MATCH_1}")
|
||||
elseif (ARCH MATCHES "^compute_([0-9]+)$")
|
||||
set (PBRT_CUDA_ARCH_NUMBER "${CMAKE_MATCH_1}")
|
||||
elseif (ARCH)
|
||||
message (FATAL_ERROR
|
||||
"PBRT_GPU_SHADER_MODEL must look like sm_89 or compute_89 (got '${ARCH}')")
|
||||
endif ()
|
||||
if (PBRT_CUDA_ARCH_NUMBER)
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
|
||||
set (CMAKE_CUDA_ARCHITECTURES "${PBRT_CUDA_ARCH_NUMBER}")
|
||||
message (STATUS "CMAKE_CUDA_ARCHITECTURES: ${CMAKE_CUDA_ARCHITECTURES}")
|
||||
else ()
|
||||
string (APPEND CMAKE_CUDA_FLAGS " --gpu-architecture=${ARCH}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# VS + CUDA_ARCHITECTURES OFF (see pbrt_lib): device link must still get --gpu-architecture.
|
||||
if (MSVC AND ${CMAKE_GENERATOR} MATCHES "^Visual Studio" AND PBRT_CUDA_ARCH_NUMBER)
|
||||
set (PBRT_MSVC_CUDA_GPU_ARCHITECTURE "sm_${PBRT_CUDA_ARCH_NUMBER}")
|
||||
configure_file (
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pbrt_cuda_vs.props.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pbrt_cuda_vs.props"
|
||||
@ONLY)
|
||||
endif ()
|
||||
|
||||
set (PBRT_CUDA_LIB cuda)
|
||||
|
|
@ -301,18 +348,40 @@ of CUDA installed, please update your PATH.")
|
|||
endif ()
|
||||
|
||||
# this macro defines cmake rules that execute the following four steps:
|
||||
# 1) compile the given cuda file ${cuda_file} to an intermediary PTX file
|
||||
# 1) compile the given cuda file ${cuda_file} to PTX or OptiX IR (.optixir)
|
||||
# 2) use the 'bin2c' tool (that comes with CUDA) to
|
||||
# create a second intermediary (.c-)file which defines a const string variable
|
||||
# (named '${c_var_name}') whose (constant) value is the PTX output
|
||||
# create a second intermediary (.c-)file which defines a const byte array variable
|
||||
# (named '${c_var_name}') whose value is the module output
|
||||
# from the previous step.
|
||||
# 3) compile the given .c file to an intermediary object file (why thus has
|
||||
# that PTX string 'embedded' as a global constant.
|
||||
# 4) assign the name of the intermediary .o file to the cmake variable
|
||||
# 'output_var', which can then be added to cmake targets.
|
||||
macro (cuda_compile_and_embed output_var cuda_file lib_name)
|
||||
set (_pbrt_cuda_117_plus FALSE)
|
||||
if (CUDA_VERSION_MAJOR GREATER 11 OR (CUDA_VERSION_MAJOR EQUAL 11 AND CUDA_VERSION_MINOR GREATER_EQUAL 7))
|
||||
set (_pbrt_cuda_117_plus TRUE)
|
||||
endif ()
|
||||
add_library ("${lib_name}" OBJECT "${cuda_file}")
|
||||
set_property (TARGET "${lib_name}" PROPERTY CUDA_PTX_COMPILATION ON)
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.27" AND _pbrt_cuda_117_plus AND PBRT_EMBED_OPTIX_IR)
|
||||
set_property (TARGET "${lib_name}" PROPERTY CUDA_OPTIX_COMPILATION ON)
|
||||
message (STATUS "Embedding OptiX IR (not PTX) for ${cuda_file} (CUDA ${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR}+ / CMake 3.27+)")
|
||||
else ()
|
||||
set_property (TARGET "${lib_name}" PROPERTY CUDA_PTX_COMPILATION ON)
|
||||
message (STATUS "Embedding PTX for ${cuda_file} (PBRT_EMBED_OPTIX_IR=${PBRT_EMBED_OPTIX_IR})")
|
||||
if (MSVC AND ${CMAKE_GENERATOR} MATCHES "^Visual Studio" AND PBRT_CUDA_ARCH_NUMBER)
|
||||
# Same MSBuild/comma issue as pbrt_lib: use virtual arch for PTX, no comma in -gencode.
|
||||
set_property (TARGET "${lib_name}" PROPERTY CUDA_ARCHITECTURES OFF)
|
||||
target_compile_options ("${lib_name}" PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-gpu-architecture=compute_${PBRT_CUDA_ARCH_NUMBER}>")
|
||||
elseif (PBRT_CUDA_ARCH_NUMBER AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.23")
|
||||
set_property (TARGET "${lib_name}" PROPERTY CUDA_ARCHITECTURES "${PBRT_CUDA_ARCH_NUMBER}-virtual")
|
||||
elseif (PBRT_CUDA_ARCH_NUMBER)
|
||||
target_compile_options ("${lib_name}" PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-gpu-architecture=compute_${PBRT_CUDA_ARCH_NUMBER}>")
|
||||
endif ()
|
||||
endif ()
|
||||
unset (_pbrt_cuda_117_plus)
|
||||
|
||||
# disable "extern declaration... is treated as a static definition" warning
|
||||
if (CUDA_VERSION_MAJOR EQUAL 11 AND CUDA_VERSION_MINOR LESS 2)
|
||||
|
|
@ -347,6 +416,11 @@ of CUDA installed, please update your PATH.")
|
|||
target_include_directories ("${lib_name}" SYSTEM PRIVATE ${NANOVDB_INCLUDE})
|
||||
target_link_libraries ("${lib_name}" PRIVATE cuda_build_configuration pbrt_opt pbrt_warnings)
|
||||
add_dependencies ("${lib_name}" pbrt_soa_generated)
|
||||
# See generated build tree pbrt_cuda_vs.props (CMake configures from cmake/pbrt_cuda_vs.props.in).
|
||||
if (MSVC AND ${CMAKE_GENERATOR} MATCHES "Visual Studio" AND PBRT_CUDA_ARCH_NUMBER)
|
||||
set_target_properties ("${lib_name}" PROPERTIES
|
||||
VS_USER_PROPS "${CMAKE_CURRENT_BINARY_DIR}/pbrt_cuda_vs.props")
|
||||
endif ()
|
||||
set (c_var_name ${output_var})
|
||||
set (embedded_file ${cuda_file}.ptx_embedded.c)
|
||||
add_custom_command (
|
||||
|
|
@ -887,7 +961,15 @@ if (PBRT_CUDA_ENABLED AND PBRT_OPTIX_PATH)
|
|||
target_include_directories (pbrt_lib SYSTEM PUBLIC ${PBRT_OPTIX_PATH}/include)
|
||||
endif ()
|
||||
|
||||
target_compile_options (pbrt_lib PUBLIC ${PBRT_CXX_FLAGS})
|
||||
# With LANGUAGE CUDA .cpp files, PUBLIC MSVC flags (/EHsc, /MP) must not apply to nvcc;
|
||||
# they end up as bare nvcc args and break CUDA 13 + VS (fatal: single input file required).
|
||||
if (PBRT_CUDA_ENABLED)
|
||||
foreach (_pbrt_cxx_flag IN LISTS PBRT_CXX_FLAGS)
|
||||
target_compile_options (pbrt_lib PUBLIC "$<$<COMPILE_LANGUAGE:CXX>:${_pbrt_cxx_flag}>")
|
||||
endforeach ()
|
||||
else ()
|
||||
target_compile_options (pbrt_lib PUBLIC ${PBRT_CXX_FLAGS})
|
||||
endif ()
|
||||
|
||||
target_link_libraries (pbrt_lib PRIVATE OpenEXR::OpenEXR pbrt_warnings pbrt_opt $<$<BOOL:PBRT_CUDA_ENABLED>:cuda_build_configuration>)
|
||||
|
||||
|
|
@ -898,6 +980,18 @@ if (WIN32)
|
|||
set_target_properties (pbrt_lib PROPERTIES OUTPUT_NAME libpbrt)
|
||||
endif()
|
||||
|
||||
if (PBRT_CUDA_ENABLED AND MSVC AND ${CMAKE_GENERATOR} MATCHES "Visual Studio" AND PBRT_CUDA_ARCH_NUMBER)
|
||||
# CMake emits --generate-code=...,code=[compute_N,sm_N]. MSBuild splits CudaCompile
|
||||
# AdditionalOptions on commas, leaving bare /EHsc /MP as nvcc args (nvcc fatal).
|
||||
# OFF disables CMake's arch flags; --gpu-architecture=sm_N has no comma for MSBuild.
|
||||
# Build tree pbrt_cuda_vs.props (from .in) strips /EHsc /MP and appends device-link sm_N.
|
||||
set_target_properties (pbrt_lib PROPERTIES CUDA_ARCHITECTURES OFF)
|
||||
target_compile_options (pbrt_lib PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:SHELL:--gpu-architecture=sm_${PBRT_CUDA_ARCH_NUMBER}>")
|
||||
set_target_properties (pbrt_lib PROPERTIES
|
||||
VS_USER_PROPS "${CMAKE_CURRENT_BINARY_DIR}/pbrt_cuda_vs.props")
|
||||
endif ()
|
||||
|
||||
set (ALL_PBRT_LIBS
|
||||
pbrt_lib
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
|
|
|
|||
BIN
book-skipmip.png
Normal file
BIN
book-skipmip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 374 B |
|
|
@ -27,12 +27,13 @@
|
|||
# Taken from https://github.com/robertmaynard/code-samples/blob/master/posts/cmake_ptx/bin2c_wrapper.cmake
|
||||
# Modified to take a custom name instead of using the object name.
|
||||
|
||||
set(file_contents)
|
||||
set(file_contents "#include <stddef.h>\n\n")
|
||||
foreach(obj ${OBJECTS})
|
||||
get_filename_component(obj_ext ${obj} EXT)
|
||||
get_filename_component(obj_dir ${obj} DIRECTORY)
|
||||
|
||||
if(obj_ext MATCHES ".ptx")
|
||||
string(TOLOWER "${obj_ext}" obj_ext_lower)
|
||||
if(obj_ext_lower STREQUAL ".ptx" OR obj_ext_lower STREQUAL ".optixir")
|
||||
set(args --name ${VAR_NAME} ${obj})
|
||||
execute_process(COMMAND "${BIN_TO_C_COMMAND}" ${args}
|
||||
WORKING_DIRECTORY ${obj_dir}
|
||||
|
|
@ -43,4 +44,5 @@ foreach(obj ${OBJECTS})
|
|||
set(file_contents "${file_contents} \n${output}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(file_contents "${file_contents}\nconst size_t ${VAR_NAME}_SIZE = sizeof(${VAR_NAME});\n")
|
||||
file(WRITE "${OUTPUT}" "${file_contents}")
|
||||
|
|
|
|||
27
cmake/pbrt_cuda_vs.props.in
Normal file
27
cmake/pbrt_cuda_vs.props.in
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generated in build tree by CMake (@ONLY). MSBuild comma issues: see PBRT Wiki / CMakeLists. -->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemDefinitionGroup>
|
||||
<CudaCompile>
|
||||
<MultiProcessorCompilation>false</MultiProcessorCompilation>
|
||||
</CudaCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- CMake's vcxproj replaces CudaLink AdditionalOptions without inheriting earlier defaults;
|
||||
append arch before nvcc -dlink so RDC matches sm_N (fixes "no kernel image" at runtime). -->
|
||||
<Target Name="PbrtAppendCudaLinkArchitecture" BeforeTargets="CudaLink">
|
||||
<ItemGroup>
|
||||
<CudaLink>
|
||||
<AdditionalOptions>%(AdditionalOptions) --gpu-architecture=@PBRT_MSVC_CUDA_GPU_ARCHITECTURE@</AdditionalOptions>
|
||||
</CudaLink>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="PbrtStripMsvcFlagsFromNvccAdditionalOptions" BeforeTargets="CudaBuild">
|
||||
<ItemGroup>
|
||||
<CudaCompile>
|
||||
<AdditionalOptions>$([System.Text.RegularExpressions.Regex]::Replace('%(CudaCompile.AdditionalOptions)', '\s+/EHsc\s+/MP\s+', ' '))</AdditionalOptions>
|
||||
</CudaCompile>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -90,6 +90,7 @@ struct __align__(OPTIX_SBT_RECORD_ALIGNMENT) OptiXAggregate::HitgroupRecord {
|
|||
|
||||
extern "C" {
|
||||
extern const unsigned char PBRT_EMBEDDED_PTX[];
|
||||
extern const size_t PBRT_EMBEDDED_PTX_SIZE;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
|
@ -1064,7 +1065,8 @@ OptixPipelineCompileOptions OptiXAggregate::getPipelineCompileOptions() {
|
|||
}
|
||||
|
||||
OptixModule OptiXAggregate::createOptiXModule(OptixDeviceContext optixContext,
|
||||
const char *ptx) {
|
||||
const char *moduleInput,
|
||||
size_t moduleInputSize) {
|
||||
OptixModuleCompileOptions moduleCompileOptions = {};
|
||||
// TODO: REVIEW THIS
|
||||
moduleCompileOptions.maxRegisterCount = OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT;
|
||||
|
|
@ -1099,7 +1101,7 @@ OptixModule OptiXAggregate::createOptiXModule(OptixDeviceContext optixContext,
|
|||
OPTIX_CHECK_WITH_LOG(
|
||||
OPTIX_MODULE_CREATE_FN(
|
||||
optixContext, &moduleCompileOptions, &pipelineCompileOptions,
|
||||
ptx, strlen(ptx), log, &logSize, &optixModule
|
||||
moduleInput, moduleInputSize, log, &logSize, &optixModule
|
||||
),
|
||||
log
|
||||
);
|
||||
|
|
@ -1234,7 +1236,8 @@ OptiXAggregate::OptiXAggregate(
|
|||
(OPTIX_VERSION % 10000) / 100, OPTIX_VERSION % 100);
|
||||
|
||||
// OptiX module
|
||||
optixModule = createOptiXModule(optixContext, (const char *)PBRT_EMBEDDED_PTX);
|
||||
optixModule = createOptiXModule(optixContext, (const char *)PBRT_EMBEDDED_PTX,
|
||||
PBRT_EMBEDDED_PTX_SIZE);
|
||||
|
||||
// Optix program groups...
|
||||
char log[4096];
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class OptiXAggregate : public WavefrontAggregate {
|
|||
int addHGRecords(const BVH &bvh);
|
||||
|
||||
static OptixModule createOptiXModule(OptixDeviceContext optixContext,
|
||||
const char *ptx);
|
||||
const char *moduleInput, size_t moduleInputSize);
|
||||
static OptixPipelineCompileOptions getPipelineCompileOptions();
|
||||
|
||||
OptixProgramGroup createRaygenPG(const char *entrypoint) const;
|
||||
|
|
|
|||
|
|
@ -1032,7 +1032,8 @@ static cudaMipmappedArray_t createSingleChannelTextureArray(
|
|||
}
|
||||
|
||||
MIPMap mipmap(image, colorSpace, WrapMode::Clamp /* TODO */, Allocator(),
|
||||
MIPMapFilterOptions());
|
||||
MIPMapFilterOptions(),
|
||||
ImageTextureMipDownsizeStepsForFile(reportGPUPathForStats));
|
||||
*nMIPMapLevels = mipmap.Levels();
|
||||
|
||||
const Image &baseImage = mipmap.GetLevel(0);
|
||||
|
|
@ -1152,7 +1153,8 @@ GPUSpectrumImageTexture *GPUSpectrumImageTexture::Create(
|
|||
: cudaReadModeElementType;
|
||||
|
||||
MIPMap mipmap(image, colorSpace, WrapMode::Clamp /* TODO */,
|
||||
Allocator(), MIPMapFilterOptions());
|
||||
Allocator(), MIPMapFilterOptions(),
|
||||
ImageTextureMipDownsizeStepsForFile(filename));
|
||||
nMIPMapLevels = mipmap.Levels();
|
||||
const Image &baseImage = mipmap.GetLevel(0);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue