Cycles: Add support for OSL texture intrinsic on the GPU

This makes it possible to use `texture` and `texture3d` in custom
OSL shaders with a constant image file name as argument on the
GPU, where previously texturing was only possible through Cycles
nodes.
For constant file name arguments, OSL calls
`OSL::RendererServices::get_texture_handle()` with the file name
string to convert it into an opaque handle for use on the GPU.
That is now used to load the respective image file using the Cycles
image manager and generate a SVM handle that can be used on
the GPU. Some care is necessary as the renderer services class is
shared across multiple Cycles instances, whereas the Cycles image
manager is local to each.

Maniphest Tasks: T101222

Differential Revision: https://developer.blender.org/D17032
This commit is contained in:
Patrick Mours 2023-01-18 17:28:03 +01:00
parent e270a198a5
commit 9066f2e043
8 changed files with 152 additions and 47 deletions

View File

@ -20,6 +20,7 @@
#include "kernel/osl/globals.h"
#include "kernel/osl/services.h"
#include "kernel/osl/types.h"
#include "util/foreach.h"
#include "util/log.h"
@ -119,6 +120,8 @@ ustring OSLRenderServices::u_u("u");
ustring OSLRenderServices::u_v("v");
ustring OSLRenderServices::u_empty;
ImageManager *OSLRenderServices::image_manager = nullptr;
OSLRenderServices::OSLRenderServices(OSL::TextureSystem *texture_system, int device_type)
: OSL::RendererServices(texture_system), device_type_(device_type)
{
@ -1154,7 +1157,7 @@ TextureSystem::TextureHandle *OSLRenderServices::get_texture_handle(ustring file
/* For non-OIIO textures, just return a pointer to our own OSLTextureHandle. */
if (it != textures.end()) {
if (it->second->type != OSLTextureHandle::OIIO) {
return (TextureSystem::TextureHandle *)it->second.get();
return reinterpret_cast<TextureSystem::TextureHandle *>(it->second.get());
}
}
@ -1173,16 +1176,53 @@ TextureSystem::TextureHandle *OSLRenderServices::get_texture_handle(ustring file
/* Assign OIIO texture handle and return. */
it->second->oiio_handle = handle;
return (TextureSystem::TextureHandle *)it->second.get();
return reinterpret_cast<TextureSystem::TextureHandle *>(it->second.get());
}
else {
if (it != textures.end() && it->second->type == OSLTextureHandle::SVM &&
it->second->svm_slots[0].w == -1) {
return reinterpret_cast<TextureSystem::TextureHandle *>(
static_cast<uintptr_t>(it->second->svm_slots[0].y + 1));
/* Construct GPU texture handle for existing textures. */
if (it != textures.end()) {
switch (it->second->type) {
case OSLTextureHandle::OIIO:
return NULL;
case OSLTextureHandle::SVM:
if (!it->second->handle.empty() && it->second->handle.get_manager() != image_manager) {
it.clear();
break;
}
return reinterpret_cast<TextureSystem::TextureHandle *>(OSL_TEXTURE_HANDLE_TYPE_SVM |
it->second->svm_slots[0].y);
case OSLTextureHandle::IES:
if (!it->second->handle.empty() && it->second->handle.get_manager() != image_manager) {
it.clear();
break;
}
return reinterpret_cast<TextureSystem::TextureHandle *>(OSL_TEXTURE_HANDLE_TYPE_IES |
it->second->svm_slots[0].y);
case OSLTextureHandle::AO:
return reinterpret_cast<TextureSystem::TextureHandle *>(
OSL_TEXTURE_HANDLE_TYPE_AO_OR_BEVEL | 1);
case OSLTextureHandle::BEVEL:
return reinterpret_cast<TextureSystem::TextureHandle *>(
OSL_TEXTURE_HANDLE_TYPE_AO_OR_BEVEL | 2);
}
}
return NULL;
if (!image_manager) {
return NULL;
}
/* Load new textures using SVM image manager. */
ImageHandle handle = image_manager->add_image(filename.string(), ImageParams());
if (handle.empty()) {
return NULL;
}
if (!textures.insert(filename, new OSLTextureHandle(handle))) {
return NULL;
}
return reinterpret_cast<TextureSystem::TextureHandle *>(OSL_TEXTURE_HANDLE_TYPE_SVM |
handle.svm_slot());
}
}

View File

@ -16,6 +16,8 @@
#include <OSL/oslexec.h>
#include <OSL/rendererservices.h>
#include "scene/image.h"
#ifdef WITH_PTEX
class PtexCache;
#endif
@ -54,10 +56,20 @@ struct OSLTextureHandle : public OIIO::RefCnt {
{
}
OSLTextureHandle(const ImageHandle &handle)
: type(SVM),
svm_slots(handle.get_svm_slots()),
oiio_handle(nullptr),
processor(nullptr),
handle(handle)
{
}
Type type;
vector<int4> svm_slots;
OSL::TextureSystem::TextureHandle *oiio_handle;
ColorSpaceProcessor *processor;
ImageHandle handle;
};
typedef OIIO::intrusive_ptr<OSLTextureHandle> OSLTextureHandleRef;
@ -324,6 +336,8 @@ class OSLRenderServices : public OSL::RendererServices {
* shading system. */
OSLTextureHandleMap textures;
static ImageManager *image_manager;
private:
int device_type_;
};

View File

@ -1443,6 +1443,8 @@ OSL_NOISE_IMPL(osl_snoise, snoise)
/* Texturing */
#include "kernel/svm/ies.h"
ccl_device_extern ccl_private OSLTextureOptions *osl_get_texture_options(
ccl_private ShaderGlobals *sg)
{
@ -1548,25 +1550,31 @@ ccl_device_extern bool osl_texture(ccl_private ShaderGlobals *sg,
ccl_private float *dalphady,
ccl_private void *errormessage)
{
if (!texture_handle) {
return false;
const unsigned int type = OSL_TEXTURE_HANDLE_TYPE(texture_handle);
const unsigned int slot = OSL_TEXTURE_HANDLE_SLOT(texture_handle);
switch (type) {
case OSL_TEXTURE_HANDLE_TYPE_SVM: {
const float4 rgba = kernel_tex_image_interp(nullptr, slot, s, 1.0f - t);
if (nchannels > 0)
result[0] = rgba.x;
if (nchannels > 1)
result[1] = rgba.y;
if (nchannels > 2)
result[2] = rgba.z;
if (alpha)
*alpha = rgba.w;
return true;
}
case OSL_TEXTURE_HANDLE_TYPE_IES: {
if (nchannels > 0)
result[0] = kernel_ies_interp(nullptr, slot, s, t);
return true;
}
default: {
return false;
}
}
/* Only SVM textures are supported. */
int id = static_cast<int>(reinterpret_cast<size_t>(texture_handle) - 1);
const float4 rgba = kernel_tex_image_interp(nullptr, id, s, 1.0f - t);
if (nchannels > 0)
result[0] = rgba.x;
if (nchannels > 1)
result[1] = rgba.y;
if (nchannels > 2)
result[2] = rgba.z;
if (alpha)
*alpha = rgba.w;
return true;
}
ccl_device_extern bool osl_texture3d(ccl_private ShaderGlobals *sg,
@ -1586,25 +1594,26 @@ ccl_device_extern bool osl_texture3d(ccl_private ShaderGlobals *sg,
ccl_private float *dalphady,
ccl_private void *errormessage)
{
if (!texture_handle) {
return false;
const unsigned int type = OSL_TEXTURE_HANDLE_TYPE(texture_handle);
const unsigned int slot = OSL_TEXTURE_HANDLE_SLOT(texture_handle);
switch (type) {
case OSL_TEXTURE_HANDLE_TYPE_SVM: {
const float4 rgba = kernel_tex_image_interp_3d(nullptr, slot, *P, INTERPOLATION_NONE);
if (nchannels > 0)
result[0] = rgba.x;
if (nchannels > 1)
result[1] = rgba.y;
if (nchannels > 2)
result[2] = rgba.z;
if (alpha)
*alpha = rgba.w;
return true;
}
default: {
return false;
}
}
/* Only SVM textures are supported. */
int id = static_cast<int>(reinterpret_cast<size_t>(texture_handle) - 1);
const float4 rgba = kernel_tex_image_interp_3d(nullptr, id, *P, INTERPOLATION_NONE);
if (nchannels > 0)
result[0] = rgba.x;
if (nchannels > 1)
result[1] = rgba.y;
if (nchannels > 2)
result[2] = rgba.z;
if (alpha)
*alpha = rgba.w;
return true;
}
ccl_device_extern bool osl_environment(ccl_private ShaderGlobals *sg,

View File

@ -90,10 +90,17 @@ struct ShaderGlobals {
int backfacing;
};
struct OSLNoiseOptions {
};
struct OSLNoiseOptions {};
struct OSLTextureOptions {
};
struct OSLTextureOptions {};
#define OSL_TEXTURE_HANDLE_TYPE_IES ((uintptr_t)0x2 << 30)
#define OSL_TEXTURE_HANDLE_TYPE_SVM ((uintptr_t)0x1 << 30)
#define OSL_TEXTURE_HANDLE_TYPE_AO_OR_BEVEL ((uintptr_t)0x3 << 30)
#define OSL_TEXTURE_HANDLE_TYPE(handle) \
((unsigned int)((uintptr_t)(handle) & ((uintptr_t)0x3 << 30)))
#define OSL_TEXTURE_HANDLE_SLOT(handle) \
((unsigned int)((uintptr_t)(handle) & ((uintptr_t)0x3FFFFFFF)))
CCL_NAMESPACE_END

View File

@ -84,6 +84,7 @@ ccl_device_inline float kernel_ies_interp(KernelGlobals kg, int slot, float h_an
return max(cubic_interp(a, b, c, d, h_frac), 0.0f);
}
#ifdef __SVM__
ccl_device_noinline void svm_node_ies(KernelGlobals kg,
ccl_private ShaderData *sd,
ccl_private float *stack,
@ -105,5 +106,6 @@ ccl_device_noinline void svm_node_ies(KernelGlobals kg,
stack_store_float(stack, fac_offset, fac);
}
}
#endif
CCL_NAMESPACE_END

View File

@ -222,6 +222,11 @@ VDBImageLoader *ImageHandle::vdb_loader(const int tile_index) const
return NULL;
}
ImageManager *ImageHandle::get_manager() const
{
return manager;
}
bool ImageHandle::operator==(const ImageHandle &other) const
{
return manager == other.manager && tile_slots == other.tile_slots;

View File

@ -153,6 +153,8 @@ class ImageHandle {
VDBImageLoader *vdb_loader(const int tile_index = 0) const;
ImageManager *get_manager() const;
protected:
vector<int> tile_slots;
ImageManager *manager;

View File

@ -184,9 +184,19 @@ void OSLShaderManager::device_update_specific(Device *device,
* is being freed after the Session is freed.
*/
thread_scoped_lock lock(ss_shared_mutex);
/* Set current image manager during the lock, so that there is no conflict with other shader
* manager instances.
*
* It is used in "OSLRenderServices::get_texture_handle" called during optimization below to
* load images for the GPU. */
OSLRenderServices::image_manager = scene->image_manager;
for (const auto &[device_type, ss] : ss_shared) {
ss->optimize_all_groups();
}
OSLRenderServices::image_manager = nullptr;
}
/* load kernels */
@ -213,6 +223,22 @@ void OSLShaderManager::device_free(Device *device, DeviceScene *dscene, Scene *s
og->bump_state.clear();
og->background_state.reset();
});
/* Remove any textures specific to an image manager from shared render services textures, since
* the image manager may get destroyed next. */
for (const auto &[device_type, ss] : ss_shared) {
OSLRenderServices *services = static_cast<OSLRenderServices *>(ss->renderer());
for (auto it = services->textures.begin(); it != services->textures.end(); ++it) {
if (it->second->handle.get_manager() == scene->image_manager) {
/* Don't lock again, since the iterator already did so. */
services->textures.erase(it->first, false);
it.clear();
/* Iterator was invalidated, start from the beginning again. */
it = services->textures.begin();
}
}
}
}
void OSLShaderManager::texture_system_init()