Cycles: Add half precision float support for volumes with NanoVDB

This patch makes it possible to change the precision with which to
store volume data in the NanoVDB data structure (as float, half, or
using variable bit quantization) via the previously unused precision
field in the volume data block.
It makes it possible to further reduce memory usage during
rendering, at a slight cost to the visual detail of a volume.

Differential Revision: https://developer.blender.org/D10023
This commit is contained in:
Patrick Mours 2022-05-20 18:01:26 +02:00
parent 14a5a91e0e
commit a8c81ffa83
Notes: blender-bot 2023-02-14 04:07:50 +01:00
Referenced by issue #81454, Cycles: enable NanoVDB by default
16 changed files with 119 additions and 13 deletions

View File

@ -219,7 +219,10 @@ static void sync_smoke_volume(
class BlenderVolumeLoader : public VDBImageLoader {
public:
BlenderVolumeLoader(BL::BlendData &b_data, BL::Volume &b_volume, const string &grid_name)
BlenderVolumeLoader(BL::BlendData &b_data,
BL::Volume &b_volume,
const string &grid_name,
BL::VolumeRender::precision_enum precision_)
: VDBImageLoader(grid_name), b_volume(b_volume)
{
b_volume.grids.load(b_data.ptr.data);
@ -240,6 +243,20 @@ class BlenderVolumeLoader : public VDBImageLoader {
break;
}
}
#endif
#ifdef WITH_NANOVDB
switch (precision_) {
case BL::VolumeRender::precision_FULL:
precision = 32;
break;
case BL::VolumeRender::precision_HALF:
precision = 16;
break;
default:
case BL::VolumeRender::precision_VARIABLE:
precision = 0;
break;
}
#endif
}
@ -318,7 +335,8 @@ static void sync_volume_object(BL::BlendData &b_data,
volume->attributes.add(std) :
volume->attributes.add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VOXEL);
ImageLoader *loader = new BlenderVolumeLoader(b_data, b_volume, name.string());
ImageLoader *loader = new BlenderVolumeLoader(
b_data, b_volume, name.string(), b_render.precision());
ImageParams params;
params.frame = b_volume.grids.frame();

View File

@ -1084,7 +1084,9 @@ void CUDADevice::tex_alloc(device_texture &mem)
need_texture_info = true;
if (mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT &&
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3) {
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3 &&
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FPN &&
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FP16) {
CUDA_RESOURCE_DESC resDesc;
memset(&resDesc, 0, sizeof(resDesc));

View File

@ -1042,7 +1042,9 @@ void HIPDevice::tex_alloc(device_texture &mem)
need_texture_info = true;
if (mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT &&
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3) {
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3 &&
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FPN &&
mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FP16) {
/* Bindless textures. */
hipResourceDesc resDesc;
memset(&resDesc, 0, sizeof(resDesc));

View File

@ -165,6 +165,8 @@ device_texture::device_texture(Device *device,
case IMAGE_DATA_TYPE_BYTE:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
data_type = TYPE_UCHAR;
data_elements = 1;
break;

View File

@ -817,6 +817,14 @@ ccl_device float4 kernel_tex_image_interp_3d(KernelGlobals kg,
}
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
return NanoVDBInterpolator<nanovdb::Vec3f>::interp_3d(info, P.x, P.y, P.z, interp);
case IMAGE_DATA_TYPE_NANOVDB_FPN: {
const float f = NanoVDBInterpolator<nanovdb::FpN, float>::interp_3d(info, P.x, P.y, P.z, interp);
return make_float4(f, f, f, 1.0f);
}
case IMAGE_DATA_TYPE_NANOVDB_FP16: {
const float f = NanoVDBInterpolator<nanovdb::Fp16, float>::interp_3d(info, P.x, P.y, P.z, interp);
return make_float4(f, f, f, 1.0f);
}
#endif
default:
assert(0);

View File

@ -125,7 +125,8 @@ kernel_tex_image_interp_tricubic(ccl_global const TextureInfo &info, float x, fl
#ifdef WITH_NANOVDB
template<typename T, typename S>
ccl_device T kernel_tex_image_interp_tricubic_nanovdb(S &s, float x, float y, float z)
ccl_device typename nanovdb::NanoGrid<T>::ValueType kernel_tex_image_interp_tricubic_nanovdb(
S &s, float x, float y, float z)
{
float px = floorf(x);
float py = floorf(y);
@ -157,7 +158,7 @@ ccl_device T kernel_tex_image_interp_tricubic_nanovdb(S &s, float x, float y, fl
}
template<typename T>
ccl_device_noinline T kernel_tex_image_interp_nanovdb(
ccl_device_noinline typename nanovdb::NanoGrid<T>::ValueType kernel_tex_image_interp_nanovdb(
ccl_global const TextureInfo &info, float x, float y, float z, uint interpolation)
{
using namespace nanovdb;
@ -238,6 +239,14 @@ ccl_device float4 kernel_tex_image_interp_3d(KernelGlobals kg,
info, x, y, z, interpolation);
return make_float4(f[0], f[1], f[2], 1.0f);
}
if (texture_type == IMAGE_DATA_TYPE_NANOVDB_FPN) {
float f = kernel_tex_image_interp_nanovdb<nanovdb::FpN>(info, x, y, z, interpolation);
return make_float4(f, f, f, 1.0f);
}
if (texture_type == IMAGE_DATA_TYPE_NANOVDB_FP16) {
float f = kernel_tex_image_interp_nanovdb<nanovdb::Fp16>(info, x, y, z, interpolation);
return make_float4(f, f, f, 1.0f);
}
#endif
if (texture_type == IMAGE_DATA_TYPE_FLOAT4 || texture_type == IMAGE_DATA_TYPE_BYTE4 ||
texture_type == IMAGE_DATA_TYPE_HALF4 || texture_type == IMAGE_DATA_TYPE_USHORT4) {

View File

@ -64,6 +64,10 @@ const char *name_from_type(ImageDataType type)
return "nanovdb_float";
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
return "nanovdb_float3";
case IMAGE_DATA_TYPE_NANOVDB_FPN:
return "nanovdb_fpn";
case IMAGE_DATA_TYPE_NANOVDB_FP16:
return "nanovdb_fp16";
case IMAGE_DATA_NUM_TYPES:
assert(!"System enumerator type, should never be used");
return "";
@ -378,7 +382,9 @@ void ImageManager::load_image_metadata(Image *img)
metadata.detect_colorspace();
assert(features.has_nanovdb || (metadata.type != IMAGE_DATA_TYPE_NANOVDB_FLOAT ||
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3));
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3 ||
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FPN ||
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FP16));
img->need_metadata = false;
}
@ -796,7 +802,8 @@ void ImageManager::device_load_image(Device *device, Scene *scene, int slot, Pro
}
}
#ifdef WITH_NANOVDB
else if (type == IMAGE_DATA_TYPE_NANOVDB_FLOAT || type == IMAGE_DATA_TYPE_NANOVDB_FLOAT3) {
else if (type == IMAGE_DATA_TYPE_NANOVDB_FLOAT || type == IMAGE_DATA_TYPE_NANOVDB_FLOAT3 ||
type == IMAGE_DATA_TYPE_NANOVDB_FPN || type == IMAGE_DATA_TYPE_NANOVDB_FP16) {
thread_scoped_lock device_lock(device_mutex);
void *pixels = img->mem->alloc(img->metadata.byte_size, 0);

View File

@ -199,6 +199,8 @@ bool OIIOImageLoader::load_pixels(const ImageMetaData &metadata,
break;
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
case IMAGE_DATA_NUM_TYPES:
break;
}

View File

@ -44,14 +44,30 @@ struct ToDenseOp {
# ifdef WITH_NANOVDB
struct ToNanoOp {
nanovdb::GridHandle<> nanogrid;
int precision;
template<typename GridType, typename FloatGridType, typename FloatDataType, int channels>
bool operator()(const openvdb::GridBase::ConstPtr &grid)
{
if constexpr (!std::is_same_v<GridType, openvdb::MaskGrid>) {
try {
nanogrid = nanovdb::openToNanoVDB(
FloatGridType(*openvdb::gridConstPtrCast<GridType>(grid)));
FloatGridType floatgrid(*openvdb::gridConstPtrCast<GridType>(grid));
if constexpr (std::is_same_v<FloatGridType, openvdb::FloatGrid>) {
if (precision == 0) {
nanogrid = nanovdb::openToNanoVDB<nanovdb::HostBuffer,
typename FloatGridType::TreeType,
nanovdb::FpN>(floatgrid);
return true;
}
else if (precision == 16) {
nanogrid = nanovdb::openToNanoVDB<nanovdb::HostBuffer,
typename FloatGridType::TreeType,
nanovdb::Fp16>(floatgrid);
return true;
}
}
nanogrid = nanovdb::openToNanoVDB(floatgrid);
}
catch (const std::exception &e) {
VLOG(1) << "Error converting OpenVDB to NanoVDB grid: " << e.what();
@ -102,6 +118,7 @@ bool VDBImageLoader::load_metadata(const ImageDeviceFeatures &features, ImageMet
openvdb::tools::pruneInactive(pruned_grid.tree());
nanogrid = nanovdb::openToNanoVDB(pruned_grid);*/
ToNanoOp op;
op.precision = precision;
if (!openvdb::grid_type_operation(grid, op)) {
return false;
}
@ -124,7 +141,15 @@ bool VDBImageLoader::load_metadata(const ImageDeviceFeatures &features, ImageMet
if (nanogrid) {
metadata.byte_size = nanogrid.size();
if (metadata.channels == 1) {
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FLOAT;
if (precision == 0) {
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FPN;
}
else if (precision == 16) {
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FP16;
}
else {
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FLOAT;
}
}
else {
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FLOAT3;

View File

@ -51,6 +51,7 @@ class VDBImageLoader : public ImageLoader {
#endif
#ifdef WITH_NANOVDB
nanovdb::GridHandle<> nanogrid;
int precision = 0;
#endif
};

View File

@ -327,9 +327,11 @@ float Object::compute_volume_step_size() const
/* Auto detect step size. */
float3 size = one_float3();
#ifdef WITH_NANOVDB
/* Dimensions were not applied to image transform with NanOVDB (see image_vdb.cpp) */
/* Dimensions were not applied to image transform with NanoVDB (see image_vdb.cpp) */
if (metadata.type != IMAGE_DATA_TYPE_NANOVDB_FLOAT &&
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3)
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3 &&
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FPN &&
metadata.type != IMAGE_DATA_TYPE_NANOVDB_FP16)
#endif
size /= make_float3(metadata.width, metadata.height, metadata.depth);

View File

@ -37,6 +37,8 @@ typedef enum ImageDataType {
IMAGE_DATA_TYPE_USHORT = 7,
IMAGE_DATA_TYPE_NANOVDB_FLOAT = 8,
IMAGE_DATA_TYPE_NANOVDB_FLOAT3 = 9,
IMAGE_DATA_TYPE_NANOVDB_FPN = 10,
IMAGE_DATA_TYPE_NANOVDB_FP16 = 11,
IMAGE_DATA_NUM_TYPES
} ImageDataType;

View File

@ -115,6 +115,9 @@ class DATA_PT_volume_render(DataButtonsPanel, Panel):
col = layout.column(align=True)
col.prop(render, "clipping")
col = layout.column()
col.prop(render, "precision")
col = layout.column(align=False)
col.prop(volume, "velocity_grid")

View File

@ -23,6 +23,7 @@
#define _DNA_DEFAULT_VolumeRender \
{ \
.precision = VOLUME_PRECISION_HALF, \
.space = VOLUME_SPACE_OBJECT, \
.step_size = 0.0f, \
.clipping = 0.001f, \

View File

@ -126,6 +126,13 @@ typedef enum VolumeWireframeDetail {
VOLUME_WIREFRAME_FINE = 1,
} VolumeWireframeDetail;
/** #VolumeRender.precision */
typedef enum VolumeRenderPrecision {
VOLUME_PRECISION_HALF = 0,
VOLUME_PRECISION_FULL = 1,
VOLUME_PRECISION_VARIABLE = 2,
} VolumeRenderPrecision;
/** #VolumeRender.space */
typedef enum VolumeRenderSpace {
VOLUME_SPACE_OBJECT = 0,

View File

@ -485,6 +485,21 @@ static void rna_def_volume_render(BlenderRNA *brna)
RNA_def_struct_sdna(srna, "VolumeRender");
RNA_def_struct_path_func(srna, "rna_VolumeRender_path");
static const EnumPropertyItem precision_items[] = {
{VOLUME_PRECISION_FULL, "FULL", 0, "Full", "Full float (Use 32 bit for all data)"},
{VOLUME_PRECISION_HALF, "HALF", 0, "Half", "Half float (Use 16 bit for all data)"},
{VOLUME_PRECISION_VARIABLE, "VARIABLE", 0, "Variable", "Use variable bit quantization"},
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "precision", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, precision_items);
RNA_def_property_ui_text(prop,
"Precision",
"Specify volume data precision. Lower values reduce memory consumption "
"at the cost of detail");
RNA_def_property_update(prop, 0, "rna_Volume_update_display");
static const EnumPropertyItem space_items[] = {
{VOLUME_SPACE_OBJECT,
"OBJECT",