Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
..
)
set(INC_SYS
)
if(WITH_CYCLES_DEVICE_OPTIX OR WITH_CYCLES_DEVICE_CUDA)
if(NOT WITH_CUDA_DYNLOAD)
add_definitions(-DCYCLES_CUDA_NVCC_EXECUTABLE="${CUDA_NVCC_EXECUTABLE}")
endif()
add_definitions(-DCYCLES_RUNTIME_OPTIX_ROOT_DIR="${CYCLES_RUNTIME_OPTIX_ROOT_DIR}")
endif()
set(SRC_BASE
device.cpp
denoise.cpp
graphics_interop.cpp
kernel.cpp
memory.cpp
queue.cpp
)
set(SRC_CPU
cpu/device.cpp
cpu/device.h
cpu/device_impl.cpp
cpu/device_impl.h
cpu/kernel.cpp
cpu/kernel.h
cpu/kernel_function.h
)
set(SRC_CUDA
cuda/device.cpp
cuda/device.h
cuda/device_impl.cpp
cuda/device_impl.h
cuda/graphics_interop.cpp
cuda/graphics_interop.h
cuda/kernel.cpp
cuda/kernel.h
cuda/queue.cpp
cuda/queue.h
cuda/util.cpp
cuda/util.h
)
set(SRC_HIP
hip/device.cpp
hip/device.h
hip/device_impl.cpp
hip/device_impl.h
hip/graphics_interop.cpp
hip/graphics_interop.h
hip/kernel.cpp
hip/kernel.h
hip/queue.cpp
hip/queue.h
hip/util.cpp
hip/util.h
)
set(SRC_HIPRT
hiprt/device_impl.cpp
hiprt/device_impl.h
hiprt/queue.cpp
hiprt/queue.h
)
set(SRC_ONEAPI
oneapi/device_impl.cpp
oneapi/device_impl.h
oneapi/device.cpp
oneapi/device.h
oneapi/graphics_interop.cpp
oneapi/graphics_interop.h
oneapi/queue.cpp
oneapi/queue.h
)
set(SRC_DUMMY
dummy/device.cpp
dummy/device.h
)
set(SRC_MULTI
multi/device.cpp
multi/device.h
)
set(SRC_METAL
metal/bvh.mm
metal/bvh.h
metal/device.mm
metal/device.h
metal/device_impl.mm
metal/device_impl.h
metal/graphics_interop.mm
metal/graphics_interop.h
metal/kernel.mm
metal/kernel.h
metal/queue.mm
metal/queue.h
metal/util.mm
metal/util.h
)
set(SRC_OPTIX
optix/device.cpp
optix/device.h
optix/device_impl.cpp
optix/device_impl.h
optix/queue.cpp
optix/queue.h
optix/util.h
)
set(SRC_HEADERS
device.h
denoise.h
graphics_interop.h
memory.h
kernel.h
queue.h
)
set(SRC
${SRC_BASE}
${SRC_CPU}
${SRC_CUDA}
${SRC_HIP}
${SRC_HIPRT}
${SRC_DUMMY}
${SRC_MULTI}
${SRC_OPTIX}
${SRC_HEADERS}
)
set(LIB
PUBLIC cycles_kernel
PUBLIC cycles_util
PRIVATE bf::dependencies::optional::openimagedenoise
PRIVATE bf::dependencies::optional::osl
)
if(WITH_CYCLES_DEVICE_OPTIX OR WITH_CYCLES_DEVICE_CUDA)
if(WITH_CUDA_DYNLOAD)
list(APPEND LIB
PRIVATE extern_cuew
)
else()
list(APPEND LIB
PRIVATE ${CUDA_CUDA_LIBRARY}
)
endif()
endif()
if(WITH_CYCLES_DEVICE_HIP AND WITH_HIP_DYNLOAD)
list(APPEND LIB
PRIVATE extern_hipew
)
endif()
if(WITH_CYCLES_DEVICE_HIPRT)
list(APPEND LIB ${HIPRT_LIBRARIES})
endif()
if(WITH_CYCLES_DEVICE_METAL)
list(APPEND LIB
PRIVATE ${METAL_LIBRARY}
)
list(APPEND SRC
${SRC_METAL}
)
endif()
if(WITH_CYCLES_DEVICE_ONEAPI)
if(WITH_CYCLES_ONEAPI_BINARIES)
set(cycles_kernel_oneapi_lib_suffix "_aot")
else()
set(cycles_kernel_oneapi_lib_suffix "_jit")
endif()
if(WIN32)
set(cycles_kernel_oneapi_lib ${CMAKE_CURRENT_BINARY_DIR}/../kernel/device/oneapi/cycles_kernel_oneapi${cycles_kernel_oneapi_lib_suffix}.lib)
else()
set(cycles_kernel_oneapi_lib ${CMAKE_CURRENT_BINARY_DIR}/../kernel/device/oneapi/libcycles_kernel_oneapi${cycles_kernel_oneapi_lib_suffix}.so)
endif()
list(APPEND LIB
PRIVATE ${cycles_kernel_oneapi_lib}
${SYCL_LIBRARIES}
)
list(APPEND SRC
${SRC_ONEAPI}
)
list(APPEND INC_SYS
${SYCL_INCLUDE_DIR}
)
# Test for the presence of sycl::ext::oneapi::experimental::unmap_external_linear_memory (necessary for interop).
# https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_bindless_images.asciidoc
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES "${SYCL_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES "${SYCL_LIBRARIES}")
check_cxx_source_compiles("
#include <sycl/ext/oneapi/bindless_images.hpp>
int main()
{
sycl::queue sycl_queue{sycl::gpu_selector_v};
sycl::ext::oneapi::experimental::unmap_external_linear_memory(nullptr, sycl_queue);
}
" SYCL_UNMAP_EXTERNAL_LINEAR_MEMORY_SUPPORTED)
if(SYCL_UNMAP_EXTERNAL_LINEAR_MEMORY_SUPPORTED)
foreach(FILE ${SRC_ONEAPI})
set_source_files_properties(
${FILE} PROPERTIES COMPILE_DEFINITIONS "SYCL_LINEAR_MEMORY_INTEROP_AVAILABLE"
)
endforeach()
else()
message(WARNING
"The installed SYCL version does not support unmap_external_linear_memory. "
"Upgrade to oneAPI >= 2025.3 or DPC++ >= 6.2 to support Vulkan-oneAPI interoperability.")
endif()
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES)
endif()
include_directories(${INC})
include_directories(SYSTEM ${INC_SYS})
cycles_add_library(cycles_device "${LIB}" ${SRC})
if(WITH_CYCLES_DEVICE_ONEAPI)
# Need to have proper rebuilding in case of changes
# in cycles_kernel_oneapi due external project behavior.
add_dependencies(cycles_device cycles_kernel_oneapi)
endif()
source_group("cpu" FILES ${SRC_CPU})
source_group("cuda" FILES ${SRC_CUDA})
source_group("dummy" FILES ${SRC_DUMMY})
source_group("hip" FILES ${SRC_HIP})
source_group("hiprt" FILES ${SRC_HIPRT})
source_group("multi" FILES ${SRC_MULTI})
source_group("metal" FILES ${SRC_METAL})
source_group("optix" FILES ${SRC_OPTIX})
source_group("oneapi" FILES ${SRC_ONEAPI})
source_group("common" FILES ${SRC_BASE} ${SRC_HEADERS})

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/cpu/device.h"
#include "device/cpu/device_impl.h"
#include "device/device.h"
/* Used for `info.denoisers`. */
/* TODO(sergey): The denoisers are probably to be moved completely out of the device into their
* own class. But until then keep API consistent with how it used to work before. */
#include "util/guiding.h"
#include "util/openimagedenoise.h"
CCL_NAMESPACE_BEGIN
unique_ptr<Device> device_cpu_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
return make_unique<CPUDevice>(info, stats, profiler, headless);
}
void device_cpu_info(vector<DeviceInfo> &devices)
{
DeviceInfo info;
info.type = DEVICE_CPU;
info.description = system_cpu_brand_string();
info.id = "CPU";
info.num = 0;
info.has_osl = true;
info.has_nanovdb = true;
info.has_profiling = true;
if (guiding_supported()) {
info.has_guiding = true;
}
else {
info.has_guiding = false;
}
if (openimagedenoise_supported()) {
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
}
devices.insert(devices.begin(), info);
}
string device_cpu_capabilities()
{
return system_cpu_support_avx2() ? "AVX2" : "";
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/string.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
unique_ptr<Device> device_cpu_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
void device_cpu_info(vector<DeviceInfo> &devices);
string device_cpu_capabilities();
CCL_NAMESPACE_END

View File

@@ -0,0 +1,369 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/cpu/device_impl.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
/* So ImathMath is included before our kernel_cpu_compat. */
#ifdef WITH_OSL
/* So no context pollution happens from indirectly included windows.h */
# ifdef _WIN32
# include "util/windows.h"
# endif
# include <OSL/oslexec.h>
#endif
#ifdef WITH_EMBREE
# include <embree4/rtcore.h>
#endif
#include "device/cpu/kernel.h"
#include "device/device.h"
#include "kernel/device/cpu/kernel.h"
#include "kernel/globals.h"
#include "kernel/types.h"
#include "bvh/embree.h"
#include "session/buffers.h"
#include "util/guiding.h"
#include "util/log.h"
#include "util/progress.h"
#include "util/task.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
CPUDevice::CPUDevice(const DeviceInfo &info_, Stats &stats_, Profiler &profiler_, bool headless_)
: Device(info_, stats_, profiler_, headless_)
{
image_info = make_unique<device_vector<KernelImageInfo>>(this, "image_info", MEM_GLOBAL);
/* Pick any kernel, all of them are supposed to have same level of microarchitecture
* optimization. */
LOG_INFO << "Using " << get_cpu_kernels().integrator_init_from_camera.get_uarch_name()
<< " CPU kernels.";
if (info.cpu_threads == 0) {
info.cpu_threads = TaskScheduler::max_concurrency();
}
#ifdef WITH_EMBREE
embree_device = rtcNewDevice("verbose=0");
#endif
}
CPUDevice::~CPUDevice()
{
#ifdef WITH_EMBREE
rtcReleaseDevice(embree_device);
#endif
image_info->free();
}
BVHLayoutMask CPUDevice::get_bvh_layout_mask(uint /*kernel_features*/) const
{
BVHLayoutMask bvh_layout_mask = BVH_LAYOUT_BVH2;
#ifdef WITH_EMBREE
bvh_layout_mask |= BVH_LAYOUT_EMBREE;
#endif /* WITH_EMBREE */
return bvh_layout_mask;
}
void CPUDevice::mem_alloc(device_memory &mem)
{
if (mem.type == MEM_IMAGE_TEXTURE) {
assert(!"mem_alloc not supported for images.");
}
else if (mem.type == MEM_GLOBAL) {
assert(!"mem_alloc not supported for global memory.");
}
else {
LOG_DEBUG << "Buffer allocate: " << mem.log_name() << ", "
<< string_human_readable_number(mem.memory_size()) << " bytes. ("
<< string_human_readable_size(mem.memory_size()) << ")";
if (mem.type == MEM_DEVICE_ONLY) {
size_t alignment = MIN_ALIGNMENT_DEVICE_MEMORY;
void *data = util_aligned_malloc(mem.memory_size(), alignment);
mem.device_pointer = (device_ptr)data;
}
else {
assert(!(mem.host_pointer == nullptr && mem.memory_size() > 0));
mem.device_pointer = (device_ptr)mem.host_pointer;
}
mem.device_size = mem.memory_size();
stats.mem_alloc(mem.device_size);
}
}
void CPUDevice::mem_copy_to(device_memory &mem)
{
if (mem.type == MEM_GLOBAL) {
global_free(mem);
global_alloc(mem);
}
else if (mem.type == MEM_IMAGE_TEXTURE) {
image_free((device_image &)mem);
image_alloc((device_image &)mem);
}
else {
if (!mem.device_pointer) {
mem_alloc(mem);
}
/* copy is no-op */
}
}
void CPUDevice::mem_move_to_host(device_memory & /*mem*/)
{
/* no-op */
}
void CPUDevice::mem_copy_from(
device_memory & /*mem*/, size_t /*y*/, size_t /*w*/, size_t /*h*/, size_t /*elem*/)
{
/* no-op */
}
void CPUDevice::mem_or_from_device(device_memory & /*mem*/)
{
/* Nothing to do data is already in host buffer. */
}
void CPUDevice::mem_zero(device_memory &mem)
{
if (!mem.device_pointer) {
mem_alloc(mem);
}
if (mem.device_pointer) {
memset((void *)mem.device_pointer, 0, mem.memory_size());
}
}
void CPUDevice::mem_free(device_memory &mem)
{
if (mem.type == MEM_GLOBAL) {
global_free(mem);
}
else if (mem.type == MEM_IMAGE_TEXTURE) {
image_free((device_image &)mem);
}
else if (mem.device_pointer) {
if (mem.type == MEM_DEVICE_ONLY) {
util_aligned_free((void *)mem.device_pointer, mem.memory_size());
}
mem.device_pointer = 0;
stats.mem_free(mem.device_size);
mem.device_size = 0;
}
}
device_ptr CPUDevice::mem_alloc_sub_ptr(device_memory &mem, const size_t offset, size_t /*size*/)
{
return (device_ptr)(((char *)mem.device_pointer) + mem.memory_elements_size(offset));
}
void CPUDevice::const_copy_to(const char *name, void *host, const size_t size)
{
#ifdef WITH_EMBREE
if (strcmp(name, "data") == 0) {
assert(size <= sizeof(KernelData));
/* Update scene handle (since it is different for each device on multi devices).
* This must be a raw pointer copy since at some points during scene update this
* pointer may be invalid. */
KernelData *const data = (KernelData *)host;
data->device_bvh = embree_traversable;
}
#endif
/* Update both the main one, and the per-thread globals in case of updates during
* render from e.g. the texture cache. */
kernel_const_copy(&kernel_globals, name, host, size);
for (ThreadKernelGlobalsCPU &kg : kernel_thread_globals_) {
kernel_const_copy(&kg, name, host, size);
}
}
void CPUDevice::global_alloc(device_memory &mem)
{
LOG_DEBUG << "Global memory allocate: " << mem.log_name() << ", "
<< string_human_readable_number(mem.memory_size()) << " bytes. ("
<< string_human_readable_size(mem.memory_size()) << ")";
/* Update both the main one, and the per-thread globals in case of updates during
* render from e.g. the texture cache. */
kernel_global_memory_copy(&kernel_globals, mem.global_name(), mem.host_pointer, mem.data_size);
for (ThreadKernelGlobalsCPU &kg : kernel_thread_globals_) {
kernel_global_memory_copy(&kg, mem.global_name(), mem.host_pointer, mem.data_size);
}
mem.device_pointer = (device_ptr)mem.host_pointer;
mem.device_size = mem.memory_size();
stats.mem_alloc(mem.device_size);
}
void CPUDevice::global_free(device_memory &mem)
{
if (mem.device_pointer) {
mem.device_pointer = 0;
stats.mem_free(mem.device_size);
mem.device_size = 0;
}
}
void CPUDevice::image_alloc(device_image &mem)
{
LOG_DEBUG << "Texture allocate: " << mem.log_name() << ", "
<< string_human_readable_number(mem.memory_size()) << " bytes. ("
<< string_human_readable_size(mem.memory_size()) << ")";
mem.device_pointer = (device_ptr)mem.host_pointer;
mem.device_size = mem.memory_size();
stats.mem_alloc(mem.device_size);
const uint image_info_id = mem.image_info_id;
if (image_info_id >= image_info->size()) {
/* Geometric growth to amortize reallocation cost. */
const size_t new_size = max(size_t(image_info_id) + 128, image_info->size() * 2);
unique_ptr<device_vector<KernelImageInfo>> new_info =
make_unique<device_vector<KernelImageInfo>>(this, "image_info", MEM_GLOBAL);
new_info->resize(new_size);
if (image_info->size() > 0) {
std::copy_n(image_info->data(), image_info->size(), new_info->data());
}
/* Move old vector to backup list to keep memory alive for concurrent access. */
old_image_infos.push_back(std::move(image_info));
image_info = std::move(new_info);
/* Update kernel globals pointers immediately. */
image_info->copy_to_device();
}
(*image_info)[image_info_id] = mem.info;
(*image_info)[image_info_id].data = (uint64_t)mem.host_pointer;
}
void CPUDevice::image_free(device_image &mem)
{
if (mem.device_pointer) {
mem.device_pointer = 0;
stats.mem_free(mem.device_size);
mem.device_size = 0;
}
}
bool CPUDevice::has_unified_memory() const
{
return true;
}
bool CPUDevice::has_unified_image_memory() const
{
return true;
}
void CPUDevice::build_bvh(BVH *bvh, Progress &progress, bool refit)
{
#ifdef WITH_EMBREE
if (bvh->params.bvh_layout == BVH_LAYOUT_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_OPTIX_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_METAL_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_HIPRT_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_EMBREEGPU_EMBREE)
{
BVHEmbree *const bvh_embree = static_cast<BVHEmbree *>(bvh);
if (refit) {
bvh_embree->refit(progress);
}
else {
bvh_embree->build(progress, &stats, embree_device);
}
if (bvh->params.top_level) {
# if RTC_VERSION >= 40400
embree_traversable = rtcGetSceneTraversable(bvh_embree->scene);
# else
embree_traversable = bvh_embree->scene;
# endif
}
}
else
#endif
{
Device::build_bvh(bvh, progress, refit);
}
}
void *CPUDevice::get_guiding_device() const
{
#if defined(WITH_PATH_GUIDING)
if (!guiding_device) {
if (guiding_device_type() == 8) {
guiding_device = make_unique<openpgl::cpp::Device>(PGL_DEVICE_TYPE_CPU_8);
}
else if (guiding_device_type() == 4) {
guiding_device = make_unique<openpgl::cpp::Device>(PGL_DEVICE_TYPE_CPU_4);
}
}
return guiding_device.get();
#else
return nullptr;
#endif
}
vector<ThreadKernelGlobalsCPU> *CPUDevice::acquire_cpu_kernel_thread_globals()
{
assert(kernel_thread_globals_.empty());
kernel_thread_globals_.clear();
OSLGlobals *osl_globals = get_cpu_osl_memory();
for (int i = 0; i < info.cpu_threads; i++) {
kernel_thread_globals_.emplace_back(kernel_globals, osl_globals, profiler, i);
}
return &kernel_thread_globals_;
}
void CPUDevice::release_cpu_kernel_thread_globals()
{
kernel_thread_globals_.clear();
old_image_infos.clear();
}
OSLGlobals *CPUDevice::get_cpu_osl_memory()
{
#ifdef WITH_OSL
return &osl_globals;
#else
return nullptr;
#endif
}
void CPUDevice::set_image_cache_func(KernelImageLoadRequestedCPU image_load_requested_cpu,
KernelImageLoadRequestedGPU /*image_load_requested_gpu*/)
{
kernel_globals.image_load_requested_cpu = image_load_requested_cpu;
}
bool CPUDevice::load_kernels(const uint /*kernel_features*/)
{
return true;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* So ImathMath is included before our kernel_cpu_compat. */
#ifdef WITH_OSL
# include <cstdint> /* Needed before `sdlexec.h` for `int32_t` with GCC 15.1. */
/* So no context pollution happens from indirectly included windows.h */
# ifdef _WIN32
# include "util/windows.h"
# endif
# include <OSL/oslexec.h>
#endif
#ifdef WITH_EMBREE
# include <embree4/rtcore.h>
#endif
#include "device/cpu/kernel.h"
#include "device/device.h"
#include "device/memory.h"
// clang-format off
#include "kernel/device/cpu/kernel.h"
#include "kernel/globals.h"
#include "kernel/osl/globals.h"
// clang-format on
#include "util/guiding.h" // IWYU pragma: keep
#include "util/list.h"
#include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
class CPUDevice : public Device {
public:
KernelGlobalsCPU kernel_globals;
vector<ThreadKernelGlobalsCPU> kernel_thread_globals_;
unique_ptr<device_vector<KernelImageInfo>> image_info;
list<unique_ptr<device_vector<KernelImageInfo>>> old_image_infos;
#ifdef WITH_OSL
OSLGlobals osl_globals;
#endif
#ifdef WITH_EMBREE
# if RTC_VERSION >= 40400
RTCTraversable embree_traversable = nullptr;
# else
RTCScene embree_traversable = nullptr;
# endif
RTCDevice embree_device;
#endif
#if defined(WITH_PATH_GUIDING)
mutable unique_ptr<openpgl::cpp::Device> guiding_device;
#endif
CPUDevice(const DeviceInfo &info_, Stats &stats_, Profiler &profiler_, bool headless_);
~CPUDevice() override;
BVHLayoutMask get_bvh_layout_mask(uint /*kernel_features*/) const override;
void mem_alloc(device_memory &mem) override;
void mem_copy_to(device_memory &mem) override;
void mem_move_to_host(device_memory &mem) override;
void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) override;
void mem_zero(device_memory &mem) override;
void mem_free(device_memory &mem) override;
void mem_or_from_device(device_memory &mem) override;
device_ptr mem_alloc_sub_ptr(device_memory &mem, const size_t offset, size_t /*size*/) override;
void const_copy_to(const char *name, void *host, const size_t size) override;
void global_alloc(device_memory &mem);
void global_free(device_memory &mem);
void image_alloc(device_image &mem);
void image_free(device_image &mem);
bool has_unified_memory() const override;
bool has_unified_image_memory() const override;
void build_bvh(BVH *bvh, Progress &progress, bool refit) override;
void *get_guiding_device() const override;
vector<ThreadKernelGlobalsCPU> *acquire_cpu_kernel_thread_globals() override;
void release_cpu_kernel_thread_globals() override;
OSLGlobals *get_cpu_osl_memory() override;
void set_image_cache_func(KernelImageLoadRequestedCPU image_load_requested_cpu,
KernelImageLoadRequestedGPU image_load_requested_gpu) override;
protected:
bool load_kernels(uint /*kernel_features*/) override;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/cpu/kernel.h"
#include "kernel/device/cpu/kernel.h"
CCL_NAMESPACE_BEGIN
#define KERNEL_FUNCTIONS(name) KERNEL_NAME_EVAL(cpu, name), KERNEL_NAME_EVAL(cpu_avx2, name)
#define REGISTER_KERNEL(name) name(KERNEL_FUNCTIONS(name))
#define REGISTER_KERNEL_FILM_CONVERT(name) \
film_convert_##name(KERNEL_FUNCTIONS(film_convert_##name)), \
film_convert_half_rgba_##name(KERNEL_FUNCTIONS(film_convert_half_rgba_##name))
CPUKernels::CPUKernels()
: /* Integrator. */
REGISTER_KERNEL(integrator_init_from_camera),
REGISTER_KERNEL(integrator_init_from_bake),
REGISTER_KERNEL(integrator_megakernel),
/* Shader evaluation. */
REGISTER_KERNEL(shader_eval_displace),
REGISTER_KERNEL(shader_eval_background),
REGISTER_KERNEL(shader_eval_curve_shadow_transparency),
REGISTER_KERNEL(shader_eval_volume_density),
/* Adaptive sampling. */
REGISTER_KERNEL(adaptive_sampling_convergence_check),
REGISTER_KERNEL(adaptive_sampling_filter_x),
REGISTER_KERNEL(adaptive_sampling_filter_y),
/* Volume Scattering Probability Guiding. */
REGISTER_KERNEL(volume_guiding_filter_x),
REGISTER_KERNEL(volume_guiding_filter_y),
/* Cryptomatte. */
REGISTER_KERNEL(cryptomatte_postprocess),
/* Film Convert. */
REGISTER_KERNEL_FILM_CONVERT(depth),
REGISTER_KERNEL_FILM_CONVERT(mist),
REGISTER_KERNEL_FILM_CONVERT(volume_majorant),
REGISTER_KERNEL_FILM_CONVERT(sample_count),
REGISTER_KERNEL_FILM_CONVERT(float),
REGISTER_KERNEL_FILM_CONVERT(light_path),
REGISTER_KERNEL_FILM_CONVERT(rgbe),
REGISTER_KERNEL_FILM_CONVERT(float3),
REGISTER_KERNEL_FILM_CONVERT(motion),
REGISTER_KERNEL_FILM_CONVERT(cryptomatte),
REGISTER_KERNEL_FILM_CONVERT(shadow_catcher),
REGISTER_KERNEL_FILM_CONVERT(shadow_catcher_matte_with_shadow),
REGISTER_KERNEL_FILM_CONVERT(combined),
REGISTER_KERNEL_FILM_CONVERT(float4)
{
}
#undef REGISTER_KERNEL
#undef REGISTER_KERNEL_FILM_CONVERT
#undef KERNEL_FUNCTIONS
CCL_NAMESPACE_END

View File

@@ -0,0 +1,137 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "device/cpu/kernel_function.h"
#include "util/half.h"
CCL_NAMESPACE_BEGIN
struct ThreadKernelGlobalsCPU;
struct KernelFilmConvert;
struct IntegratorStateCPU;
struct TileInfo;
class CPUKernels {
public:
/* Integrator. */
using IntegratorFunction =
CPUKernelFunction<void (*)(const ThreadKernelGlobalsCPU *kg, IntegratorStateCPU *state)>;
using IntegratorShadeFunction = CPUKernelFunction<void (*)(const ThreadKernelGlobalsCPU *kg,
IntegratorStateCPU *state,
ccl_global float *render_buffer)>;
using IntegratorInitFunction = CPUKernelFunction<bool (*)(const ThreadKernelGlobalsCPU *kg,
IntegratorStateCPU *state,
KernelWorkTile *tile,
ccl_global float *render_buffer)>;
IntegratorInitFunction integrator_init_from_camera;
IntegratorInitFunction integrator_init_from_bake;
IntegratorShadeFunction integrator_megakernel;
/* Shader evaluation. */
using ShaderEvalFunction = CPUKernelFunction<void (*)(
const ThreadKernelGlobalsCPU *kg, const KernelShaderEvalInput *, float *, const int)>;
ShaderEvalFunction shader_eval_displace;
ShaderEvalFunction shader_eval_background;
ShaderEvalFunction shader_eval_curve_shadow_transparency;
ShaderEvalFunction shader_eval_volume_density;
/* Adaptive stopping. */
using AdaptiveSamplingConvergenceCheckFunction =
CPUKernelFunction<bool (*)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int y,
const float threshold,
const int reset,
const int offset,
int stride)>;
using FilterXFunction = CPUKernelFunction<void (*)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int y,
const int start_x,
const int width,
const int offset,
int stride)>;
using FilterYFunction = CPUKernelFunction<void (*)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int start_y,
const int height,
const int offset,
int stride)>;
AdaptiveSamplingConvergenceCheckFunction adaptive_sampling_convergence_check;
FilterXFunction adaptive_sampling_filter_x;
FilterYFunction adaptive_sampling_filter_y;
/* Volume Scattering Probability Guiding. */
CPUKernelFunction<void (*)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int y,
const int center_x,
const int min_x,
const int max_x,
const int offset,
int stride)>
volume_guiding_filter_x;
FilterYFunction volume_guiding_filter_y;
/* Cryptomatte. */
using CryptomattePostprocessFunction = CPUKernelFunction<void (*)(
const ThreadKernelGlobalsCPU *kg, ccl_global float *render_buffer, const int pixel_index)>;
CryptomattePostprocessFunction cryptomatte_postprocess;
/* Film Convert. */
using FilmConvertFunction = CPUKernelFunction<void (*)(const KernelFilmConvert *kfilm_convert,
const float *buffer,
float *pixel,
const int width,
const int buffer_stride,
const int pixel_stride)>;
using FilmConvertHalfRGBAFunction =
CPUKernelFunction<void (*)(const KernelFilmConvert *kfilm_convert,
const float *buffer,
half4 *pixel,
const int width,
const int buffer_stride)>;
#define KERNEL_FILM_CONVERT_FUNCTION(name) \
FilmConvertFunction film_convert_##name; \
FilmConvertHalfRGBAFunction film_convert_half_rgba_##name;
KERNEL_FILM_CONVERT_FUNCTION(depth)
KERNEL_FILM_CONVERT_FUNCTION(mist)
KERNEL_FILM_CONVERT_FUNCTION(volume_majorant)
KERNEL_FILM_CONVERT_FUNCTION(sample_count)
KERNEL_FILM_CONVERT_FUNCTION(float)
KERNEL_FILM_CONVERT_FUNCTION(light_path)
KERNEL_FILM_CONVERT_FUNCTION(rgbe)
KERNEL_FILM_CONVERT_FUNCTION(float3)
KERNEL_FILM_CONVERT_FUNCTION(motion)
KERNEL_FILM_CONVERT_FUNCTION(cryptomatte)
KERNEL_FILM_CONVERT_FUNCTION(shadow_catcher)
KERNEL_FILM_CONVERT_FUNCTION(shadow_catcher_matte_with_shadow)
KERNEL_FILM_CONVERT_FUNCTION(combined)
KERNEL_FILM_CONVERT_FUNCTION(float4)
#undef KERNEL_FILM_CONVERT_FUNCTION
CPUKernels();
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/debug.h" // IWYU pragma: keep
#include "util/system.h" // IWYU pragma: keep
CCL_NAMESPACE_BEGIN
/* A wrapper around per-microarchitecture variant of a kernel function.
*
* Provides a function-call-like API which gets routed to the most suitable implementation.
*
* For example, on a computer which only has AVX2 the kernel_avx2 will be used. */
template<typename FunctionType> class CPUKernelFunction {
public:
CPUKernelFunction(FunctionType kernel_default, FunctionType kernel_avx2)
{
kernel_info_ = get_best_kernel_info(kernel_default, kernel_avx2);
}
template<typename... Args> auto operator()(Args... args) const
{
assert(kernel_info_.kernel);
return kernel_info_.kernel(args...);
}
const char *get_uarch_name() const
{
return kernel_info_.uarch_name;
}
protected:
/* Helper class which allows to pass human-readable microarchitecture name together with function
* pointer. */
class KernelInfo {
public:
KernelInfo() : KernelInfo("", nullptr) {}
/* TODO(sergey): Use string view, to have higher-level functionality (i.e. comparison) without
* memory allocation. */
KernelInfo(const char *uarch_name, FunctionType kernel)
: uarch_name(uarch_name), kernel(kernel)
{
}
const char *uarch_name;
FunctionType kernel;
};
KernelInfo get_best_kernel_info(FunctionType kernel_default, FunctionType kernel_avx2)
{
/* Silence warnings about unused variables when compiling without some architectures. */
(void)kernel_avx2;
#ifdef WITH_CYCLES_OPTIMIZED_KERNEL_AVX2
if (DebugFlags().cpu.has_avx2() && system_cpu_support_avx2()) {
return KernelInfo("AVX2", kernel_avx2);
}
#endif
return KernelInfo("default", kernel_default);
}
KernelInfo kernel_info_;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,360 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/cuda/device.h"
#include "device/device.h"
#include "util/log.h"
#ifdef WITH_CUDA
# include "device/cuda/device_impl.h"
# include "integrator/denoiser_oidn_gpu.h" // IWYU pragma: keep
# include "util/string.h"
# ifdef _WIN32
# include "util/windows.h"
# endif
#endif /* WITH_CUDA */
CCL_NAMESPACE_BEGIN
bool device_cuda_init()
{
#if !defined(WITH_CUDA)
return false;
#elif defined(WITH_CUDA_DYNLOAD)
static bool initialized = false;
static bool result = false;
if (initialized) {
return result;
}
initialized = true;
int cuew_result = cuewInit(CUEW_INIT_CUDA);
if (cuew_result == CUEW_SUCCESS) {
LOG_INFO << "CUEW initialization succeeded";
if (CUDADevice::have_precompiled_kernels()) {
LOG_INFO << "Found precompiled kernels";
result = true;
}
else if (cuewCompilerPath() != nullptr) {
LOG_INFO << "Found CUDA compiler " << cuewCompilerPath();
result = true;
}
else {
LOG_INFO << "Neither precompiled kernels nor CUDA compiler was found,"
<< " unable to use CUDA";
}
}
else {
LOG_WARNING << "CUEW initialization failed: "
<< ((cuew_result == CUEW_ERROR_ATEXIT_FAILED) ?
"Error setting up atexit() handler" :
"Error opening the library");
}
return result;
#else /* WITH_CUDA_DYNLOAD */
return true;
#endif /* WITH_CUDA_DYNLOAD */
}
unique_ptr<Device> device_cuda_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
#ifdef WITH_CUDA
return make_unique<CUDADevice>(info, stats, profiler, headless);
#else
(void)info;
(void)stats;
(void)profiler;
(void)headless;
LOG_FATAL << "Request to create CUDA device without compiled-in support. Should never happen.";
return nullptr;
#endif
}
#ifdef WITH_CUDA
static CUresult device_cuda_safe_init()
{
# ifdef _WIN32
__try
{
return cuInit(0);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
/* Ignore crashes inside the CUDA driver and hope we can
* survive even with corrupted CUDA installs. */
fprintf(stderr, "Cycles CUDA: driver crashed, continuing without CUDA.\n");
}
return CUDA_ERROR_NO_DEVICE;
# else
return cuInit(0);
# endif
}
#endif /* WITH_CUDA */
void device_cuda_info(vector<DeviceInfo> &devices)
{
#ifdef WITH_CUDA
CUresult result = device_cuda_safe_init();
if (result != CUDA_SUCCESS) {
if (result != CUDA_ERROR_NO_DEVICE) {
LOG_ERROR << "CUDA cuInit: " << cuewErrorString(result);
}
return;
}
int count = 0;
result = cuDeviceGetCount(&count);
if (result != CUDA_SUCCESS) {
LOG_ERROR << "CUDA cuDeviceGetCount: " << cuewErrorString(result);
return;
}
vector<DeviceInfo> display_devices;
for (int num = 0; num < count; num++) {
char name[256];
result = cuDeviceGetName(name, 256, num);
if (result != CUDA_SUCCESS) {
LOG_ERROR << "CUDA cuDeviceGetName: " << cuewErrorString(result);
continue;
}
if (!cudaSupportsDevice(num)) {
LOG_INFO << "Ignoring device \"" << name << "\", this graphics card is no longer supported.";
continue;
}
DeviceInfo info;
info.type = DEVICE_CUDA;
info.description = string(name);
info.num = num;
info.has_nanovdb = true;
info.denoisers = 0;
info.has_gpu_queue = true;
/* Check if the device has P2P access to any other device in the system. */
for (int peer_num = 0; peer_num < count && !info.has_peer_memory; peer_num++) {
if (num != peer_num) {
if (cudaSupportsDevice(peer_num)) {
int can_access = 0;
cuDeviceCanAccessPeer(&can_access, num, peer_num);
info.has_peer_memory = (can_access != 0);
}
}
}
int pci_location[3] = {0, 0, 0};
cuDeviceGetAttribute(&pci_location[0], CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID, num);
cuDeviceGetAttribute(&pci_location[1], CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, num);
cuDeviceGetAttribute(&pci_location[2], CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID, num);
info.id = string_printf("CUDA_%s_%04x:%02x:%02x",
name,
(unsigned int)pci_location[0],
(unsigned int)pci_location[1],
(unsigned int)pci_location[2]);
# if defined(WITH_OPENIMAGEDENOISE)
# if OIDN_VERSION >= 20300
if (oidnIsCUDADeviceSupported(num)) {
# else
if (OIDNDenoiserGPU::is_device_supported(info)) {
# endif
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
}
# endif
/* If device has a kernel timeout and no compute preemption, we assume
* it is connected to a display and will freeze the display while doing
* computations. */
int timeout_attr = 0, preempt_attr = 0;
cuDeviceGetAttribute(&timeout_attr, CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT, num);
cuDeviceGetAttribute(&preempt_attr, CU_DEVICE_ATTRIBUTE_COMPUTE_PREEMPTION_SUPPORTED, num);
# ifdef _WIN32
int major;
cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, num);
/* The CUDA driver reports compute preemption as not being available on
* Windows 10 even when it is, due to an issue in application profiles.
* Detect case where we expect it to be available and override. */
if (preempt_attr == 0 && (major >= 6) && system_windows_version_at_least(10, 17134)) {
LOG_INFO << "Assuming device has compute preemption on Windows 10.";
preempt_attr = 1;
}
# endif
if (timeout_attr && !preempt_attr) {
LOG_INFO << "Device is recognized as display.";
info.description += " (Display)";
info.display_device = true;
display_devices.push_back(info);
}
else {
LOG_INFO << "Device has compute preemption or is not used for display.";
devices.push_back(info);
}
LOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE) {
LOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}
}
if (!display_devices.empty()) {
devices.insert(devices.end(), display_devices.begin(), display_devices.end());
}
#else /* WITH_CUDA */
(void)devices;
#endif /* WITH_CUDA */
}
string device_cuda_capabilities()
{
#ifdef WITH_CUDA
CUresult result = device_cuda_safe_init();
if (result != CUDA_SUCCESS) {
if (result != CUDA_ERROR_NO_DEVICE) {
return string("Error initializing CUDA: ") + cuewErrorString(result);
}
return "No CUDA device found\n";
}
int count;
result = cuDeviceGetCount(&count);
if (result != CUDA_SUCCESS) {
return string("Error getting devices: ") + cuewErrorString(result);
}
string capabilities;
for (int num = 0; num < count; num++) {
char name[256];
if (cuDeviceGetName(name, 256, num) != CUDA_SUCCESS) {
continue;
}
capabilities += string("\t") + name + "\n";
int value;
# define GET_ATTR(attr) \
{ \
if (cuDeviceGetAttribute(&value, CU_DEVICE_ATTRIBUTE_##attr, num) == CUDA_SUCCESS) { \
capabilities += string_printf("\t\tCU_DEVICE_ATTRIBUTE_" #attr "\t\t\t%d\n", value); \
} \
} \
(void)0
/* TODO(sergey): Strip all attributes which are not useful for us
* or does not depend on the driver.
*/
GET_ATTR(MAX_THREADS_PER_BLOCK);
GET_ATTR(MAX_BLOCK_DIM_X);
GET_ATTR(MAX_BLOCK_DIM_Y);
GET_ATTR(MAX_BLOCK_DIM_Z);
GET_ATTR(MAX_GRID_DIM_X);
GET_ATTR(MAX_GRID_DIM_Y);
GET_ATTR(MAX_GRID_DIM_Z);
GET_ATTR(MAX_SHARED_MEMORY_PER_BLOCK);
GET_ATTR(SHARED_MEMORY_PER_BLOCK);
GET_ATTR(TOTAL_CONSTANT_MEMORY);
GET_ATTR(WARP_SIZE);
GET_ATTR(MAX_PITCH);
GET_ATTR(MAX_REGISTERS_PER_BLOCK);
GET_ATTR(REGISTERS_PER_BLOCK);
GET_ATTR(CLOCK_RATE);
GET_ATTR(TEXTURE_ALIGNMENT);
GET_ATTR(GPU_OVERLAP);
GET_ATTR(MULTIPROCESSOR_COUNT);
GET_ATTR(KERNEL_EXEC_TIMEOUT);
GET_ATTR(INTEGRATED);
GET_ATTR(CAN_MAP_HOST_MEMORY);
GET_ATTR(COMPUTE_MODE);
GET_ATTR(MAXIMUM_TEXTURE1D_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_HEIGHT);
GET_ATTR(MAXIMUM_TEXTURE3D_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE3D_HEIGHT);
GET_ATTR(MAXIMUM_TEXTURE3D_DEPTH);
GET_ATTR(MAXIMUM_TEXTURE2D_LAYERED_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_LAYERED_HEIGHT);
GET_ATTR(MAXIMUM_TEXTURE2D_LAYERED_LAYERS);
GET_ATTR(MAXIMUM_TEXTURE2D_ARRAY_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_ARRAY_HEIGHT);
GET_ATTR(MAXIMUM_TEXTURE2D_ARRAY_NUMSLICES);
GET_ATTR(SURFACE_ALIGNMENT);
GET_ATTR(CONCURRENT_KERNELS);
GET_ATTR(ECC_ENABLED);
GET_ATTR(TCC_DRIVER);
GET_ATTR(MEMORY_CLOCK_RATE);
GET_ATTR(GLOBAL_MEMORY_BUS_WIDTH);
GET_ATTR(L2_CACHE_SIZE);
GET_ATTR(MAX_THREADS_PER_MULTIPROCESSOR);
GET_ATTR(ASYNC_ENGINE_COUNT);
GET_ATTR(UNIFIED_ADDRESSING);
GET_ATTR(MAXIMUM_TEXTURE1D_LAYERED_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE1D_LAYERED_LAYERS);
GET_ATTR(CAN_TEX2D_GATHER);
GET_ATTR(MAXIMUM_TEXTURE2D_GATHER_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_GATHER_HEIGHT);
GET_ATTR(MAXIMUM_TEXTURE3D_WIDTH_ALTERNATE);
GET_ATTR(MAXIMUM_TEXTURE3D_HEIGHT_ALTERNATE);
GET_ATTR(MAXIMUM_TEXTURE3D_DEPTH_ALTERNATE);
GET_ATTR(TEXTURE_PITCH_ALIGNMENT);
GET_ATTR(MAXIMUM_TEXTURECUBEMAP_WIDTH);
GET_ATTR(MAXIMUM_TEXTURECUBEMAP_LAYERED_WIDTH);
GET_ATTR(MAXIMUM_TEXTURECUBEMAP_LAYERED_LAYERS);
GET_ATTR(MAXIMUM_SURFACE1D_WIDTH);
GET_ATTR(MAXIMUM_SURFACE2D_WIDTH);
GET_ATTR(MAXIMUM_SURFACE2D_HEIGHT);
GET_ATTR(MAXIMUM_SURFACE3D_WIDTH);
GET_ATTR(MAXIMUM_SURFACE3D_HEIGHT);
GET_ATTR(MAXIMUM_SURFACE3D_DEPTH);
GET_ATTR(MAXIMUM_SURFACE1D_LAYERED_WIDTH);
GET_ATTR(MAXIMUM_SURFACE1D_LAYERED_LAYERS);
GET_ATTR(MAXIMUM_SURFACE2D_LAYERED_WIDTH);
GET_ATTR(MAXIMUM_SURFACE2D_LAYERED_HEIGHT);
GET_ATTR(MAXIMUM_SURFACE2D_LAYERED_LAYERS);
GET_ATTR(MAXIMUM_SURFACECUBEMAP_WIDTH);
GET_ATTR(MAXIMUM_SURFACECUBEMAP_LAYERED_WIDTH);
GET_ATTR(MAXIMUM_SURFACECUBEMAP_LAYERED_LAYERS);
GET_ATTR(MAXIMUM_TEXTURE1D_LINEAR_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_LINEAR_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_LINEAR_HEIGHT);
GET_ATTR(MAXIMUM_TEXTURE2D_LINEAR_PITCH);
GET_ATTR(MAXIMUM_TEXTURE2D_MIPMAPPED_WIDTH);
GET_ATTR(MAXIMUM_TEXTURE2D_MIPMAPPED_HEIGHT);
GET_ATTR(COMPUTE_CAPABILITY_MAJOR);
GET_ATTR(COMPUTE_CAPABILITY_MINOR);
GET_ATTR(MAXIMUM_TEXTURE1D_MIPMAPPED_WIDTH);
GET_ATTR(STREAM_PRIORITIES_SUPPORTED);
GET_ATTR(GLOBAL_L1_CACHE_SUPPORTED);
GET_ATTR(LOCAL_L1_CACHE_SUPPORTED);
GET_ATTR(MAX_SHARED_MEMORY_PER_MULTIPROCESSOR);
GET_ATTR(MAX_REGISTERS_PER_MULTIPROCESSOR);
GET_ATTR(MANAGED_MEMORY);
GET_ATTR(MULTI_GPU_BOARD);
GET_ATTR(MULTI_GPU_BOARD_GROUP_ID);
# undef GET_ATTR
capabilities += "\n";
}
return capabilities;
#else /* WITH_CUDA */
return "";
#endif /* WITH_CUDA */
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/string.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
bool device_cuda_init();
unique_ptr<Device> device_cuda_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
void device_cuda_info(vector<DeviceInfo> &devices);
string device_cuda_capabilities();
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_CUDA
# include "device/cuda/kernel.h"
# include "device/cuda/queue.h"
# include "device/cuda/util.h"
# include "device/device.h"
# ifdef WITH_CUDA_DYNLOAD
# include "cuew.h"
# else
# include <cuda.h>
# include <cudaGL.h>
# endif
CCL_NAMESPACE_BEGIN
class DeviceQueue;
class CUDADevice : public GPUDevice {
friend class CUDAContextScope;
public:
CUdevice cuDevice;
CUcontext cuContext;
CUmodule cuModule;
int pitch_alignment;
int cuDevId;
int cuDevArchitecture;
bool first_error;
CUDADeviceKernels kernels;
static bool have_precompiled_kernels();
BVHLayoutMask get_bvh_layout_mask(uint /*kernel_features*/) const override;
void set_error(const string &error) override;
CUDADevice(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless);
~CUDADevice() override;
bool support_device(const uint /*kernel_features*/);
bool check_peer_access(Device *peer_device) override;
bool use_adaptive_compilation();
string compile_kernel_get_common_cflags(const uint kernel_features);
string compile_kernel(const string &cflags, const char *name, bool optix = false);
bool load_kernels(const uint kernel_features) override;
void reserve_local_memory(const uint kernel_features);
/* All memory types. */
void mem_alloc(device_memory &mem) override;
void mem_copy_to(device_memory &mem) override;
void mem_move_to_host(device_memory &mem) override;
void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) override;
void mem_zero(device_memory &mem) override;
void mem_free(device_memory &mem) override;
device_ptr mem_alloc_sub_ptr(device_memory &mem, const size_t offset, size_t /*size*/) override;
/* Global memory. */
void global_alloc(device_memory &mem);
void global_copy_to(device_memory &mem);
void global_free(device_memory &mem);
/* Image memory. */
void image_alloc(device_image &mem);
void image_copy_to(device_image &mem);
void image_free(device_image &mem);
/* Device side memory. */
void get_device_memory_info(size_t &total, size_t &free) override;
bool alloc_device(void *&device_pointer, const size_t size) override;
void free_device(void *device_pointer) override;
/* Shared memory. */
bool shared_alloc(void *&shared_pointer, const size_t size) override;
void shared_free(void *shared_pointer) override;
void *shared_to_device_pointer(const void *shared_pointer) override;
/* Memory copy. */
void copy_host_to_device(void *device_pointer, void *host_pointer, const size_t size) override;
void const_copy_to(const char *name, void *host, const size_t size) override;
bool should_use_graphics_interop(const GraphicsInteropDevice &interop_device,
const bool log) override;
unique_ptr<DeviceQueue> gpu_queue_create() override;
int get_num_multiprocessors();
int get_max_num_threads_per_multiprocessor();
protected:
bool get_device_attribute(CUdevice_attribute attribute, int *value);
int get_device_default_attribute(CUdevice_attribute attribute, const int default_value);
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,186 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_CUDA
# include "device/cuda/graphics_interop.h"
# include "device/cuda/device_impl.h"
# include "device/cuda/util.h"
# include "session/display_driver.h"
# ifdef _WIN32
# include "util/windows.h"
# else
# include <unistd.h>
# endif
CCL_NAMESPACE_BEGIN
CUDADeviceGraphicsInterop::CUDADeviceGraphicsInterop(CUDADeviceQueue *queue)
: queue_(queue), device_(static_cast<CUDADevice *>(queue->device))
{
}
CUDADeviceGraphicsInterop::~CUDADeviceGraphicsInterop()
{
CUDAContextScope scope(device_);
free();
}
void CUDADeviceGraphicsInterop::set_buffer(GraphicsInteropBuffer &interop_buffer)
{
CUDAContextScope scope(device_);
if (interop_buffer.is_empty()) {
free();
return;
}
need_zero_ |= interop_buffer.take_zero();
if (!interop_buffer.has_new_handle()) {
return;
}
free();
switch (interop_buffer.get_type()) {
case GraphicsInteropDevice::OPENGL: {
const CUresult result = cuGraphicsGLRegisterBuffer(&cu_graphics_resource_,
interop_buffer.take_handle(),
CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
if (result != CUDA_SUCCESS) {
LOG_ERROR << "Error registering OpenGL buffer: " << cuewErrorString(result);
break;
}
buffer_size_ = interop_buffer.get_size();
break;
}
case GraphicsInteropDevice::VULKAN: {
CUDA_EXTERNAL_MEMORY_HANDLE_DESC external_memory_handle_desc = {};
# ifdef _WIN32
/* cuImportExternalMemory will not take ownership of the handle. */
vulkan_windows_handle_ = interop_buffer.take_handle();
external_memory_handle_desc.type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32;
external_memory_handle_desc.handle.win32.handle = reinterpret_cast<void *>(
vulkan_windows_handle_);
# else
/* cuImportExternalMemory will take ownership of the handle. */
external_memory_handle_desc.type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD;
external_memory_handle_desc.handle.fd = interop_buffer.take_handle();
# endif
external_memory_handle_desc.size = interop_buffer.get_size();
CUresult result = cuImportExternalMemory(&cu_external_memory_, &external_memory_handle_desc);
if (result != CUDA_SUCCESS) {
# ifdef _WIN32
CloseHandle(HANDLE(vulkan_windows_handle_));
vulkan_windows_handle_ = 0;
# else
close(external_memory_handle_desc.handle.fd);
# endif
LOG_ERROR << "Error importing Vulkan memory: " << cuewErrorString(result);
break;
}
buffer_size_ = interop_buffer.get_size();
CUDA_EXTERNAL_MEMORY_BUFFER_DESC external_memory_buffer_desc = {};
external_memory_buffer_desc.size = external_memory_handle_desc.size;
external_memory_buffer_desc.offset = 0;
CUdeviceptr external_memory_device_ptr = 0;
result = cuExternalMemoryGetMappedBuffer(
&external_memory_device_ptr, cu_external_memory_, &external_memory_buffer_desc);
if (result != CUDA_SUCCESS) {
if (external_memory_device_ptr) {
cuMemFree(external_memory_device_ptr);
external_memory_device_ptr = 0;
}
LOG_ERROR << "Error mapping Vulkan memory: " << cuewErrorString(result);
break;
}
cu_external_memory_ptr_ = external_memory_device_ptr;
break;
}
case GraphicsInteropDevice::METAL:
case GraphicsInteropDevice::NONE:
break;
}
}
device_ptr CUDADeviceGraphicsInterop::map()
{
CUdeviceptr cu_buffer = 0;
if (cu_graphics_resource_) {
/* OpenGL buffer needs mapping. */
CUDAContextScope scope(device_);
size_t bytes;
cuda_device_assert(device_,
cuGraphicsMapResources(1, &cu_graphics_resource_, queue_->stream()));
cuda_device_assert(
device_, cuGraphicsResourceGetMappedPointer(&cu_buffer, &bytes, cu_graphics_resource_));
}
else {
/* Vulkan buffer is always mapped. */
cu_buffer = cu_external_memory_ptr_;
}
if (cu_buffer && need_zero_) {
cuda_device_assert(device_, cuMemsetD8Async(cu_buffer, 0, buffer_size_, queue_->stream()));
need_zero_ = false;
}
return static_cast<device_ptr>(cu_buffer);
}
void CUDADeviceGraphicsInterop::unmap()
{
if (cu_graphics_resource_) {
CUDAContextScope scope(device_);
cuda_device_assert(device_,
cuGraphicsUnmapResources(1, &cu_graphics_resource_, queue_->stream()));
}
}
void CUDADeviceGraphicsInterop::free()
{
if (cu_graphics_resource_) {
cuda_device_assert(device_, cuGraphicsUnregisterResource(cu_graphics_resource_));
cu_graphics_resource_ = nullptr;
}
if (cu_external_memory_ptr_) {
cuda_device_assert(device_, cuMemFree(cu_external_memory_ptr_));
cu_external_memory_ptr_ = 0;
}
if (cu_external_memory_) {
cuda_device_assert(device_, cuDestroyExternalMemory(cu_external_memory_));
cu_external_memory_ = nullptr;
}
# ifdef _WIN32
if (vulkan_windows_handle_) {
CloseHandle(HANDLE(vulkan_windows_handle_));
vulkan_windows_handle_ = 0;
}
# endif
buffer_size_ = 0;
need_zero_ = false;
}
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_CUDA
# include "device/graphics_interop.h"
# include "session/display_driver.h"
# ifdef WITH_CUDA_DYNLOAD
# include "cuew.h"
# else
# include <cuda.h>
# endif
CCL_NAMESPACE_BEGIN
class CUDADevice;
class CUDADeviceQueue;
class CUDADeviceGraphicsInterop : public DeviceGraphicsInterop {
public:
explicit CUDADeviceGraphicsInterop(CUDADeviceQueue *queue);
CUDADeviceGraphicsInterop(const CUDADeviceGraphicsInterop &other) = delete;
CUDADeviceGraphicsInterop(CUDADeviceGraphicsInterop &&other) noexcept = delete;
~CUDADeviceGraphicsInterop() override;
CUDADeviceGraphicsInterop &operator=(const CUDADeviceGraphicsInterop &other) = delete;
CUDADeviceGraphicsInterop &operator=(CUDADeviceGraphicsInterop &&other) = delete;
void set_buffer(GraphicsInteropBuffer &interop_buffer) override;
device_ptr map() override;
void unmap() override;
protected:
CUDADeviceQueue *queue_ = nullptr;
CUDADevice *device_ = nullptr;
/* Size of the buffer in bytes. */
size_t buffer_size_ = 0;
/* The destination was requested to be cleared. */
bool need_zero_ = false;
/* CUDA resources. */
CUgraphicsResource cu_graphics_resource_ = nullptr;
CUexternalMemory cu_external_memory_ = nullptr;
CUdeviceptr cu_external_memory_ptr_ = 0;
/* Vulkan handle to free. */
# ifdef _WIN32
int64_t vulkan_windows_handle_ = 0;
# endif
void free();
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_CUDA
# include "device/cuda/kernel.h"
# include "device/cuda/device_impl.h"
CCL_NAMESPACE_BEGIN
void CUDADeviceKernels::load(CUDADevice *device)
{
CUmodule cuModule = device->cuModule;
for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) {
CUDADeviceKernel &kernel = kernels_[i];
if (!device_kernel_has_gpu_function((DeviceKernel)i)) {
continue;
}
const std::string function_name = std::string("kernel_gpu_") +
device_kernel_as_string((DeviceKernel)i);
cuda_device_assert(device,
cuModuleGetFunction(&kernel.function, cuModule, function_name.c_str()));
if (kernel.function) {
cuda_device_assert(device, cuFuncSetCacheConfig(kernel.function, CU_FUNC_CACHE_PREFER_L1));
cuda_device_assert(
device,
cuOccupancyMaxPotentialBlockSize(
&kernel.min_blocks, &kernel.num_threads_per_block, kernel.function, nullptr, 0, 0));
}
else {
LOG_ERROR << "Unable to load kernel " << function_name;
}
}
loaded = true;
}
const CUDADeviceKernel &CUDADeviceKernels::get(DeviceKernel kernel) const
{
return kernels_[(int)kernel];
}
bool CUDADeviceKernels::available(DeviceKernel kernel) const
{
return kernels_[(int)kernel].function != nullptr;
}
CCL_NAMESPACE_END
#endif /* WITH_CUDA */

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_CUDA
# include "device/kernel.h"
# ifdef WITH_CUDA_DYNLOAD
# include "cuew.h"
# else
# include <cuda.h>
# endif
CCL_NAMESPACE_BEGIN
class CUDADevice;
/* CUDA kernel and associate occupancy information. */
class CUDADeviceKernel {
public:
CUfunction function = nullptr;
int num_threads_per_block = 0;
int min_blocks = 0;
};
/* Cache of CUDA kernels for each DeviceKernel. */
class CUDADeviceKernels {
public:
void load(CUDADevice *device);
const CUDADeviceKernel &get(DeviceKernel kernel) const;
bool available(DeviceKernel kernel) const;
protected:
CUDADeviceKernel kernels_[DEVICE_KERNEL_NUM];
bool loaded = false;
};
CCL_NAMESPACE_END
#endif /* WITH_CUDA */

View File

@@ -0,0 +1,271 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_CUDA
# include "device/cuda/queue.h"
# include "device/cuda/device_impl.h"
# include "device/cuda/graphics_interop.h"
# include "device/cuda/kernel.h"
CCL_NAMESPACE_BEGIN
/* CUDADeviceQueue */
CUDADeviceQueue::CUDADeviceQueue(CUDADevice *device)
: DeviceQueue(device), cuda_device_(device), cuda_stream_(nullptr)
{
const CUDAContextScope scope(cuda_device_);
cuda_device_assert(cuda_device_, cuStreamCreate(&cuda_stream_, CU_STREAM_NON_BLOCKING));
}
CUDADeviceQueue::~CUDADeviceQueue()
{
const CUDAContextScope scope(cuda_device_);
cuStreamDestroy(cuda_stream_);
}
int CUDADeviceQueue::num_concurrent_states(const size_t state_size) const
{
const int max_num_threads = cuda_device_->get_num_multiprocessors() *
cuda_device_->get_max_num_threads_per_multiprocessor();
int num_states = max(max_num_threads, 65536) * 16;
const char *factor_str = getenv("CYCLES_CONCURRENT_STATES_FACTOR");
if (factor_str) {
const float factor = (float)atof(factor_str);
if (factor != 0.0f) {
num_states = max((int)(num_states * factor), 1024);
}
else {
LOG_TRACE << "CYCLES_CONCURRENT_STATES_FACTOR evaluated to 0";
}
}
LOG_TRACE << "GPU queue concurrent states: " << num_states << ", using up to "
<< string_human_readable_size(num_states * state_size);
return num_states;
}
int CUDADeviceQueue::num_concurrent_busy_states(const size_t /*state_size*/) const
{
const int max_num_threads = cuda_device_->get_num_multiprocessors() *
cuda_device_->get_max_num_threads_per_multiprocessor();
if (max_num_threads == 0) {
return 65536;
}
return 4 * max_num_threads;
}
void CUDADeviceQueue::init_execution()
{
/* Synchronize all textures and memory copies before executing task.
* Use default stream (nullptr) since that's what we will synchronize
* here to ensure all scene data is copied. */
CUDAContextScope scope(cuda_device_);
cuda_device_->load_image_info(nullptr);
cuda_device_assert(cuda_device_, cuCtxSynchronize());
debug_init_execution();
}
void CUDADeviceQueue::load_image_info()
{
CUDAContextScope scope(cuda_device_);
cuda_device_->load_image_info(this);
}
bool CUDADeviceQueue::enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args)
{
if (cuda_device_->have_error()) {
return false;
}
debug_enqueue_begin(kernel, work_size);
const CUDAContextScope scope(cuda_device_);
/* Update image info in case integrator memory alloc caused texture to move to host. */
if (cuda_device_->load_image_info(nullptr)) {
cuda_device_assert(cuda_device_, cuCtxSynchronize());
if (cuda_device_->have_error()) {
return false;
}
}
/* Compute kernel launch parameters. */
const CUDADeviceKernel &cuda_kernel = cuda_device_->kernels.get(kernel);
const int num_threads_per_block = cuda_kernel.num_threads_per_block;
const int num_blocks = divide_up(work_size, num_threads_per_block);
int shared_mem_bytes = 0;
switch (kernel) {
case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_PATHS_ARRAY:
/* See parall_active_index.h for why this amount of shared memory is needed. */
shared_mem_bytes = (num_threads_per_block + 1) * sizeof(int);
break;
default:
break;
}
/* Launch kernel. */
assert_success(cuLaunchKernel(cuda_kernel.function,
num_blocks,
1,
1,
num_threads_per_block,
1,
1,
shared_mem_bytes,
cuda_stream_,
const_cast<void **>(args.values),
nullptr),
"enqueue");
debug_enqueue_end();
return !(cuda_device_->have_error());
}
bool CUDADeviceQueue::synchronize()
{
if (cuda_device_->have_error()) {
return false;
}
const CUDAContextScope scope(cuda_device_);
assert_success(cuStreamSynchronize(cuda_stream_), "synchronize");
debug_synchronize();
return !(cuda_device_->have_error());
}
void CUDADeviceQueue::zero_to_device(device_memory &mem)
{
assert(mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
/* Allocate on demand. */
if (mem.device_pointer == 0) {
if (mem.type == MEM_GLOBAL) {
cuda_device_->global_alloc(mem);
}
else {
cuda_device_->mem_alloc(mem);
}
}
/* Zero memory on device. */
device_ptr d_ptr = mem.device->mem_device_ptr(mem, cuda_device_);
assert(d_ptr != 0);
const CUDAContextScope scope(cuda_device_);
assert_success(cuMemsetD8Async((CUdeviceptr)d_ptr, 0, mem.memory_size(), cuda_stream_),
"zero_to_device");
}
void CUDADeviceQueue::copy_to_device(device_memory &mem)
{
assert(mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
/* Allocate on demand. */
if (mem.device_pointer == 0) {
if (mem.type == MEM_GLOBAL) {
cuda_device_->global_alloc(mem);
}
else {
cuda_device_->mem_alloc(mem);
}
}
device_ptr d_ptr = mem.device->mem_device_ptr(mem, cuda_device_);
assert(d_ptr != 0);
assert(mem.host_pointer != nullptr);
/* Copy memory to device. */
const CUDAContextScope scope(cuda_device_);
assert_success(
cuMemcpyHtoDAsync((CUdeviceptr)d_ptr, mem.host_pointer, mem.memory_size(), cuda_stream_),
"copy_to_device");
}
void CUDADeviceQueue::copy_from_device(device_memory &mem)
{
assert(mem.type != MEM_GLOBAL && mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
assert(mem.device_pointer != 0);
assert(mem.host_pointer != nullptr);
/* Copy memory from device. */
const CUDAContextScope scope(cuda_device_);
assert_success(
cuMemcpyDtoHAsync(
mem.host_pointer, (CUdeviceptr)mem.device_pointer, mem.memory_size(), cuda_stream_),
"copy_from_device");
}
void *CUDADeviceQueue::copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage)
{
if (mem.memory_size() == 0) {
return nullptr;
}
storage.resize(mem.memory_size());
device_ptr d_ptr = mem.device->mem_device_ptr(mem, cuda_device_);
assert(d_ptr != 0);
const CUDAContextScope scope(cuda_device_);
assert_success(
cuMemcpyDtoHAsync(storage.data(), (CUdeviceptr)d_ptr, mem.memory_size(), cuda_stream_),
"copy_from_device_synchronized");
synchronize();
return storage.data();
}
void CUDADeviceQueue::assert_success(CUresult result, const char *operation)
{
if (result != CUDA_SUCCESS) {
const char *name = cuewErrorString(result);
cuda_device_->set_error(string_printf(
"%s in CUDA queue %s (%s)", name, operation, debug_active_kernels().c_str()));
}
}
unique_ptr<DeviceGraphicsInterop> CUDADeviceQueue::graphics_interop_create()
{
return make_unique<CUDADeviceGraphicsInterop>(this);
}
CCL_NAMESPACE_END
#endif /* WITH_CUDA */

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_CUDA
# include "device/memory.h"
# include "device/queue.h"
# include "device/cuda/util.h"
CCL_NAMESPACE_BEGIN
class CUDADevice;
class device_memory;
/* Base class for CUDA queues. */
class CUDADeviceQueue : public DeviceQueue {
public:
CUDADeviceQueue(CUDADevice *device);
~CUDADeviceQueue() override;
int num_concurrent_states(const size_t state_size) const override;
int num_concurrent_busy_states(const size_t state_size) const override;
void init_execution() override;
void load_image_info() override;
bool enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args) override;
bool synchronize() override;
void zero_to_device(device_memory &mem) override;
void copy_to_device(device_memory &mem) override;
void copy_from_device(device_memory &mem) override;
void *copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage) override;
virtual CUstream stream()
{
return cuda_stream_;
}
unique_ptr<DeviceGraphicsInterop> graphics_interop_create() override;
protected:
CUDADevice *cuda_device_;
CUstream cuda_stream_;
void assert_success(CUresult result, const char *operation);
};
CCL_NAMESPACE_END
#endif /* WITH_CUDA */

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_CUDA
# include "device/cuda/util.h"
# include "device/cuda/device_impl.h"
CCL_NAMESPACE_BEGIN
CUDAContextScope::CUDAContextScope(CUDADevice *device) : device(device)
{
cuda_device_assert(device, cuCtxPushCurrent(device->cuContext));
}
CUDAContextScope::~CUDAContextScope()
{
cuda_device_assert(device, cuCtxPopCurrent(nullptr));
}
# ifndef WITH_CUDA_DYNLOAD
const char *cuewErrorString(CUresult result)
{
/* We can only give error code here without major code duplication, that
* should be enough since dynamic loading is only being disabled by folks
* who knows what they're doing anyway.
*
* NOTE: Avoid call from several threads.
*/
static string error;
error = string_printf("%d", result);
return error.c_str();
}
const char *cuewCompilerPath()
{
return CYCLES_CUDA_NVCC_EXECUTABLE;
}
int cuewCompilerVersion()
{
return (CUDA_VERSION / 100) + (CUDA_VERSION % 100 / 10);
}
# endif
CCL_NAMESPACE_END
#endif /* WITH_CUDA */

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_CUDA
# ifdef WITH_CUDA_DYNLOAD
# include <cuew.h>
# else
# include <cuda.h>
# endif
CCL_NAMESPACE_BEGIN
class CUDADevice;
/* Utility to push/pop CUDA context. */
class CUDAContextScope {
public:
CUDAContextScope(CUDADevice *device);
~CUDAContextScope();
private:
CUDADevice *device;
};
/* Utility for checking return values of CUDA function calls. */
# define cuda_device_assert(cuda_device, stmt) \
{ \
CUresult result = stmt; \
if (result != CUDA_SUCCESS) { \
const char *name = cuewErrorString(result); \
cuda_device->set_error( \
string_printf("%s in %s (%s:%d)", name, #stmt, __FILE__, __LINE__)); \
} \
} \
(void)0
# define cuda_assert(stmt) cuda_device_assert(this, stmt)
# ifndef WITH_CUDA_DYNLOAD
/* Transparently implement some functions, so majority of the file does not need
* to worry about difference between dynamically loaded and linked CUDA at all. */
const char *cuewErrorString(CUresult result);
const char *cuewCompilerPath();
int cuewCompilerVersion();
# endif /* WITH_CUDA_DYNLOAD */
static inline bool cudaSupportsDevice(const int cudaDevID)
{
int major;
cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, cudaDevID);
if (major >= 5) {
return true;
}
return false;
}
CCL_NAMESPACE_END
#endif /* WITH_CUDA */

View File

@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/denoise.h"
CCL_NAMESPACE_BEGIN
const char *denoiserTypeToHumanReadable(DenoiserType type)
{
switch (type) {
case DENOISER_OPTIX:
return "OptiX";
case DENOISER_OPENIMAGEDENOISE:
return "OpenImageDenoise";
case DENOISER_NUM:
case DENOISER_NONE:
case DENOISER_ALL:
return "UNKNOWN";
}
return "UNKNOWN";
}
const NodeEnum *DenoiseParams::get_type_enum()
{
static NodeEnum type_enum;
if (type_enum.empty()) {
type_enum.insert("optix", DENOISER_OPTIX);
type_enum.insert("openimageio", DENOISER_OPENIMAGEDENOISE);
}
return &type_enum;
}
const NodeEnum *DenoiseParams::get_prefilter_enum()
{
static NodeEnum prefilter_enum;
if (prefilter_enum.empty()) {
prefilter_enum.insert("none", DENOISER_PREFILTER_NONE);
prefilter_enum.insert("fast", DENOISER_PREFILTER_FAST);
prefilter_enum.insert("accurate", DENOISER_PREFILTER_ACCURATE);
}
return &prefilter_enum;
}
const NodeEnum *DenoiseParams::get_quality_enum()
{
static NodeEnum quality_enum;
if (quality_enum.empty()) {
quality_enum.insert("high", DENOISER_QUALITY_HIGH);
quality_enum.insert("balanced", DENOISER_QUALITY_BALANCED);
quality_enum.insert("fast", DENOISER_QUALITY_FAST);
}
return &quality_enum;
}
NODE_DEFINE(DenoiseParams)
{
NodeType *type = NodeType::add("denoise_params", create);
const NodeEnum *type_enum = get_type_enum();
const NodeEnum *prefilter_enum = get_prefilter_enum();
const NodeEnum *quality_enum = get_quality_enum();
SOCKET_BOOLEAN(use, "Use", false);
SOCKET_ENUM(type, "Type", *type_enum, DENOISER_OPENIMAGEDENOISE);
SOCKET_INT(start_sample, "Start Sample", 0);
SOCKET_INT(passes, "Passes", DENOISER_PASS_ALBEDO | DENOISER_PASS_NORMAL);
SOCKET_BOOLEAN(temporally_stable, "Temporally Stable", false);
SOCKET_ENUM(prefilter, "Prefilter", *prefilter_enum, DENOISER_PREFILTER_FAST);
SOCKET_ENUM(quality, "Quality", *quality_enum, DENOISER_QUALITY_HIGH);
SOCKET_FLOAT(upscale_factor, "Upscale Factor", 1.0f);
return type;
}
DenoiseParams::DenoiseParams() : Node(get_node_type()) {}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "graph/node.h"
CCL_NAMESPACE_BEGIN
enum DenoiserType {
DENOISER_OPTIX = 2,
DENOISER_OPENIMAGEDENOISE = 4,
DENOISER_NUM,
DENOISER_NONE = 0,
DENOISER_ALL = ~0,
};
/* COnstruct human-readable string which denotes the denoiser type. */
const char *denoiserTypeToHumanReadable(DenoiserType type);
using DenoiserTypeMask = int;
enum DenoiserPass {
DENOISER_PASS_NONE = 0,
DENOISER_PASS_ALBEDO = 1 << 0,
DENOISER_PASS_SPECULAR_ALBEDO = 1 << 1,
DENOISER_PASS_NORMAL = 1 << 2,
DENOISER_PASS_ROUGHNESS = 1 << 3,
DENOISER_PASS_DEPTH = 1 << 4,
DENOISER_PASS_MOTION = 1 << 5,
DENOISER_PASS_BACKWARD_MOTION = 1 << 6,
};
using DenoiserPassMask = int;
enum DenoiserPrefilter {
/* Best quality of the result without extra processing time, but requires guiding passes to be
* noise-free. */
DENOISER_PREFILTER_NONE = 1,
/* Denoise color and guiding passes together.
* Improves quality when guiding passes are noisy using least amount of extra processing time. */
DENOISER_PREFILTER_FAST = 2,
/* Prefilter noisy guiding passes before denoising color.
* Improves quality when guiding passes are noisy using extra processing time. */
DENOISER_PREFILTER_ACCURATE = 3,
DENOISER_PREFILTER_NUM,
};
enum DenoiserQuality {
DENOISER_QUALITY_HIGH = 1,
DENOISER_QUALITY_BALANCED = 2,
DENOISER_QUALITY_FAST = 3,
DENOISER_QUALITY_NUM,
};
/* NOTE: Is not a real scene node. Using Node API for ease of (de)serialization.
* The default values here do not really matter as they are always initialized from the
* Integrator node. */
class DenoiseParams : public Node {
public:
NODE_DECLARE
/* Apply denoiser to image. */
bool use = false;
/* Denoiser type. */
DenoiserType type = DENOISER_OPENIMAGEDENOISE;
/* Viewport start sample. */
int start_sample = 0;
/* Auxiliary passes. */
DenoiserPassMask passes = DENOISER_PASS_ALBEDO | DENOISER_PASS_NORMAL;
/* Configure the denoiser to use motion vectors, previous image and a temporally stable model. */
bool temporally_stable = false;
/* If true, then allow, if supported, OpenImageDenoise to use GPU device.
* If false, then OpenImageDenoise will always use CPU regardless of GPU device presence. */
bool use_gpu = true;
DenoiserPrefilter prefilter = DENOISER_PREFILTER_FAST;
DenoiserQuality quality = DENOISER_QUALITY_HIGH;
float upscale_factor = 1.0f;
static const NodeEnum *get_type_enum();
static const NodeEnum *get_prefilter_enum();
static const NodeEnum *get_quality_enum();
DenoiseParams();
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,896 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include <cstdlib>
#include <cstring>
#include "bvh/bvh2.h"
#include "device/device.h"
#include "device/queue.h"
#include "device/cpu/device.h"
#include "device/cpu/kernel.h"
#include "device/cuda/device.h"
#include "device/dummy/device.h"
#include "device/hip/device.h"
#include "device/metal/device.h"
#include "device/multi/device.h"
#include "device/oneapi/device.h"
#include "device/optix/device.h"
#include "util/log.h"
#include "util/math.h"
#include "util/string.h"
#include "util/system.h"
#include "util/task.h"
#include "util/types.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
bool Device::need_types_update = true;
bool Device::need_devices_update = true;
thread_mutex Device::device_mutex;
uint Device::devices_initialized_mask = 0;
/* Lazily init inside function so they get destructed before guardedalloc leak check. */
vector<DeviceInfo> &Device::cuda_devices()
{
static vector<DeviceInfo> devices_;
return devices_;
}
vector<DeviceInfo> &Device::optix_devices()
{
static vector<DeviceInfo> devices_;
return devices_;
}
vector<DeviceInfo> &Device::cpu_devices()
{
static vector<DeviceInfo> devices_;
return devices_;
}
vector<DeviceInfo> &Device::hip_devices()
{
static vector<DeviceInfo> devices_;
return devices_;
}
vector<DeviceInfo> &Device::metal_devices()
{
static vector<DeviceInfo> devices_;
return devices_;
}
vector<DeviceInfo> &Device::oneapi_devices()
{
static vector<DeviceInfo> devices_;
return devices_;
}
/* Device */
Device::~Device() noexcept(false) = default;
void Device::set_error(const string &error)
{
if (!have_error()) {
error_msg = error;
}
LOG_ERROR << error;
fflush(stderr);
}
void Device::build_bvh(BVH *bvh, Progress &progress, bool refit)
{
assert(bvh->params.bvh_layout == BVH_LAYOUT_BVH2);
BVH2 *const bvh2 = static_cast<BVH2 *>(bvh);
if (refit) {
bvh2->refit(progress);
}
else {
bvh2->build(progress, &stats);
}
}
unique_ptr<Device> Device::create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
if (!info.multi_devices.empty()) {
/* Always create a multi device when info contains multiple devices.
* This is done so that the type can still be e.g. DEVICE_CPU to indicate
* that it is a homogeneous collection of devices, which simplifies checks. */
return device_multi_create(info, stats, profiler, headless);
}
unique_ptr<Device> device;
switch (info.type) {
case DEVICE_CPU:
device = device_cpu_create(info, stats, profiler, headless);
break;
#ifdef WITH_CUDA
case DEVICE_CUDA:
if (device_cuda_init()) {
device = device_cuda_create(info, stats, profiler, headless);
}
break;
#endif
#ifdef WITH_OPTIX
case DEVICE_OPTIX:
if (device_optix_init()) {
device = device_optix_create(info, stats, profiler, headless);
}
break;
#endif
#ifdef WITH_HIP
case DEVICE_HIP:
if (device_hip_init()) {
device = device_hip_create(info, stats, profiler, headless);
}
break;
#endif
#ifdef WITH_METAL
case DEVICE_METAL:
if (device_metal_init()) {
device = device_metal_create(info, stats, profiler, headless);
}
break;
#endif
#ifdef WITH_ONEAPI
case DEVICE_ONEAPI:
device = device_oneapi_create(info, stats, profiler, headless);
break;
#endif
default:
break;
}
if (device == nullptr) {
device = device_dummy_create(info, stats, profiler, headless);
}
return device;
}
DeviceType Device::type_from_string(const char *name)
{
if (strcmp(name, "CPU") == 0) {
return DEVICE_CPU;
}
if (strcmp(name, "CUDA") == 0) {
return DEVICE_CUDA;
}
if (strcmp(name, "OPTIX") == 0) {
return DEVICE_OPTIX;
}
if (strcmp(name, "MULTI") == 0) {
return DEVICE_MULTI;
}
if (strcmp(name, "HIP") == 0) {
return DEVICE_HIP;
}
if (strcmp(name, "METAL") == 0) {
return DEVICE_METAL;
}
if (strcmp(name, "ONEAPI") == 0) {
return DEVICE_ONEAPI;
}
if (strcmp(name, "HIPRT") == 0) {
return DEVICE_HIPRT;
}
return DEVICE_NONE;
}
string Device::string_from_type(DeviceType type)
{
if (type == DEVICE_CPU) {
return "CPU";
}
if (type == DEVICE_CUDA) {
return "CUDA";
}
if (type == DEVICE_OPTIX) {
return "OPTIX";
}
if (type == DEVICE_MULTI) {
return "MULTI";
}
if (type == DEVICE_HIP) {
return "HIP";
}
if (type == DEVICE_METAL) {
return "METAL";
}
if (type == DEVICE_ONEAPI) {
return "ONEAPI";
}
if (type == DEVICE_HIPRT) {
return "HIPRT";
}
return "";
}
vector<DeviceType> Device::available_types()
{
vector<DeviceType> types;
types.push_back(DEVICE_CPU);
#ifdef WITH_CUDA
types.push_back(DEVICE_CUDA);
#endif
#ifdef WITH_OPTIX
types.push_back(DEVICE_OPTIX);
#endif
#ifdef WITH_HIP
types.push_back(DEVICE_HIP);
#endif
#ifdef WITH_METAL
types.push_back(DEVICE_METAL);
#endif
#ifdef WITH_ONEAPI
types.push_back(DEVICE_ONEAPI);
#endif
#ifdef WITH_HIPRT
types.push_back(DEVICE_HIPRT);
#endif
return types;
}
vector<DeviceInfo> Device::available_devices(const uint mask)
{
/* Lazy initialize devices. On some platforms OpenCL or CUDA drivers can
* be broken and cause crashes when only trying to get device info, so
* we don't want to do any initialization until the user chooses to. */
const thread_scoped_lock lock(device_mutex);
vector<DeviceInfo> devices;
#if defined(WITH_CUDA) || defined(WITH_OPTIX)
if (mask & (DEVICE_MASK_CUDA | DEVICE_MASK_OPTIX)) {
if (!(devices_initialized_mask & DEVICE_MASK_CUDA)) {
if (device_cuda_init()) {
device_cuda_info(cuda_devices());
}
devices_initialized_mask |= DEVICE_MASK_CUDA;
}
if (mask & DEVICE_MASK_CUDA) {
for (DeviceInfo &info : cuda_devices()) {
devices.push_back(info);
}
}
}
#endif
#ifdef WITH_OPTIX
if (mask & DEVICE_MASK_OPTIX) {
if (!(devices_initialized_mask & DEVICE_MASK_OPTIX)) {
if (device_optix_init()) {
device_optix_info(cuda_devices(), optix_devices());
}
devices_initialized_mask |= DEVICE_MASK_OPTIX;
}
for (DeviceInfo &info : optix_devices()) {
devices.push_back(info);
}
}
#endif
#ifdef WITH_HIP
if (mask & DEVICE_MASK_HIP) {
if (!(devices_initialized_mask & DEVICE_MASK_HIP)) {
if (device_hip_init()) {
device_hip_info(hip_devices());
}
devices_initialized_mask |= DEVICE_MASK_HIP;
}
for (DeviceInfo &info : hip_devices()) {
devices.push_back(info);
}
}
#endif
#ifdef WITH_ONEAPI
if (mask & DEVICE_MASK_ONEAPI) {
if (!(devices_initialized_mask & DEVICE_MASK_ONEAPI)) {
if (device_oneapi_init()) {
device_oneapi_info(oneapi_devices());
}
devices_initialized_mask |= DEVICE_MASK_ONEAPI;
}
for (DeviceInfo &info : oneapi_devices()) {
devices.push_back(info);
}
}
#endif
if (mask & DEVICE_MASK_CPU) {
if (!(devices_initialized_mask & DEVICE_MASK_CPU)) {
device_cpu_info(cpu_devices());
devices_initialized_mask |= DEVICE_MASK_CPU;
}
for (const DeviceInfo &info : cpu_devices()) {
devices.push_back(info);
}
}
#ifdef WITH_METAL
if (mask & DEVICE_MASK_METAL) {
if (!(devices_initialized_mask & DEVICE_MASK_METAL)) {
if (device_metal_init()) {
device_metal_info(metal_devices());
}
devices_initialized_mask |= DEVICE_MASK_METAL;
}
for (const DeviceInfo &info : metal_devices()) {
devices.push_back(info);
}
}
#endif
return devices;
}
DeviceInfo Device::dummy_device(const string &error_msg)
{
DeviceInfo info;
info.type = DEVICE_DUMMY;
info.error_msg = error_msg;
return info;
}
string Device::device_capabilities(const uint mask)
{
const thread_scoped_lock lock(device_mutex);
string capabilities;
if (mask & DEVICE_MASK_CPU) {
capabilities += "\nCPU device capabilities: ";
capabilities += device_cpu_capabilities() + "\n";
}
#ifdef WITH_CUDA
if (mask & DEVICE_MASK_CUDA) {
if (device_cuda_init()) {
const string device_capabilities = device_cuda_capabilities();
if (!device_capabilities.empty()) {
capabilities += "\nCUDA device capabilities:\n";
capabilities += device_capabilities;
}
}
}
#endif
#ifdef WITH_HIP
if (mask & DEVICE_MASK_HIP) {
if (device_hip_init()) {
const string device_capabilities = device_hip_capabilities();
if (!device_capabilities.empty()) {
capabilities += "\nHIP device capabilities:\n";
capabilities += device_capabilities;
}
}
}
#endif
#ifdef WITH_ONEAPI
if (mask & DEVICE_MASK_ONEAPI) {
if (device_oneapi_init()) {
const string device_capabilities = device_oneapi_capabilities();
if (!device_capabilities.empty()) {
capabilities += "\noneAPI device capabilities:\n";
capabilities += device_capabilities;
}
}
}
#endif
#ifdef WITH_METAL
if (mask & DEVICE_MASK_METAL) {
if (device_metal_init()) {
const string device_capabilities = device_metal_capabilities();
if (!device_capabilities.empty()) {
capabilities += "\nMetal device capabilities:\n";
capabilities += device_capabilities;
}
}
}
#endif
return capabilities;
}
DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
const int threads,
bool background)
{
assert(!subdevices.empty());
if (subdevices.size() == 1) {
/* No multi device needed. */
return subdevices.front();
}
DeviceInfo info;
info.type = DEVICE_NONE;
info.id = "MULTI";
info.description = "Multi Device";
info.num = 0;
info.has_nanovdb = true;
info.has_mnee_ = true;
info.has_osl = true;
info.has_guiding = true;
info.has_profiling = true;
info.has_peer_memory = false;
info.use_hardware_raytracing = false;
info.denoisers = DENOISER_ALL;
for (const DeviceInfo &device : subdevices) {
/* Ensure CPU device does not slow down GPU. */
if (device.type == DEVICE_CPU && subdevices.size() > 1) {
if (background) {
const int orig_cpu_threads = (threads) ? threads : TaskScheduler::max_concurrency();
const int cpu_threads = max(orig_cpu_threads - (subdevices.size() - 1), size_t(0));
LOG_INFO << "CPU render threads reduced from " << orig_cpu_threads << " to " << cpu_threads
<< ", to dedicate to GPU.";
if (cpu_threads >= 1) {
DeviceInfo cpu_device = device;
cpu_device.cpu_threads = cpu_threads;
info.multi_devices.push_back(cpu_device);
}
else {
continue;
}
}
else {
LOG_INFO << "CPU render threads disabled for interactive render.";
continue;
}
}
else {
info.multi_devices.push_back(device);
}
/* Create unique ID for this combination of devices. */
info.id += device.id;
/* Set device type to MULTI if subdevices are not of a common type. */
if (info.type == DEVICE_NONE) {
info.type = device.type;
}
else if (device.type != info.type) {
info.type = DEVICE_MULTI;
}
/* Accumulate device info. */
info.has_nanovdb &= device.has_nanovdb;
info.has_mnee_ &= device.has_mnee();
info.has_osl &= device.has_osl;
info.has_guiding &= device.has_guiding;
info.has_profiling &= device.has_profiling;
info.has_peer_memory |= device.has_peer_memory;
info.use_hardware_raytracing |= device.use_hardware_raytracing;
info.denoisers &= device.denoisers;
}
return info;
}
void Device::tag_update()
{
free_memory();
}
void Device::free_memory()
{
devices_initialized_mask = 0;
cuda_devices().free_memory();
optix_devices().free_memory();
hip_devices().free_memory();
oneapi_devices().free_memory();
cpu_devices().free_memory();
metal_devices().free_memory();
}
unique_ptr<DeviceQueue> Device::gpu_queue_create()
{
LOG_FATAL << "Device does not support queues.";
return nullptr;
}
const CPUKernels &Device::get_cpu_kernels()
{
/* Initialize CPU kernels once and reuse. */
static const CPUKernels kernels;
return kernels;
}
vector<ThreadKernelGlobalsCPU> *Device::acquire_cpu_kernel_thread_globals()
{
LOG_FATAL << "Device does not support CPU kernels.";
return nullptr;
}
void Device::release_cpu_kernel_thread_globals()
{
/* No-op for non-CPU devices. */
}
OSLGlobals *Device::get_cpu_osl_memory()
{
return nullptr;
}
void *Device::get_guiding_device() const
{
LOG_ERROR << "Request guiding field from a device which does not support it.";
return nullptr;
}
void *Device::host_alloc(const MemoryType /*type*/, const size_t size)
{
return util_aligned_malloc(size, MIN_ALIGNMENT_DEVICE_MEMORY);
}
void Device::host_free(const MemoryType /*type*/, void *host_pointer, const size_t size)
{
util_aligned_free(host_pointer, size);
}
void Device::mem_or_from_device(device_memory &mem)
{
/* Note that we always accumulate into the host buffer without zeroing, as CPU and unified
* memory write into the host buffer and we need to combine with those flags. */
const size_t size = mem.memory_size();
vector<uint8_t> tmp(size);
uint8_t *combined = static_cast<uint8_t *>(mem.host_pointer);
mem.host_pointer = tmp.data();
mem_copy_from(
mem, 0, mem.data_width, (mem.data_height == 0) ? 1 : mem.data_height, sizeof(uint8_t));
const uint8_t *src = (const uint8_t *)mem.host_pointer;
for (size_t i = 0; i < size; i++) {
combined[i] |= src[i];
}
mem.host_pointer = combined;
}
device_ptr Device::mem_device_ptr(const device_memory &mem, Device *sub_device)
{
assert(sub_device == this);
(void)sub_device;
return mem.device_pointer;
}
GPUDevice::~GPUDevice() noexcept(false) = default;
bool GPUDevice::load_image_info(DeviceQueue *queue)
{
/* Note image_info is never host mapped, and load_image_info() should only
* be called right before kernel enqueue when all memory operations have completed. */
if (need_image_info) {
/* If the host buffer was grown with host_only_resize() while a kernel was reading the old
* device buffer, we now free and reallocate it. */
if (image_info.device_size < image_info.memory_size()) {
generic_free(image_info);
}
if (queue) {
queue->copy_to_device(image_info);
}
else {
image_info.copy_to_device();
}
need_image_info = false;
return true;
}
return false;
}
void GPUDevice::init_host_memory(const size_t preferred_texture_headroom,
const size_t preferred_working_headroom)
{
/* Limit amount of host mapped memory, because allocating too much can
* cause system instability. Leave at least half or 4 GB of system
* memory free, whichever is smaller. */
const size_t default_limit = 4 * 1024 * 1024 * 1024LL;
const size_t system_ram = system_physical_ram();
if (system_ram > 0) {
if (system_ram / 2 > default_limit) {
map_host_limit = system_ram - default_limit;
}
else {
map_host_limit = system_ram / 2;
}
}
else {
LOG_WARNING << "Mapped host memory disabled, failed to get system RAM";
map_host_limit = 0;
}
/* Amount of device memory to keep free after texture memory
* and working memory allocations respectively. We set the working
* memory limit headroom lower than the working one so there
* is space left for it. */
device_working_headroom = preferred_working_headroom > 0 ? preferred_working_headroom :
32 * 1024 * 1024LL; // 32MB
device_image_headroom = preferred_texture_headroom > 0 ? preferred_texture_headroom :
128 * 1024 * 1024LL; // 128MB
LOG_INFO << "Mapped host memory limit set to " << string_human_readable_number(map_host_limit)
<< " bytes. (" << string_human_readable_size(map_host_limit) << ")";
}
void GPUDevice::move_textures_to_host(size_t size, const size_t headroom, const bool for_texture)
{
static thread_mutex move_mutex;
const thread_scoped_lock lock(move_mutex);
/* Check if there is enough space. Within mutex locks so that multiple threads
* calling take into account memory freed by another thread. */
size_t total = 0;
size_t free = 0;
get_device_memory_info(total, free);
if (size + headroom < free) {
return;
}
while (size > 0) {
/* Find suitable memory allocation to move. */
device_memory *max_mem = nullptr;
size_t max_size = 0;
bool max_is_image = false;
thread_scoped_lock lock(device_mem_map_mutex);
for (MemMap::value_type &pair : device_mem_map) {
device_memory &mem = *pair.first;
Mem *cmem = &pair.second;
/* Can only move textures allocated on this device (and not those from peer devices).
* And need to ignore memory that is already on the host. */
if (!mem.is_resident(this) || mem.is_shared(this)) {
continue;
}
const bool is_texture = (mem.type == MEM_IMAGE_TEXTURE || mem.type == MEM_GLOBAL) &&
(&mem != &image_info);
const bool is_image = is_texture && (mem.data_height > 1);
/* Can't move this type of memory. */
if (!is_texture || cmem->array) {
continue;
}
/* For other textures, only move image textures. */
if (for_texture && !is_image) {
continue;
}
/* Try to move largest allocation, prefer moving images. */
if (is_image > max_is_image || (is_image == max_is_image && mem.device_size > max_size)) {
max_is_image = is_image;
max_size = mem.device_size;
max_mem = &mem;
}
}
lock.unlock();
/* Move to host memory. This part is mutex protected since
* multiple backend devices could be moving the memory. The
* first one will do it, and the rest will adopt the pointer. */
if (max_mem) {
LOG_DEBUG << "Move memory from device to host: " << max_mem->log_name();
/* Potentially need to call back into multi device, so pointer mapping
* and peer devices are updated. This is also necessary since the device
* pointer may just be a key here, so cannot be accessed and freed directly.
* Unfortunately it does mean that memory is reallocated on all other
* devices as well, which is potentially dangerous when still in use (since
* a thread rendering on another devices would only be caught in this mutex
* if it so happens to do an allocation at the same time as well. */
max_mem->move_to_host = true;
max_mem->device_move_to_host();
max_mem->move_to_host = false;
size = (max_size >= size) ? 0 : size - max_size;
/* Tag image info update for new pointers. */
need_image_info = true;
}
else {
break;
}
}
}
GPUDevice::Mem *GPUDevice::generic_alloc(device_memory &mem, const size_t pitch_padding)
{
void *device_pointer = nullptr;
const size_t size = mem.memory_size() + pitch_padding;
bool mem_alloc_result = false;
const char *status = "";
/* First try allocating in device memory, respecting headroom. We make
* an exception for image info. It is small and frequently accessed,
* so treat it as working memory.
*
* If there is not enough room for working memory, we will try to move
* textures to host memory, assuming the performance impact would have
* been worse for working memory. */
const bool is_texture = (mem.type == MEM_IMAGE_TEXTURE || mem.type == MEM_GLOBAL) &&
(&mem != &image_info);
const bool is_image = is_texture && (mem.data_height > 1);
const size_t headroom = (is_texture) ? device_image_headroom : device_working_headroom;
/* Move textures to host memory if needed. */
if (!mem.move_to_host && !is_image && can_map_host) {
move_textures_to_host(size, headroom, is_texture);
}
size_t total = 0;
size_t free = 0;
get_device_memory_info(total, free);
/* Allocate in device memory. */
if ((!mem.move_to_host && (size + headroom) < free) || (mem.type == MEM_DEVICE_ONLY)) {
mem_alloc_result = alloc_device(device_pointer, size);
if (mem_alloc_result) {
device_mem_in_use += size;
status = " in device memory";
}
}
/* Fall back to mapped host memory if needed and possible. */
void *shared_pointer = nullptr;
if (!mem_alloc_result && can_map_host && mem.type != MEM_DEVICE_ONLY) {
if (mem.shared_pointer) {
/* Another device already allocated host memory. */
mem_alloc_result = true;
shared_pointer = mem.shared_pointer;
}
else if (map_host_used + size < map_host_limit) {
/* Allocate host memory ourselves. */
mem_alloc_result = shared_alloc(shared_pointer, size);
assert((mem_alloc_result && shared_pointer != nullptr) ||
(!mem_alloc_result && shared_pointer == nullptr));
}
if (mem_alloc_result) {
device_pointer = shared_to_device_pointer(shared_pointer);
map_host_used += size;
status = " in host memory";
}
}
if (!mem_alloc_result) {
if (mem.type == MEM_DEVICE_ONLY) {
status = " failed, out of device memory";
set_error("System is out of GPU memory");
}
else {
status = " failed, out of device and host memory";
set_error("System is out of GPU and shared host memory");
}
}
LOG_DEBUG << "Buffer allocate: " << mem.log_name() << ", "
<< string_human_readable_number(mem.memory_size()) << " bytes. ("
<< string_human_readable_size(mem.memory_size()) << ")" << status;
mem.device_pointer = (device_ptr)device_pointer;
mem.device_size = size;
stats.mem_alloc(size);
if (!mem.device_pointer) {
return nullptr;
}
/* Insert into map of allocations. */
const thread_scoped_lock lock(device_mem_map_mutex);
Mem *cmem = &device_mem_map[&mem];
if (shared_pointer != nullptr) {
/* Replace host pointer with our host allocation. Only works if
* memory layout is the same and has no pitch padding. Also
* does not work if we move textures to host during a render,
* since other devices might be using the memory. */
if (!mem.move_to_host && pitch_padding == 0 && mem.host_pointer &&
mem.host_pointer != shared_pointer)
{
memcpy(shared_pointer, mem.host_pointer, size);
host_free(mem.type, mem.host_pointer, mem.memory_size());
mem.host_pointer = shared_pointer;
}
mem.shared_pointer = shared_pointer;
mem.shared_counter++;
}
return cmem;
}
void GPUDevice::generic_free(device_memory &mem)
{
if (!(mem.device_pointer && mem.is_resident(this))) {
return;
}
/* Host pointer should already have been freed at this point. If not we might
* end up freeing shared memory and can't recover original host memory. */
assert(mem.host_pointer == nullptr || mem.move_to_host || !mem.is_shared(this));
const thread_scoped_lock lock(device_mem_map_mutex);
DCHECK(device_mem_map.find(&mem) != device_mem_map.end());
/* For host mapped memory, reference counting is used to safely free it. */
if (mem.is_shared(this)) {
assert(mem.shared_counter > 0);
if (--mem.shared_counter == 0) {
if (mem.host_pointer == mem.shared_pointer) {
/* Safely move the device-side data back to the host before it is freed.
* We should actually never reach this code as it is inefficient, but
* better than to crash if there is a bug. */
assert(!"GPU device should not copy memory back to host");
const size_t size = mem.memory_size();
mem.host_pointer = mem.host_alloc(size);
memcpy(mem.host_pointer, mem.shared_pointer, size);
}
shared_free(mem.shared_pointer);
mem.shared_pointer = nullptr;
}
map_host_used -= mem.device_size;
}
else {
/* Free device memory. */
free_device((void *)mem.device_pointer);
device_mem_in_use -= mem.device_size;
}
stats.mem_free(mem.device_size);
mem.device_pointer = 0;
mem.device_size = 0;
device_mem_map.erase(device_mem_map.find(&mem));
}
void GPUDevice::generic_copy_to(device_memory &mem)
{
if (!mem.host_pointer || !mem.device_pointer) {
return;
}
/* If not host mapped, the current device only uses device memory allocated by backend
* device allocation regardless of mem.host_pointer and mem.shared_pointer, and should
* copy data from mem.host_pointer. */
if (!(mem.is_shared(this) && mem.host_pointer == mem.shared_pointer)) {
copy_host_to_device((void *)mem.device_pointer, mem.host_pointer, mem.memory_size());
}
}
bool GPUDevice::is_shared(const void *shared_pointer,
const device_ptr device_pointer,
Device * /*sub_device*/)
{
return (shared_pointer && device_pointer &&
(device_ptr)shared_to_device_pointer(shared_pointer) == device_pointer);
}
/* DeviceInfo */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,451 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <cstdlib>
#include <functional>
#include "bvh/params.h"
#include "device/denoise.h"
#include "device/memory.h"
#include "util/profiling.h"
#include "util/stats.h"
#include "util/string.h"
#include "util/thread.h"
#include "util/types.h"
#include "util/types_image.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class BVH;
class DeviceQueue;
class GraphicsInteropDevice;
class Progress;
class CPUKernels;
class Scene;
struct OSLGlobals;
struct ThreadKernelGlobalsCPU;
/* Device Types */
enum DeviceType {
DEVICE_NONE = 0,
DEVICE_CPU,
DEVICE_CUDA,
DEVICE_MULTI,
DEVICE_OPTIX,
DEVICE_HIP,
DEVICE_HIPRT,
DEVICE_METAL,
DEVICE_ONEAPI,
DEVICE_DUMMY,
};
enum DeviceTypeMask {
DEVICE_MASK_CPU = (1 << DEVICE_CPU),
DEVICE_MASK_CUDA = (1 << DEVICE_CUDA),
DEVICE_MASK_OPTIX = (1 << DEVICE_OPTIX),
DEVICE_MASK_HIP = (1 << DEVICE_HIP),
DEVICE_MASK_METAL = (1 << DEVICE_METAL),
DEVICE_MASK_ONEAPI = (1 << DEVICE_ONEAPI),
DEVICE_MASK_ALL = ~0
};
#define DEVICE_MASK(type) (DeviceTypeMask)(1 << type)
enum KernelOptimizationLevel {
KERNEL_OPTIMIZATION_LEVEL_OFF = 0,
KERNEL_OPTIMIZATION_LEVEL_INTERSECT = 1,
KERNEL_OPTIMIZATION_LEVEL_FULL = 2,
KERNEL_OPTIMIZATION_NUM_LEVELS
};
enum MetalRTSetting {
METALRT_OFF = 0,
METALRT_ON = 1,
METALRT_AUTO = 2,
METALRT_NUM_SETTINGS
};
class DeviceInfo {
public:
DeviceType type = DEVICE_CPU;
string description;
/* used for user preferences, should stay fixed with changing hardware config */
string id = "CPU";
int num = 0;
bool display_device = false; /* GPU is used as a display device. */
bool has_nanovdb = false; /* Support NanoVDB volumes. */
bool has_mnee_ = true; /* Support MNEE. */
bool has_osl = false; /* Support Open Shading Language. */
bool has_guiding = false; /* Support path guiding. */
bool has_profiling = false; /* Supports runtime collection of profiling info. */
bool has_peer_memory = false; /* GPU has P2P access to memory of another GPU. */
bool has_gpu_queue = false; /* Device supports GPU queue. */
bool use_hardware_raytracing = false; /* Use hardware instructions to accelerate ray tracing. */
bool use_metalrt_by_default = false; /* Use MetalRT by default. */
/* Indicate that device execution has been optimized by Blender or vendor developers.
* For LTS versions, this helps communicate that newer versions may have better performance. */
bool has_execution_optimization = true;
KernelOptimizationLevel kernel_optimization_level =
KERNEL_OPTIMIZATION_LEVEL_FULL; /* Optimization level applied to path tracing
* kernels (Metal only). */
DenoiserTypeMask denoisers = DENOISER_NONE; /* Supported denoiser types. */
int cpu_threads = 0;
vector<DeviceInfo> multi_devices;
string error_msg;
DeviceInfo() = default;
bool operator==(const DeviceInfo &info) const
{
/* Multiple Devices with the same ID would be very bad. */
assert(id != info.id ||
(type == info.type && num == info.num && description == info.description));
return id == info.id && use_hardware_raytracing == info.use_hardware_raytracing &&
kernel_optimization_level == info.kernel_optimization_level;
}
bool operator!=(const DeviceInfo &info) const
{
return !(*this == info);
}
bool has_mnee() const
{
/* Shadow caustics not supported on HIP without hardware ray-tracing, see #160089.
* This is a more complex condition that can't be determined in device_hip_info,
* so there is a helper for it here. */
return has_mnee_ && (type != DEVICE_HIP || use_hardware_raytracing);
}
};
/* Device */
class Device {
friend class device_sub_ptr;
protected:
Device(const DeviceInfo &info_, Stats &stats_, Profiler &profiler_, bool headless_)
: info(info_), stats(stats_), profiler(profiler_), headless(headless_)
{
}
string error_msg;
KernelImageLoadRequestedGPU image_load_requested_gpu_;
virtual device_ptr mem_alloc_sub_ptr(device_memory & /*mem*/, size_t /*offset*/, size_t /*size*/)
{
/* Only required for devices that implement denoising. */
assert(false);
return (device_ptr)0;
}
virtual void mem_free_sub_ptr(device_ptr /*ptr*/){};
public:
/* noexcept needed to silence TBB warning. */
virtual ~Device() noexcept(false);
/* info */
DeviceInfo info;
virtual const string &error_message()
{
return error_msg;
}
bool have_error()
{
return !error_message().empty();
}
virtual void set_error(const string &error);
virtual BVHLayoutMask get_bvh_layout_mask(const uint kernel_features) const = 0;
/* statistics */
Stats &stats;
Profiler &profiler;
bool headless = true;
/* constant memory */
virtual void const_copy_to(const char *name, void *host, const size_t size) = 0;
/* load/compile kernels, must be called before adding tasks */
virtual bool load_kernels(uint /*kernel_features*/)
{
return true;
}
virtual bool load_osl_kernels()
{
return true;
}
/* Request cancellation of any long-running work. */
virtual void cancel() {}
/* Report status and return true if device is ready for rendering. */
virtual bool is_ready(string & /*status*/) const
{
return true;
}
/* GPU device only functions.
* These may not be used on CPU or multi-devices. */
/* Create new queue for executing kernels in. */
virtual unique_ptr<DeviceQueue> gpu_queue_create();
/* CPU device only functions.
* These may not be used on GPU or multi-devices. */
/* Get CPU kernel functions for native instruction set. */
static const CPUKernels &get_cpu_kernels();
/* Acquire thread globals for CPU kernel execution. Creates them if needed,
* and updates all data pointers from the device's kernel globals. */
virtual vector<ThreadKernelGlobalsCPU> *acquire_cpu_kernel_thread_globals();
/* Release thread globals, allowing them to be destroyed. */
virtual void release_cpu_kernel_thread_globals();
/* Get OpenShadingLanguage memory buffer. */
virtual OSLGlobals *get_cpu_osl_memory();
/* Image Cache. */
virtual void set_image_cache_func(KernelImageLoadRequestedCPU /*image_load_requested_cpu*/,
KernelImageLoadRequestedGPU image_load_requested_gpu)
{
image_load_requested_gpu_ = image_load_requested_gpu;
}
void image_load_requested_gpu(DeviceQueue &queue)
{
if (image_load_requested_gpu_) {
image_load_requested_gpu_(queue);
}
}
/* Acceleration structure building. */
virtual void build_bvh(BVH *bvh, Progress &progress, bool refit);
/* Used by Metal and OptiX. */
virtual void release_bvh(BVH * /*bvh*/) {}
/* Inform of BVH limits, return true to force-rebuild all BVHs and kernels. */
virtual bool set_bvh_limits(size_t /*instance_count*/, size_t /*max_prim_count*/)
{
return false;
}
/* multi device */
virtual int device_number(Device * /*sub_device*/)
{
return 0;
}
/* Called after kernel texture setup, and prior to integrator state setup. */
virtual void optimize_for_scene(Scene * /*scene*/) {}
virtual bool is_resident(device_ptr /*key*/, Device *sub_device)
{
/* Memory is always resident if this is not a multi device, regardless of whether the pointer
* is valid or not (since it may not have been allocated yet). */
return sub_device == this;
}
/* Return the real device pointer for mem on the given sub_device. */
virtual device_ptr mem_device_ptr(const device_memory &mem, Device *sub_device);
virtual bool check_peer_access(Device * /*peer_device*/)
{
return false;
}
virtual bool has_unified_memory() const
{
return false;
}
virtual bool has_unified_image_memory() const
{
return false;
}
virtual bool is_shared(const void * /*shared_pointer*/,
const device_ptr /*device_pointer*/,
Device * /*sub_device*/)
{
return false;
}
/* Graphics resources interoperability.
*
* The interoperability comes here by the meaning that the device is capable of computing result
* directly into a OpenGL, Vulkan or Metal buffer. */
/* Check display is to be updated using graphics interoperability.
* The interoperability can not be used is it is not supported by the device. But the device
* might also force disable the interoperability if it detects that it will be slower than
* copying pixels from the render buffer. */
virtual bool should_use_graphics_interop(const GraphicsInteropDevice & /*interop_device*/,
const bool /*log*/ = false)
{
return false;
}
/* Returns native buffer handle for device pointer. */
virtual void *get_native_buffer(device_ptr /*ptr*/)
{
return nullptr;
}
/* Guiding */
/* Returns path guiding device handle. */
virtual void *get_guiding_device() const;
/* Read back a device_memory byte buffer from device and OR values into the host buffer.
* The host buffer is not zeroed as part of this. */
virtual void mem_or_from_device(device_memory &mem);
/* Sub-devices */
/* Run given callback for every individual device which will be handling rendering.
* For the single device the callback is called for the device itself. For the multi-device the
* callback is only called for the sub-devices. */
virtual void foreach_device(const std::function<void(Device *)> &callback)
{
callback(this);
}
/* static */
static unique_ptr<Device> create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
static DeviceType type_from_string(const char *name);
static string string_from_type(DeviceType type);
static vector<DeviceType> available_types();
static vector<DeviceInfo> available_devices(const uint device_type_mask = DEVICE_MASK_ALL);
static DeviceInfo dummy_device(const string &error_msg = "");
static string device_capabilities(const uint device_type_mask = DEVICE_MASK_ALL);
static DeviceInfo get_multi_device(const vector<DeviceInfo> &subdevices,
const int threads,
bool background);
/* Tag devices lists for update. */
static void tag_update();
static void free_memory();
protected:
/* Memory allocation, only accessed through device_memory. */
friend class MultiDevice;
friend class DeviceServer;
friend class device_memory;
virtual void *host_alloc(const MemoryType type, const size_t size);
virtual void host_free(const MemoryType type, void *host_pointer, const size_t size);
virtual void mem_alloc(device_memory &mem) = 0;
virtual void mem_copy_to(device_memory &mem) = 0;
virtual void mem_move_to_host(device_memory &mem) = 0;
virtual void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) = 0;
virtual void mem_zero(device_memory &mem) = 0;
virtual void mem_free(device_memory &mem) = 0;
private:
/* Indicted whether device types and devices lists were initialized. */
static bool need_types_update, need_devices_update;
static thread_mutex device_mutex;
static vector<DeviceInfo> &cuda_devices();
static vector<DeviceInfo> &optix_devices();
static vector<DeviceInfo> &cpu_devices();
static vector<DeviceInfo> &hip_devices();
static vector<DeviceInfo> &metal_devices();
static vector<DeviceInfo> &oneapi_devices();
static uint devices_initialized_mask;
};
/* Device, which is GPU, with some common functionality for GPU back-ends. */
class GPUDevice : public Device {
protected:
GPUDevice(const DeviceInfo &info_, Stats &stats_, Profiler &profiler_, bool headless_)
: Device(info_, stats_, profiler_, headless_), image_info(this, "image_info", MEM_GLOBAL)
{
}
public:
~GPUDevice() noexcept(false) override;
/* For GPUs that can use bindless textures in some way or another. */
device_vector<KernelImageInfo> image_info;
thread_mutex image_info_mutex;
bool need_image_info = false;
/* Returns true if the image info was copied to the device (meaning, some more
* re-initialization might be needed). */
virtual bool load_image_info(DeviceQueue *queue);
protected:
/* Memory allocation, only accessed through device_memory. */
friend class device_memory;
bool can_map_host = false;
size_t map_host_used = 0;
size_t map_host_limit = 0;
size_t device_image_headroom = 0;
size_t device_working_headroom = 0;
using texMemObject = unsigned long long;
using arrayMemObject = uintptr_t;
struct Mem {
Mem() = default;
texMemObject texobject = 0;
arrayMemObject array = 0;
};
using MemMap = map<device_memory *, Mem>;
MemMap device_mem_map;
thread_mutex device_mem_map_mutex;
/* Simple counter which will try to track amount of used device memory */
size_t device_mem_in_use = 0;
virtual void init_host_memory(const size_t preferred_texture_headroom = 0,
const size_t preferred_working_headroom = 0);
virtual void move_textures_to_host(const size_t size,
const size_t headroom,
const bool for_texture);
/* Allocation, deallocation and copy functions, with corresponding
* support of device/host allocations. */
virtual GPUDevice::Mem *generic_alloc(device_memory &mem, const size_t pitch_padding = 0);
virtual void generic_free(device_memory &mem);
virtual void generic_copy_to(device_memory &mem);
/* total - amount of device memory, free - amount of available device memory */
virtual void get_device_memory_info(size_t &total, size_t &free) = 0;
/* Device side memory. */
virtual bool alloc_device(void *&device_pointer, const size_t size) = 0;
virtual void free_device(void *device_pointer) = 0;
/* Shared memory. */
virtual bool shared_alloc(void *&shared_pointer, const size_t size) = 0;
virtual void shared_free(void *shared_pointer) = 0;
bool is_shared(const void *shared_pointer,
const device_ptr device_pointer,
Device *sub_device) override;
/* This function should return device pointer corresponding to shared pointer, which
* is host buffer, allocated in `shared_alloc`. */
virtual void *shared_to_device_pointer(const void *shared_pointer) = 0;
/* Memory copy. */
virtual void copy_host_to_device(void *device_pointer,
void *host_pointer,
const size_t size) = 0;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/dummy/device.h"
#include "device/device.h"
#include "device/queue.h"
CCL_NAMESPACE_BEGIN
/* Dummy device for when creating an appropriate rendering device fails. */
class DummyDevice : public Device {
public:
DummyDevice(const DeviceInfo &info_, Stats &stats_, Profiler &profiler_, bool headless_)
: Device(info_, stats_, profiler_, headless_)
{
error_msg = info.error_msg;
}
~DummyDevice() override = default;
BVHLayoutMask get_bvh_layout_mask(uint /*kernel_features*/) const override
{
return 0;
}
void mem_alloc(device_memory & /*mem*/) override {}
void mem_copy_to(device_memory & /*mem*/) override {}
void mem_move_to_host(device_memory & /*mem*/) override {}
void mem_copy_from(
device_memory & /*mem*/, size_t /*y*/, size_t /*w*/, size_t /*h*/, size_t /*elem*/) override
{
}
void mem_zero(device_memory & /*mem*/) override {}
void mem_free(device_memory & /*mem*/) override {}
void const_copy_to(const char * /*name*/, void * /*host*/, size_t /*size*/) override {}
};
unique_ptr<Device> device_dummy_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
return make_unique<DummyDevice>(info, stats, profiler, headless);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
unique_ptr<Device> device_dummy_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
CCL_NAMESPACE_END

View File

@@ -0,0 +1,9 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/graphics_interop.h"
CCL_NAMESPACE_BEGIN
CCL_NAMESPACE_END

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/types.h"
CCL_NAMESPACE_BEGIN
class GraphicsInteropBuffer;
/* Device-side graphics interoperability support.
*
* Takes care of holding all the handlers needed by the device to implement interoperability with
* the graphics library. */
class DeviceGraphicsInterop {
public:
DeviceGraphicsInterop() = default;
virtual ~DeviceGraphicsInterop() = default;
/* Update this device-side graphics interoperability buffer with the given destination
* resource information. */
virtual void set_buffer(GraphicsInteropBuffer &interop_buffer) = 0;
virtual device_ptr map() = 0;
virtual void unmap() = 0;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,325 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/hip/device.h"
#include "device/device.h"
#include "util/log.h"
#ifdef WITH_HIP
# include "device/hip/device_impl.h"
# include "integrator/denoiser_oidn_gpu.h" // IWYU pragma: keep
# include "util/string.h"
# ifdef _WIN32
# include "util/windows.h"
# endif
#endif /* WITH_HIP */
#ifdef WITH_HIPRT
# include "device/hiprt/device_impl.h"
#endif
CCL_NAMESPACE_BEGIN
bool device_hip_init()
{
#if !defined(WITH_HIP)
return false;
#elif defined(WITH_HIP_DYNLOAD)
static bool initialized = false;
static bool result = false;
if (initialized) {
return result;
}
initialized = true;
int hipew_result = hipewInit(HIPEW_INIT_HIP);
if (hipew_result == HIPEW_SUCCESS) {
LOG_INFO << "HIPEW initialization succeeded";
if (!hipSupportsDriver()) {
LOG_WARNING << "Driver version is too old";
}
else if (HIPDevice::have_precompiled_kernels()) {
LOG_INFO << "Found precompiled kernels";
result = true;
}
else if (hipewCompilerPath() != nullptr) {
LOG_INFO << "Found HIPCC " << hipewCompilerPath();
result = true;
}
else {
LOG_INFO << "Neither precompiled kernels nor HIPCC was found,"
<< " unable to use HIP";
}
}
else {
if (hipew_result == HIPEW_ERROR_ATEXIT_FAILED) {
LOG_WARNING << "HIPEW initialization failed: Error setting up atexit() handler";
}
else if (hipew_result == HIPEW_ERROR_OLD_DRIVER) {
LOG_WARNING << "HIPEW initialization failed: Driver version too old, requires AMD Adrenalin "
"driver 24.9.1 or newer, or AMD Radeon Pro driver 24.Q4 or newer";
}
else {
LOG_WARNING << "HIPEW initialization failed: Error opening HIP dynamic library";
}
}
return result;
#else /* WITH_HIP_DYNLOAD */
return true;
#endif /* WITH_HIP_DYNLOAD */
}
unique_ptr<Device> device_hip_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
const bool headless)
{
#ifdef WITH_HIPRT
if (info.use_hardware_raytracing) {
return make_unique<HIPRTDevice>(info, stats, profiler, headless);
}
return make_unique<HIPDevice>(info, stats, profiler, headless);
#elif defined(WITH_HIP)
return make_unique<HIPDevice>(info, stats, profiler, headless);
#else
(void)info;
(void)stats;
(void)profiler;
(void)headless;
LOG_FATAL << "Request to create HIP device without compiled-in support. Should never happen.";
return nullptr;
#endif
}
#ifdef WITH_HIP
static hipError_t device_hip_safe_init()
{
# ifdef _WIN32
__try
{
return hipInit(0);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
/* Ignore crashes inside the HIP driver and hope we can
* survive even with corrupted HIP installs. */
fprintf(stderr, "Cycles HIP: driver crashed, continuing without HIP.\n");
}
return hipErrorNoDevice;
# else
return hipInit(0);
# endif
}
#endif /* WITH_HIP */
void device_hip_info(vector<DeviceInfo> &devices)
{
#ifdef WITH_HIP
hipError_t result = device_hip_safe_init();
if (result != hipSuccess) {
if (result != hipErrorNoDevice) {
LOG_ERROR << "HIP hipInit: " << hipewErrorString(result);
}
return;
}
int count = 0;
result = hipGetDeviceCount(&count);
if (result != hipSuccess) {
LOG_ERROR << "HIP hipGetDeviceCount: " << hipewErrorString(result);
return;
}
# ifdef WITH_HIPRT
const bool has_hardware_raytracing = HIPRTDevice::is_supported();
# else
const bool has_hardware_raytracing = false;
# endif
vector<DeviceInfo> display_devices;
for (int num = 0; num < count; num++) {
char name[256];
result = hipDeviceGetName(name, 256, num);
if (result != hipSuccess) {
LOG_ERROR << "HIP hipDeviceGetName: " << hipewErrorString(result);
continue;
}
if (!hipSupportsDevice(num)) {
continue;
}
DeviceInfo info;
info.type = DEVICE_HIP;
info.description = string(name);
info.num = num;
info.has_nanovdb = true;
info.has_gpu_queue = true;
/* Check if the device has P2P access to any other device in the system. */
for (int peer_num = 0; peer_num < count && !info.has_peer_memory; peer_num++) {
if (num != peer_num) {
if (hipSupportsDevice(peer_num)) {
int can_access = 0;
hipDeviceCanAccessPeer(&can_access, num, peer_num);
info.has_peer_memory = (can_access != 0);
}
}
}
/* Disable on RDNA1 due to bug rendering curves in HIP-RT 2.5 or HIP SDK 6.3. */
info.use_hardware_raytracing = has_hardware_raytracing && hipIsRDNA2OrNewer(num);
int pci_location[3] = {0, 0, 0};
hipDeviceGetAttribute(&pci_location[0], hipDeviceAttributePciDomainID, num);
hipDeviceGetAttribute(&pci_location[1], hipDeviceAttributePciBusId, num);
hipDeviceGetAttribute(&pci_location[2], hipDeviceAttributePciDeviceId, num);
info.id = string_printf("HIP_%s_%04x:%02x:%02x",
name,
(unsigned int)pci_location[0],
(unsigned int)pci_location[1],
(unsigned int)pci_location[2]);
info.denoisers = 0;
# if defined(WITH_OPENIMAGEDENOISE)
/* Check first if OIDN supports it, not doing so can crash the HIP driver with
* "hipErrorNoBinaryForGpu: Unable to find code object for all current devices". */
# if OIDN_VERSION >= 20300
if (hipSupportsDeviceOIDN(num) && oidnIsHIPDeviceSupported(num)) {
# else
if (hipSupportsDeviceOIDN(num) && OIDNDenoiserGPU::is_device_supported(info)) {
# endif
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
}
# endif
/* If device has a kernel timeout and no compute preemption, we assume
* it is connected to a display and will freeze the display while doing
* computations. */
int timeout_attr = 0;
hipDeviceGetAttribute(&timeout_attr, hipDeviceAttributeKernelExecTimeout, num);
if (timeout_attr) {
LOG_INFO << "Device is recognized as display.";
info.description += " (Display)";
info.display_device = true;
display_devices.push_back(info);
}
else {
LOG_INFO << "Device has compute preemption or is not used for display.";
devices.push_back(info);
}
LOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE) {
LOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}
}
if (!display_devices.empty()) {
devices.insert(devices.end(), display_devices.begin(), display_devices.end());
}
#else /* WITH_HIP */
(void)devices;
#endif /* WITH_HIP */
}
string device_hip_capabilities()
{
#ifdef WITH_HIP
hipError_t result = device_hip_safe_init();
if (result != hipSuccess) {
if (result != hipErrorNoDevice) {
return string("Error initializing HIP: ") + hipewErrorString(result);
}
return "No HIP device found\n";
}
int count;
result = hipGetDeviceCount(&count);
if (result != hipSuccess) {
return string("Error getting devices: ") + hipewErrorString(result);
}
string capabilities;
for (int num = 0; num < count; num++) {
char name[256];
if (hipDeviceGetName(name, 256, num) != hipSuccess) {
continue;
}
capabilities += string("\t") + name + "\n";
int value;
# define GET_ATTR(attr) \
{ \
if (hipDeviceGetAttribute(&value, hipDeviceAttribute##attr, num) == hipSuccess) { \
capabilities += string_printf("\t\thipDeviceAttribute" #attr "\t\t\t%d\n", value); \
} \
} \
(void)0
/* TODO(sergey): Strip all attributes which are not useful for us
* or does not depend on the driver.
*/
GET_ATTR(MaxThreadsPerBlock);
GET_ATTR(MaxBlockDimX);
GET_ATTR(MaxBlockDimY);
GET_ATTR(MaxBlockDimZ);
GET_ATTR(MaxGridDimX);
GET_ATTR(MaxGridDimY);
GET_ATTR(MaxGridDimZ);
GET_ATTR(MaxSharedMemoryPerBlock);
GET_ATTR(TotalConstantMemory);
GET_ATTR(WarpSize);
GET_ATTR(MaxPitch);
GET_ATTR(MaxRegistersPerBlock);
GET_ATTR(ClockRate);
GET_ATTR(TextureAlignment);
GET_ATTR(MultiprocessorCount);
GET_ATTR(KernelExecTimeout);
GET_ATTR(Integrated);
GET_ATTR(CanMapHostMemory);
GET_ATTR(ComputeMode);
GET_ATTR(MaxTexture1DWidth);
GET_ATTR(MaxTexture2DWidth);
GET_ATTR(MaxTexture2DHeight);
GET_ATTR(MaxTexture3DWidth);
GET_ATTR(MaxTexture3DHeight);
GET_ATTR(MaxTexture3DDepth);
GET_ATTR(ConcurrentKernels);
GET_ATTR(EccEnabled);
GET_ATTR(MemoryClockRate);
GET_ATTR(MemoryBusWidth);
GET_ATTR(L2CacheSize);
GET_ATTR(MaxThreadsPerMultiProcessor);
GET_ATTR(ComputeCapabilityMajor);
GET_ATTR(ComputeCapabilityMinor);
GET_ATTR(MaxSharedMemoryPerMultiprocessor);
GET_ATTR(ManagedMemory);
GET_ATTR(IsMultiGpuBoard);
# undef GET_ATTR
capabilities += "\n";
}
return capabilities;
#else /* WITH_HIP */
return "";
#endif /* WITH_HIP */
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/string.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
bool device_hip_init();
unique_ptr<Device> device_hip_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
void device_hip_info(vector<DeviceInfo> &devices);
string device_hip_capabilities();
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_HIP
# include "device/device.h"
# include "device/hip/kernel.h"
# include "device/hip/queue.h"
# include "device/hip/util.h"
# ifdef WITH_HIP_DYNLOAD
# include "hipew.h"
# endif
CCL_NAMESPACE_BEGIN
class DeviceQueue;
class HIPDevice : public GPUDevice {
friend class HIPContextScope;
public:
hipDevice_t hipDevice;
hipCtx_t hipContext;
hipModule_t hipModule;
int pitch_alignment;
int hipDevId;
int hipDevArchitecture;
int hipRuntimeVersion;
bool first_error;
HIPDeviceKernels kernels;
static bool have_precompiled_kernels();
BVHLayoutMask get_bvh_layout_mask(uint /*kernel_features*/) const override;
void set_error(const string &error) override;
HIPDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless);
~HIPDevice() override;
bool support_device(const uint /*kernel_features*/);
bool check_peer_access(Device *peer_device) override;
bool use_adaptive_compilation();
string compile_kernel_get_common_cflags(const uint kernel_features);
string compile_kernel(const uint kernel_features, const char *name, const char *base = "hip");
bool load_kernels(const uint kernel_features) override;
void reserve_local_memory(const uint kernel_features);
/* All memory types. */
void mem_alloc(device_memory &mem) override;
void mem_copy_to(device_memory &mem) override;
void mem_move_to_host(device_memory &mem) override;
void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) override;
void mem_zero(device_memory &mem) override;
void mem_free(device_memory &mem) override;
device_ptr mem_alloc_sub_ptr(device_memory &mem, const size_t offset, size_t /*size*/) override;
/* Global memory. */
void global_alloc(device_memory &mem);
void global_copy_to(device_memory &mem);
void global_free(device_memory &mem);
/* Image memory. */
void image_alloc(device_image &mem);
void image_copy_to(device_image &mem);
void image_free(device_image &mem);
/* Device side memory. */
void get_device_memory_info(size_t &total, size_t &free) override;
bool alloc_device(void *&device_pointer, const size_t size) override;
void free_device(void *device_pointer) override;
/* Shared memory. */
bool shared_alloc(void *&shared_pointer, const size_t size) override;
void shared_free(void *shared_pointer) override;
void *shared_to_device_pointer(const void *shared_pointer) override;
/* Memory copy. */
void copy_host_to_device(void *device_pointer, void *host_pointer, const size_t size) override;
void const_copy_to(const char *name, void *host, const size_t size) override;
/* Graphics resources interoperability. */
bool should_use_graphics_interop(const GraphicsInteropDevice &interop_device,
const bool log) override;
unique_ptr<DeviceQueue> gpu_queue_create() override;
int get_num_multiprocessors();
int get_max_num_threads_per_multiprocessor();
protected:
bool get_device_attribute(hipDeviceAttribute_t attribute, int *value);
int get_device_default_attribute(hipDeviceAttribute_t attribute, const int default_value);
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,120 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_HIP
# include "device/hip/graphics_interop.h"
# include "device/hip/device_impl.h"
# include "device/hip/util.h"
CCL_NAMESPACE_BEGIN
HIPDeviceGraphicsInterop::HIPDeviceGraphicsInterop(HIPDeviceQueue *queue)
: queue_(queue), device_(static_cast<HIPDevice *>(queue->device))
{
}
HIPDeviceGraphicsInterop::~HIPDeviceGraphicsInterop()
{
HIPContextScope scope(device_);
free();
}
void HIPDeviceGraphicsInterop::set_buffer(GraphicsInteropBuffer &interop_buffer)
{
HIPContextScope scope(device_);
if (interop_buffer.is_empty()) {
free();
return;
}
need_zero_ |= interop_buffer.take_zero();
if (!interop_buffer.has_new_handle()) {
return;
}
free();
switch (interop_buffer.get_type()) {
case GraphicsInteropDevice::OPENGL: {
const hipError_t result = hipGraphicsGLRegisterBuffer(
&hip_graphics_resource_, interop_buffer.take_handle(), hipGraphicsRegisterFlagsNone);
if (result != hipSuccess) {
LOG_ERROR << "Error registering OpenGL buffer: " << hipewErrorString(result);
break;
}
buffer_size_ = interop_buffer.get_size();
break;
}
case GraphicsInteropDevice::VULKAN:
case GraphicsInteropDevice::METAL:
case GraphicsInteropDevice::NONE:
/* TODO: implement vulkan support. */
break;
}
}
device_ptr HIPDeviceGraphicsInterop::map()
{
hipDeviceptr_t hip_buffer = 0;
if (hip_graphics_resource_) {
HIPContextScope scope(device_);
size_t bytes;
hip_device_assert(device_,
hipGraphicsMapResources(1, &hip_graphics_resource_, queue_->stream()));
hip_device_assert(
device_, hipGraphicsResourceGetMappedPointer(&hip_buffer, &bytes, hip_graphics_resource_));
}
else {
/* Vulkan buffer is always mapped. */
hip_buffer = hip_external_memory_ptr_;
}
if (hip_buffer && need_zero_) {
hip_device_assert(device_, hipMemsetD8Async(hip_buffer, 0, buffer_size_, queue_->stream()));
need_zero_ = false;
}
return static_cast<device_ptr>(hip_buffer);
}
void HIPDeviceGraphicsInterop::unmap()
{
if (hip_graphics_resource_) {
HIPContextScope scope(device_);
hip_device_assert(device_,
hipGraphicsUnmapResources(1, &hip_graphics_resource_, queue_->stream()));
}
}
void HIPDeviceGraphicsInterop::free()
{
if (hip_graphics_resource_) {
hip_device_assert(device_, hipGraphicsUnregisterResource(hip_graphics_resource_));
hip_graphics_resource_ = nullptr;
}
if (hip_external_memory_ptr_) {
hip_device_assert(device_, hipFree(hip_external_memory_ptr_));
hip_external_memory_ptr_ = 0;
}
buffer_size_ = 0;
need_zero_ = false;
}
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_HIP
# include "device/graphics_interop.h"
# include "session/display_driver.h"
# ifdef WITH_HIP_DYNLOAD
# include "hipew.h"
# endif
CCL_NAMESPACE_BEGIN
class HIPDevice;
class HIPDeviceQueue;
class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
public:
explicit HIPDeviceGraphicsInterop(HIPDeviceQueue *queue);
HIPDeviceGraphicsInterop(const HIPDeviceGraphicsInterop &other) = delete;
HIPDeviceGraphicsInterop(HIPDeviceGraphicsInterop &&other) noexcept = delete;
~HIPDeviceGraphicsInterop() override;
HIPDeviceGraphicsInterop &operator=(const HIPDeviceGraphicsInterop &other) = delete;
HIPDeviceGraphicsInterop &operator=(HIPDeviceGraphicsInterop &&other) = delete;
void set_buffer(GraphicsInteropBuffer &interop_buffer) override;
device_ptr map() override;
void unmap() override;
protected:
HIPDeviceQueue *queue_ = nullptr;
HIPDevice *device_ = nullptr;
/* Size of the buffer in bytes. */
size_t buffer_size_ = 0;
/* The destination was requested to be cleared. */
bool need_zero_ = false;
hipGraphicsResource hip_graphics_resource_ = nullptr;
hipDeviceptr_t hip_external_memory_ptr_ = 0;
void free();
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_HIP
# include "kernel/types.h"
# include "device/hip/device_impl.h"
# include "device/hip/kernel.h"
CCL_NAMESPACE_BEGIN
bool HIPDeviceKernels::load_kernel(HIPDevice *device,
hipModule_t hip_module,
const DeviceKernel kernel)
{
if (available(kernel)) {
return true;
}
if (!device_kernel_has_gpu_function(kernel)) {
return false;
}
HIPDeviceKernel &hip_kernel = kernels_[int(kernel)];
const std::string function_name = std::string("kernel_gpu_") + device_kernel_as_string(kernel);
hip_device_assert(device,
hipModuleGetFunction(&hip_kernel.function, hip_module, function_name.c_str()));
if (!hip_kernel.function) {
LOG_ERROR << "Unable to load kernel " << function_name;
return false;
}
hip_device_assert(device, hipFuncSetCacheConfig(hip_kernel.function, hipFuncCachePreferL1));
hip_device_assert(
device,
hipModuleOccupancyMaxPotentialBlockSize(
&hip_kernel.min_blocks, &hip_kernel.num_threads_per_block, hip_kernel.function, 0, 0));
LOG_DEBUG << "Loaded kernel: " << function_name;
return true;
}
void HIPDeviceKernels::load_all(HIPDevice *device, hipModule_t hip_module)
{
LOG_DEBUG << "Loading all HIP kernels";
for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) {
load_kernel(device, hip_module, DeviceKernel(i));
}
}
void HIPDeviceKernels::load_raytrace(HIPDevice *device, hipModule_t hip_module)
{
LOG_DEBUG << "Loading ray-tracing HIP-RT kernels";
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST);
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW);
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE);
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK);
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT);
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
load_kernel(device, hip_module, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE);
}
const HIPDeviceKernel &HIPDeviceKernels::get(DeviceKernel kernel) const
{
return kernels_[(int)kernel];
}
bool HIPDeviceKernels::available(DeviceKernel kernel) const
{
return kernels_[(int)kernel].function != nullptr;
}
CCL_NAMESPACE_END
#endif /* WITH_HIP */

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_HIP
# include "device/kernel.h"
# ifdef WITH_HIP_DYNLOAD
# include "hipew.h"
# endif
CCL_NAMESPACE_BEGIN
class HIPDevice;
/* HIP kernel and associate occupancy information. */
class HIPDeviceKernel {
public:
hipFunction_t function = nullptr;
int num_threads_per_block = 0;
int min_blocks = 0;
};
/* Cache of HIP kernels for each DeviceKernel. */
class HIPDeviceKernels {
public:
void load_all(HIPDevice *device, hipModule_t hip_module);
void load_raytrace(HIPDevice *device, hipModule_t hip_module);
const HIPDeviceKernel &get(DeviceKernel kernel) const;
bool available(DeviceKernel kernel) const;
protected:
bool load_kernel(HIPDevice *device, hipModule_t hip_module, DeviceKernel kernel);
HIPDeviceKernel kernels_[DEVICE_KERNEL_NUM];
};
CCL_NAMESPACE_END
#endif /* WITH_HIP */

View File

@@ -0,0 +1,267 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_HIP
# include "device/hip/queue.h"
# include "device/hip/device_impl.h"
# include "device/hip/graphics_interop.h"
# include "device/hip/kernel.h"
CCL_NAMESPACE_BEGIN
/* HIPDeviceQueue */
HIPDeviceQueue::HIPDeviceQueue(HIPDevice *device)
: DeviceQueue(device), hip_device_(device), hip_stream_(nullptr)
{
const HIPContextScope scope(hip_device_);
hip_device_assert(hip_device_, hipStreamCreateWithFlags(&hip_stream_, hipStreamNonBlocking));
}
HIPDeviceQueue::~HIPDeviceQueue()
{
const HIPContextScope scope(hip_device_);
hipStreamDestroy(hip_stream_);
}
int HIPDeviceQueue::num_concurrent_states(const size_t state_size) const
{
const int max_num_threads = hip_device_->get_num_multiprocessors() *
hip_device_->get_max_num_threads_per_multiprocessor();
int num_states = ((max_num_threads == 0) ? 65536 : max_num_threads) * 16;
const char *factor_str = getenv("CYCLES_CONCURRENT_STATES_FACTOR");
if (factor_str) {
const float factor = (float)atof(factor_str);
if (factor != 0.0f) {
num_states = max((int)(num_states * factor), 1024);
}
else {
LOG_TRACE << "CYCLES_CONCURRENT_STATES_FACTOR evaluated to 0";
}
}
LOG_TRACE << "GPU queue concurrent states: " << num_states << ", using up to "
<< string_human_readable_size(num_states * state_size);
return num_states;
}
int HIPDeviceQueue::num_concurrent_busy_states(const size_t /*state_size*/) const
{
const int max_num_threads = hip_device_->get_num_multiprocessors() *
hip_device_->get_max_num_threads_per_multiprocessor();
if (max_num_threads == 0) {
return 65536;
}
return 4 * max_num_threads;
}
void HIPDeviceQueue::init_execution()
{
/* Synchronize all textures and memory copies before executing task. */
HIPContextScope scope(hip_device_);
hip_device_->load_image_info(nullptr);
hip_device_assert(hip_device_, hipDeviceSynchronize());
debug_init_execution();
}
void HIPDeviceQueue::load_image_info()
{
HIPContextScope scope(hip_device_);
hip_device_->load_image_info(this);
}
bool HIPDeviceQueue::enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args)
{
if (hip_device_->have_error()) {
return false;
}
debug_enqueue_begin(kernel, work_size);
const HIPContextScope scope(hip_device_);
/* Update image info in case memory moved to host. */
if (hip_device_->load_image_info(nullptr)) {
hip_device_assert(hip_device_, hipDeviceSynchronize());
if (hip_device_->have_error()) {
return false;
}
}
/* Compute kernel launch parameters. */
const HIPDeviceKernel &hip_kernel = hip_device_->kernels.get(kernel);
const int num_threads_per_block = hip_kernel.num_threads_per_block;
const int num_blocks = divide_up(work_size, num_threads_per_block);
int shared_mem_bytes = 0;
switch (kernel) {
case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_PATHS_ARRAY:
/* See parall_active_index.h for why this amount of shared memory is needed. */
shared_mem_bytes = (num_threads_per_block + 1) * sizeof(int);
break;
default:
break;
}
/* Launch kernel. */
assert_success(hipModuleLaunchKernel(hip_kernel.function,
num_blocks,
1,
1,
num_threads_per_block,
1,
1,
shared_mem_bytes,
hip_stream_,
const_cast<void **>(args.values),
nullptr),
"enqueue");
debug_enqueue_end();
return !(hip_device_->have_error());
}
bool HIPDeviceQueue::synchronize()
{
if (hip_device_->have_error()) {
return false;
}
const HIPContextScope scope(hip_device_);
assert_success(hipStreamSynchronize(hip_stream_), "synchronize");
debug_synchronize();
return !(hip_device_->have_error());
}
void HIPDeviceQueue::zero_to_device(device_memory &mem)
{
assert(mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
/* Allocate on demand. */
if (mem.device_pointer == 0) {
if (mem.type == MEM_GLOBAL) {
hip_device_->global_alloc(mem);
}
else {
hip_device_->mem_alloc(mem);
}
}
/* Zero memory on device. */
device_ptr d_ptr = mem.device->mem_device_ptr(mem, hip_device_);
assert(d_ptr != 0);
const HIPContextScope scope(hip_device_);
assert_success(hipMemsetD8Async((hipDeviceptr_t)d_ptr, 0, mem.memory_size(), hip_stream_),
"zero_to_device");
}
void HIPDeviceQueue::copy_to_device(device_memory &mem)
{
assert(mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
/* Allocate on demand. */
if (mem.device_pointer == 0) {
if (mem.type == MEM_GLOBAL) {
hip_device_->global_alloc(mem);
}
else {
hip_device_->mem_alloc(mem);
}
}
device_ptr d_ptr = mem.device->mem_device_ptr(mem, hip_device_);
assert(d_ptr != 0);
assert(mem.host_pointer != nullptr);
/* Copy memory to device. */
const HIPContextScope scope(hip_device_);
assert_success(
hipMemcpyHtoDAsync((hipDeviceptr_t)d_ptr, mem.host_pointer, mem.memory_size(), hip_stream_),
"copy_to_device");
}
void HIPDeviceQueue::copy_from_device(device_memory &mem)
{
assert(mem.type != MEM_GLOBAL && mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
assert(mem.device_pointer != 0);
assert(mem.host_pointer != nullptr);
/* Copy memory from device. */
const HIPContextScope scope(hip_device_);
assert_success(
hipMemcpyDtoHAsync(
mem.host_pointer, (hipDeviceptr_t)mem.device_pointer, mem.memory_size(), hip_stream_),
"copy_from_device");
}
void *HIPDeviceQueue::copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage)
{
if (mem.memory_size() == 0) {
return nullptr;
}
storage.resize(mem.memory_size());
device_ptr d_ptr = mem.device->mem_device_ptr(mem, hip_device_);
assert(d_ptr != 0);
const HIPContextScope scope(hip_device_);
assert_success(
hipMemcpyDtoHAsync(storage.data(), (hipDeviceptr_t)d_ptr, mem.memory_size(), hip_stream_),
"copy_from_device_synchronized");
synchronize();
return storage.data();
}
void HIPDeviceQueue::assert_success(hipError_t result, const char *operation)
{
if (result != hipSuccess) {
const char *name = hipewErrorString(result);
hip_device_->set_error(
string_printf("%s in HIP queue %s (%s)", name, operation, debug_active_kernels().c_str()));
}
}
unique_ptr<DeviceGraphicsInterop> HIPDeviceQueue::graphics_interop_create()
{
return make_unique<HIPDeviceGraphicsInterop>(this);
}
CCL_NAMESPACE_END
#endif /* WITH_HIP */

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_HIP
# include "device/memory.h"
# include "device/queue.h"
# include "device/hip/util.h"
CCL_NAMESPACE_BEGIN
class HIPDevice;
class device_memory;
/* Base class for HIP queues. */
class HIPDeviceQueue : public DeviceQueue {
public:
HIPDeviceQueue(HIPDevice *device);
~HIPDeviceQueue() override;
int num_concurrent_states(const size_t state_size) const override;
int num_concurrent_busy_states(const size_t state_size) const override;
void init_execution() override;
void load_image_info() override;
bool enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args) override;
bool synchronize() override;
void zero_to_device(device_memory &mem) override;
void copy_to_device(device_memory &mem) override;
void copy_from_device(device_memory &mem) override;
void *copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage) override;
virtual hipStream_t stream()
{
return hip_stream_;
}
unique_ptr<DeviceGraphicsInterop> graphics_interop_create() override;
protected:
HIPDevice *hip_device_;
hipStream_t hip_stream_;
void assert_success(hipError_t result, const char *operation);
};
CCL_NAMESPACE_END
#endif /* WITH_HIP */

View File

@@ -0,0 +1,77 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_HIP
# include "device/hip/util.h"
# include "device/hip/device_impl.h"
CCL_NAMESPACE_BEGIN
HIPContextScope::HIPContextScope(HIPDevice *device) : device(device)
{
hip_device_assert(device, hipCtxPushCurrent(device->hipContext));
}
HIPContextScope::~HIPContextScope()
{
hip_device_assert(device, hipCtxPopCurrent(nullptr));
}
# ifndef WITH_HIP_DYNLOAD
const char *hipewErrorString(hipError_t result)
{
/* We can only give error code here without major code duplication, that
* should be enough since dynamic loading is only being disabled by folks
* who knows what they're doing anyway.
*
* NOTE: Avoid call from several threads.
*/
static string error;
error = string_printf("%d", result);
return error.c_str();
}
const char *hipewCompilerPath()
{
return CYCLES_HIP_HIPCC_EXECUTABLE;
}
int hipewCompilerVersion()
{
return (HIP_VERSION / 100) + (HIP_VERSION % 100 / 10);
}
# endif /* !WITH_HIP_DYNLOAD */
bool hipSupportsDriver()
{
int hip_driver_version = 0;
hipError_t result = hipDriverGetVersion(&hip_driver_version);
if (result != hipSuccess) {
LOG_WARNING << "Error getting driver version: " << hipewErrorString(result);
return false;
}
LOG_TRACE << "Detected HIP driver version: " << hip_driver_version;
# ifdef _WIN32
if (hip_driver_version < 60241512) {
/* Users get error messages about being unable to find GPU binaries on older GPU drivers.
* 60241512 corresponds to Adrenalin 24.9.1. */
return false;
}
# else /* Linux */
if (hip_driver_version < 60342131) {
/* Users get error messages about being unable to load GPU kernels on older ROCm versions.
* 60342131 corresponds to ROCm 6.3.0 */
return false;
}
# endif
return true;
}
CCL_NAMESPACE_END
#endif /* WITH_HIP */

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_HIP
# include <cstring>
# include <string>
# ifdef WITH_HIP_DYNLOAD
# include "hipew.h"
# endif
CCL_NAMESPACE_BEGIN
class HIPDevice;
/* Utility to push/pop HIP context. */
class HIPContextScope {
public:
HIPContextScope(HIPDevice *device);
~HIPContextScope();
private:
HIPDevice *device;
};
/* Utility for checking return values of HIP function calls. */
# define hip_device_assert(hip_device, stmt) \
{ \
hipError_t result = stmt; \
if (result != hipSuccess) { \
const char *name = hipewErrorString(result); \
hip_device->set_error( \
string_printf("%s in %s (%s:%d)", name, #stmt, __FILE__, __LINE__)); \
} \
} \
(void)0
# define hip_assert(stmt) hip_device_assert(this, stmt)
# ifndef WITH_HIP_DYNLOAD
/* Transparently implement some functions, so majority of the file does not need
* to worry about difference between dynamically loaded and linked HIP at all. */
const char *hipewErrorString(hipError_t result);
const char *hipewCompilerPath();
int hipewCompilerVersion();
# endif /* !WITH_HIP_DYNLOAD */
bool hipSupportsDriver();
static std::string hipDeviceArch(const int hipDevId)
{
hipDeviceProp_t props;
hipGetDeviceProperties(&props, hipDevId);
const char *arch = strtok(props.gcnArchName, ":");
return (arch == nullptr) ? props.gcnArchName : arch;
}
static inline bool hipSupportsDevice(const int hipDevId)
{
int major, minor;
hipDeviceGetAttribute(&major, hipDeviceAttributeComputeCapabilityMajor, hipDevId);
hipDeviceGetAttribute(&minor, hipDeviceAttributeComputeCapabilityMinor, hipDevId);
return (major >= 10);
}
static inline bool hipIsRDNA2OrNewer(const int hipDevId)
{
int major, minor;
hipDeviceGetAttribute(&major, hipDeviceAttributeComputeCapabilityMajor, hipDevId);
hipDeviceGetAttribute(&minor, hipDeviceAttributeComputeCapabilityMinor, hipDevId);
return (major > 10 || (major == 10 && minor >= 3));
}
static inline bool hipSupportsDeviceOIDN(const int hipDevId)
{
/* Matches HIPDevice::getArch in HIP. */
const std::string arch = hipDeviceArch(hipDevId);
return (arch == "gfx1030" || arch == "gfx1031" || arch == "gfx1032" || arch == "gfx1034" ||
arch == "gfx1035" || arch == "gfx1036" || arch == "gfx1100" || arch == "gfx1101" ||
arch == "gfx1102" || arch == "gfx1103" || arch == "gfx1150" || arch == "gfx1151" ||
arch == "gfx1152" || arch == "gfx1200" || arch == "gfx1201");
}
CCL_NAMESPACE_END
#endif /* WITH_HIP */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_HIPRT
# include "device/hip/device_impl.h"
# include "device/hip/kernel.h"
# include "device/hip/queue.h"
# include "device/hiprt/queue.h"
# include <hiprt/hiprt_types.h>
CCL_NAMESPACE_BEGIN
class Mesh;
class Hair;
class PointCloud;
class Geometry;
class Object;
class BVHHIPRT;
class HIPRTDevice : public HIPDevice {
public:
static bool is_supported();
BVHLayoutMask get_bvh_layout_mask(const uint kernel_features) const override;
HIPRTDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless);
~HIPRTDevice() override;
unique_ptr<DeviceQueue> gpu_queue_create() override;
string compile_kernel_get_common_cflags(const uint kernel_features);
string compile_kernel(const uint kernel_features, const char *name, const char *base = "hiprt");
bool load_kernels(const uint kernel_features) override;
void const_copy_to(const char *name, void *host, const size_t size) override;
void build_bvh(BVH *bvh, Progress &progress, bool refit) override;
void release_bvh(BVH *bvh) override;
hiprtContext get_hiprt_context()
{
return hiprt_context;
}
hiprtGlobalStackBuffer global_stack_buffer;
protected:
enum Filter_Function { Closest = 0, Shadows, Local, Volume, Max_Intersect_Filter_Function };
enum Primitive_Type { Triangle = 0, Curve, Motion_Triangle, Point, Max_Primitive_Type };
hiprtGeometryBuildInput prepare_triangle_blas(BVHHIPRT *bvh, Mesh *mesh);
hiprtGeometryBuildInput prepare_curve_blas(BVHHIPRT *bvh, Hair *hair);
hiprtGeometryBuildInput prepare_point_blas(BVHHIPRT *bvh, PointCloud *pointcloud);
void build_blas(BVHHIPRT *bvh, Geometry *geom, hiprtBuildOptions options);
hiprtBuildFlags select_blas_build_flags(BVHHIPRT *bvh,
Geometry *geom,
const hiprtGeometryBuildInput &geom_input);
hiprtScene build_tlas(BVHHIPRT *bvh,
const vector<Object *> &objects,
hiprtBuildOptions options,
bool refit);
void free_bvh_memory_delayed();
hiprtContext hiprt_context;
hipModule_t hiprt_module_;
hiprtScene scene;
hiprtFuncTable functions_table;
thread_mutex hiprt_mutex;
size_t scratch_buffer_size;
device_vector<char> scratch_buffer;
/* This vector tracks the hiprt_geom members of BVHRT so that device memory
* can be managed/released in HIPRTDevice.
* Even if synchronization occurs before memory release, a GPU job may still
* launch between synchronization and release, potentially causing the GPU
* to access unmapped memory. */
vector<hiprtGeometry> stale_bvh;
/* Is this scene using motion blur? Note there might exist motion data even if
* motion blur is disabled, for render passes. */
bool use_motion_blur = false;
/* The following vectors are to transfer scene information available on the host to the GPU
* visibility, instance_transform_matrix, transform_headers, and hiprt_blas_ptr are passed to
* hiprt to build bvh the rest are directly used in traversal functions/intersection kernels and
* are defined on the GPU side as members of KernelParamsHIPRT struct the host memory is copied
* to GPU through const_copy_to() function. */
/* Originally, visibility was only passed to HIP RT but after a bug report it was noted it was
* required for custom primitives (i.e., motion triangles). This buffer, however, has visibility
* per object not per primitive so the same buffer as the one that is passed to HIP RT can be
* used. */
device_vector<uint32_t> prim_visibility;
/* instance_transform_matrix passes transform matrix of instances converted from Cycles Transform
* format to instanceFrames member of hiprtSceneBuildInput. */
device_vector<hiprtFrameMatrix> instance_transform_matrix;
/* Movement over a time interval for motion blur is captured through multiple transform matrices.
* In this case transform matrix of an instance cannot be directly retrieved by looking up
* instance_transform_matrix give the instance id. transform_headers maps the instance id to the
* appropriate index to retrieve instance transform matrix (frameIndex member of
* hiprtTransformHeader). transform_headers also has the information on how many transform
* matrices are associated with an instance (frameCount member of hiprtTransformHeader)
* transform_headers is passed to hiprt through instanceTransformHeaders member of
* hiprtSceneBuildInput. */
device_vector<hiprtTransformHeader> transform_headers;
/* Instance/object ids are not explicitly passed to hiprt.
* HIP RT assigns the ids based on the order blas pointers are passed to it (through
* instanceGeometries member of hiprtSceneBuildInput). If blas is absent for a particular
* geometry (e.g. a plane), HIP RT removes that entry and in scenes with objects with no blas,
* the instance id that hiprt returns for a hit point will not necessarily match the instance id
* of the application. user_instance_id provides a map for retrieving original instance id from
* what HIP RT returns as instance id. hiprt_blas_ptr is the list of all the valid blas pointers.
* blas_ptr has all the valid pointers and null pointers and blas for any geometry can be
* directly retrieved from this array (used in subsurface scattering). */
device_vector<int> user_instance_id;
device_vector<hiprtInstance> hiprt_blas_ptr;
device_vector<uint64_t> blas_ptr;
/* custom_prim_info stores custom information for custom primitives for all the primitives in a
* scene. Primitive id that HIP RT returns is local to the geometry that was hit.
* custom_prim_info_offset returns the offset required to add to the primitive id to retrieve
* primitive info from custom_prim_info. */
device_vector<int2> custom_prim_info;
device_vector<int2> custom_prim_info_offset;
/* prims_time stores primitive time for geometries with motion blur.
* prim_time_offset returns the offset to add to primitive id to retrieve primitive time. */
device_vector<float2> prims_time;
device_vector<int> prim_time_offset;
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_HIPRT
# include "device/hiprt/queue.h"
# include <hiprt/hiprt.h>
# include "device/hip/graphics_interop.h"
# include "device/hip/kernel.h"
# include "device/hiprt/device_impl.h"
# include "kernel/device/hiprt/globals.h"
CCL_NAMESPACE_BEGIN
HIPRTDeviceQueue::HIPRTDeviceQueue(HIPRTDevice *device)
: HIPDeviceQueue((HIPDevice *)device), hiprt_device_(device)
{
}
bool HIPRTDeviceQueue::enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args)
{
if (hiprt_device_->have_error()) {
return false;
}
if (!device_kernel_has_intersection(kernel)) {
return HIPDeviceQueue::enqueue(kernel, work_size, args);
}
debug_enqueue_begin(kernel, work_size);
const HIPContextScope scope(hiprt_device_);
const HIPDeviceKernel &hip_kernel = hiprt_device_->kernels.get(kernel);
if (!hiprt_device_->global_stack_buffer.stackData) {
uint32_t max_path = num_concurrent_states(0);
hiprtGlobalStackBufferInput stack_buffer_input{
hiprtStackTypeGlobal, hiprtStackEntryTypeInteger, HIPRT_THREAD_STACK_SIZE, max_path};
hiprtError rt_result = hiprtCreateGlobalStackBuffer(hiprt_device_->get_hiprt_context(),
stack_buffer_input,
hiprt_device_->global_stack_buffer);
if (rt_result != hiprtSuccess) {
LOG_ERROR << "Failed to create hiprt Global Stack Buffer";
return false;
}
}
DeviceKernelArguments args_copy = args;
args_copy.add(DeviceKernelArguments::HIPRT_GLOBAL_STACK,
(void *)(&hiprt_device_->global_stack_buffer),
sizeof(hiprtGlobalStackBuffer));
/* Compute kernel launch parameters. */
const int num_threads_per_block = HIPRT_THREAD_GROUP_SIZE;
const int num_blocks = divide_up(work_size, num_threads_per_block);
int shared_mem_bytes = 0;
assert_success(hipModuleLaunchKernel(hip_kernel.function,
num_blocks,
1,
1,
num_threads_per_block,
1,
1,
shared_mem_bytes,
hip_stream_,
const_cast<void **>(args_copy.values),
nullptr),
"enqueue");
debug_enqueue_end();
return !(hiprt_device_->have_error());
}
CCL_NAMESPACE_END
#endif /* WITH_HIPRT */

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_HIPRT
# include "device/memory.h"
# include "device/queue.h"
# include "device/hip/queue.h"
CCL_NAMESPACE_BEGIN
class HIPRTDevice;
class HIPRTDeviceQueue : public HIPDeviceQueue {
public:
HIPRTDeviceQueue(HIPRTDevice *device);
~HIPRTDeviceQueue() override = default;
bool enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args) override;
protected:
HIPRTDevice *hiprt_device_;
};
CCL_NAMESPACE_END
#endif /* WITH_HIPRT */

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/kernel.h"
#ifndef __KERNEL_ONEAPI__
# include "util/log.h"
#endif
CCL_NAMESPACE_BEGIN
bool device_kernel_has_shading(DeviceKernel kernel)
{
return (kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME_RAY_MARCHING ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT ||
kernel == DEVICE_KERNEL_SHADER_EVAL_DISPLACE ||
kernel == DEVICE_KERNEL_SHADER_EVAL_BACKGROUND ||
kernel == DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY ||
kernel == DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY);
}
bool device_kernel_has_intersection(DeviceKernel kernel)
{
return (kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST ||
kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW ||
kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE ||
kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK ||
kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT ||
kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE);
}
bool device_kernel_has_gpu_function(DeviceKernel kernel)
{
return !(kernel == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL ||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING);
}
const char *device_kernel_as_string(DeviceKernel kernel)
{
switch (kernel) {
/* Integrator. */
case DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA:
return "integrator_init_from_camera";
case DEVICE_KERNEL_INTEGRATOR_INIT_FROM_BAKE:
return "integrator_init_from_bake";
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST:
return "integrator_intersect_closest";
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW:
return "integrator_intersect_shadow";
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE:
return "integrator_intersect_subsurface";
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK:
return "integrator_intersect_volume_stack";
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT:
return "integrator_intersect_dedicated_light";
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE:
return "integrator_intersect_mnee";
case DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING:
return "integrator_shadow_path_mnee_pending";
case DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND:
return "integrator_shade_background";
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE:
return "integrator_shade_light_nee";
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD:
return "integrator_shade_light_forward";
case DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW:
return "integrator_shade_shadow";
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE:
return "integrator_shade_surface";
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE:
return "integrator_shade_surface_raytrace";
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME:
return "integrator_shade_volume";
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME_RAY_MARCHING:
return "integrator_shade_volume_ray_marching";
case DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT:
return "integrator_shade_dedicated_light";
case DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL:
return "integrator_megakernel";
case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY:
return "integrator_queued_paths_array";
case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY:
return "integrator_queued_shadow_paths_array";
case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY:
return "integrator_active_paths_array";
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY:
return "integrator_terminated_paths_array";
case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY:
return "integrator_sorted_paths_array";
case DEVICE_KERNEL_INTEGRATOR_SORT_BUCKET_PASS:
return "integrator_sort_bucket_pass";
case DEVICE_KERNEL_INTEGRATOR_SORT_WRITE_PASS:
return "integrator_sort_write_pass";
case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY:
return "integrator_compact_paths_array";
case DEVICE_KERNEL_INTEGRATOR_COMPACT_STATES:
return "integrator_compact_states";
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_SHADOW_PATHS_ARRAY:
return "integrator_terminated_shadow_paths_array";
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_PATHS_ARRAY:
return "integrator_compact_shadow_paths_array";
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES:
return "integrator_compact_shadow_states";
case DEVICE_KERNEL_INTEGRATOR_RESET:
return "integrator_reset";
case DEVICE_KERNEL_INTEGRATOR_SHADOW_CATCHER_COUNT_POSSIBLE_SPLITS:
return "integrator_shadow_catcher_count_possible_splits";
/* Shader evaluation. */
case DEVICE_KERNEL_SHADER_EVAL_DISPLACE:
return "shader_eval_displace";
case DEVICE_KERNEL_SHADER_EVAL_BACKGROUND:
return "shader_eval_background";
case DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY:
return "shader_eval_curve_shadow_transparency";
case DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY:
return "shader_eval_volume_density";
/* Film. */
#define FILM_CONVERT_KERNEL_AS_STRING(variant, variant_lowercase) \
case DEVICE_KERNEL_FILM_CONVERT_##variant: \
return "film_convert_" #variant_lowercase; \
case DEVICE_KERNEL_FILM_CONVERT_##variant##_HALF_RGBA: \
return "film_convert_" #variant_lowercase "_half_rgba";
FILM_CONVERT_KERNEL_AS_STRING(DEPTH, depth)
FILM_CONVERT_KERNEL_AS_STRING(MIST, mist)
FILM_CONVERT_KERNEL_AS_STRING(VOLUME_MAJORANT, volume_majorant)
FILM_CONVERT_KERNEL_AS_STRING(SAMPLE_COUNT, sample_count)
FILM_CONVERT_KERNEL_AS_STRING(FLOAT, float)
FILM_CONVERT_KERNEL_AS_STRING(LIGHT_PATH, light_path)
FILM_CONVERT_KERNEL_AS_STRING(RGBE, rgbe)
FILM_CONVERT_KERNEL_AS_STRING(FLOAT3, float3)
FILM_CONVERT_KERNEL_AS_STRING(MOTION, motion)
FILM_CONVERT_KERNEL_AS_STRING(CRYPTOMATTE, cryptomatte)
FILM_CONVERT_KERNEL_AS_STRING(SHADOW_CATCHER, shadow_catcher)
FILM_CONVERT_KERNEL_AS_STRING(SHADOW_CATCHER_MATTE_WITH_SHADOW,
shadow_catcher_matte_with_shadow)
FILM_CONVERT_KERNEL_AS_STRING(COMBINED, combined)
FILM_CONVERT_KERNEL_AS_STRING(FLOAT4, float4)
#undef FILM_CONVERT_KERNEL_AS_STRING
/* Adaptive sampling. */
case DEVICE_KERNEL_ADAPTIVE_SAMPLING_CONVERGENCE_CHECK:
return "adaptive_sampling_convergence_check";
case DEVICE_KERNEL_ADAPTIVE_SAMPLING_CONVERGENCE_FILTER_X:
return "adaptive_sampling_filter_x";
case DEVICE_KERNEL_ADAPTIVE_SAMPLING_CONVERGENCE_FILTER_Y:
return "adaptive_sampling_filter_y";
/* Denoising. */
case DEVICE_KERNEL_FILTER_GUIDING_PREPROCESS:
return "filter_guiding_preprocess";
case DEVICE_KERNEL_FILTER_GUIDING_SET_FAKE_ALBEDO:
return "filter_guiding_set_fake_albedo";
case DEVICE_KERNEL_FILTER_COLOR_PREPROCESS:
return "filter_color_preprocess";
case DEVICE_KERNEL_FILTER_COLOR_POSTPROCESS:
return "filter_color_postprocess";
case DEVICE_KERNEL_FILTER_COLOR_FLIP_Y:
return "filter_color_flip_y";
/* Volume Scattering Probability Guiding. */
case DEVICE_KERNEL_VOLUME_GUIDING_FILTER_X:
return "volume_guiding_filter_x";
case DEVICE_KERNEL_VOLUME_GUIDING_FILTER_Y:
return "volume_guiding_filter_y";
/* Cryptomatte. */
case DEVICE_KERNEL_CRYPTOMATTE_POSTPROCESS:
return "cryptomatte_postprocess";
/* Generic */
case DEVICE_KERNEL_PREFIX_SUM:
return "prefix_sum";
case DEVICE_KERNEL_NUM:
break;
};
#ifndef __KERNEL_ONEAPI__
LOG_FATAL << "Unhandled kernel " << static_cast<int>(kernel) << ", should never happen.";
#endif
return "UNKNOWN";
}
#ifndef __KERNEL_ONEAPI__
std::ostream &operator<<(std::ostream &os, DeviceKernel kernel)
{
os << device_kernel_as_string(kernel);
return os;
}
string device_kernel_mask_as_string(DeviceKernelMask mask)
{
string str;
for (uint64_t i = 0; i < mask.size(); i++) {
if (mask.test(i)) {
if (!str.empty()) {
str += " ";
}
str += device_kernel_as_string((DeviceKernel)i);
}
}
return str;
}
bool DeviceKernelMask::operator<(const DeviceKernelMask &other) const
{
for (size_t i = 0; i < size(); i++) {
if (test(i) ^ other.test(i)) {
return other.test(i);
}
}
return false;
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifndef __KERNEL_ONEAPI__
# include "kernel/types.h"
# include "util/string.h"
# include <bitset>
# include <iosfwd>
#endif
CCL_NAMESPACE_BEGIN
/* DeviceKernel */
bool device_kernel_has_shading(DeviceKernel kernel);
bool device_kernel_has_intersection(DeviceKernel kernel);
bool device_kernel_has_gpu_function(DeviceKernel kernel);
const char *device_kernel_as_string(DeviceKernel kernel);
#ifndef __KERNEL_ONEAPI__
std::ostream &operator<<(std::ostream &os, DeviceKernel kernel);
/* DeviceKernelMask */
struct DeviceKernelMask : public std::bitset<DEVICE_KERNEL_NUM> {
bool operator<(const DeviceKernelMask &other) const;
};
string device_kernel_mask_as_string(DeviceKernelMask mask);
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,314 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/memory.h"
#include "device/device.h"
CCL_NAMESPACE_BEGIN
static const char *name_from_type(ImageDataType type)
{
switch (type) {
case IMAGE_DATA_TYPE_FLOAT4:
return "float4";
case IMAGE_DATA_TYPE_BYTE4:
return "byte4";
case IMAGE_DATA_TYPE_HALF4:
return "half4";
case IMAGE_DATA_TYPE_FLOAT:
return "float";
case IMAGE_DATA_TYPE_BYTE:
return "byte";
case IMAGE_DATA_TYPE_HALF:
return "half";
case IMAGE_DATA_TYPE_USHORT4:
return "ushort4";
case IMAGE_DATA_TYPE_USHORT:
return "ushort";
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
return "nanovdb_float";
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
return "nanovdb_float3";
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
return "nanovdb_float4";
case IMAGE_DATA_TYPE_NANOVDB_FPN:
return "nanovdb_fpn";
case IMAGE_DATA_TYPE_NANOVDB_FP16:
return "nanovdb_fp16";
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
return "nanovdb_empty";
case IMAGE_DATA_NUM_TYPES:
assert(!"System enumerator type, should never be used");
return "";
}
assert(!"Unhandled image data type");
return "";
}
/* Device Memory */
device_memory::device_memory(Device *device, const char *name, MemoryType type)
: data_type(device_type_traits<uchar>::data_type),
data_elements(device_type_traits<uchar>::num_elements),
data_size(0),
device_size(0),
data_width(0),
data_height(0),
type(type),
device(device),
device_pointer(0),
host_pointer(nullptr),
shared_pointer(nullptr),
shared_counter(0),
name_(name),
original_device_ptr(0),
original_device_size(0),
original_device(nullptr),
need_realloc_(false),
modified(false)
{
}
device_memory::~device_memory()
{
assert(shared_pointer == nullptr);
assert(shared_counter == 0);
}
const char *device_memory::global_name() const
{
return name_;
}
string device_memory::log_name() const
{
return (name_) ? name_ : "unknown";
}
void *device_memory::host_alloc(const size_t size)
{
if (!size) {
return nullptr;
}
void *ptr = device->host_alloc(type, size);
if (ptr == nullptr) {
throw std::bad_alloc();
}
return ptr;
}
void device_memory::host_and_device_free()
{
if (host_pointer) {
if (host_pointer != shared_pointer) {
device->host_free(type, host_pointer, memory_size());
}
host_pointer = nullptr;
}
if (device_pointer) {
device->mem_free(*this);
}
data_size = 0;
data_width = 0;
data_height = 0;
}
void device_memory::host_only_free()
{
/* Free only the host buffer, leaving device allocation intact. */
if (host_pointer && host_pointer != shared_pointer) {
device->host_free(type, host_pointer, memory_size());
host_pointer = nullptr;
}
}
void device_memory::device_alloc()
{
assert(!device_pointer && type != MEM_IMAGE_TEXTURE && type != MEM_GLOBAL);
device->mem_alloc(*this);
}
void device_memory::device_copy_to()
{
if (host_pointer) {
device->mem_copy_to(*this);
}
}
void device_memory::device_move_to_host()
{
if (host_pointer) {
device->mem_move_to_host(*this);
}
}
void device_memory::device_copy_from(const size_t y, const size_t w, size_t h, const size_t elem)
{
assert(type != MEM_IMAGE_TEXTURE && type != MEM_READ_ONLY);
device->mem_copy_from(*this, y, w, h, elem);
}
void device_memory::device_zero()
{
if (data_size) {
device->mem_zero(*this);
}
}
bool device_memory::device_is_cpu()
{
return (device->info.type == DEVICE_CPU);
}
void device_memory::swap_device(Device *new_device,
const size_t new_device_size,
device_ptr new_device_ptr)
{
original_device = device;
original_device_size = device_size;
original_device_ptr = device_pointer;
device = new_device;
device_size = new_device_size;
device_pointer = new_device_ptr;
}
void device_memory::restore_device()
{
device = original_device;
device_size = original_device_size;
device_pointer = original_device_ptr;
}
bool device_memory::is_resident(Device *sub_device) const
{
return device->is_resident(device_pointer, sub_device);
}
bool device_memory::is_shared(Device *sub_device) const
{
return device->is_shared(shared_pointer, device_pointer, sub_device);
}
/* Device Sub `ptr`. */
device_sub_ptr::device_sub_ptr(device_memory &mem, const size_t offset, const size_t size)
: device(mem.device)
{
ptr = device->mem_alloc_sub_ptr(mem, offset, size);
}
device_sub_ptr::~device_sub_ptr()
{
device->mem_free_sub_ptr(ptr);
}
/* Device Texture */
device_image::device_image(Device *device,
const char *name,
const uint image_info_id,
ImageDataType image_data_type,
InterpolationType interpolation,
ExtensionType extension)
: device_memory(device, name, MEM_IMAGE_TEXTURE), image_info_id(image_info_id)
{
switch (image_data_type) {
case IMAGE_DATA_TYPE_FLOAT4:
data_type = TYPE_FLOAT;
data_elements = 4;
break;
case IMAGE_DATA_TYPE_FLOAT:
data_type = TYPE_FLOAT;
data_elements = 1;
break;
case IMAGE_DATA_TYPE_BYTE4:
data_type = TYPE_UCHAR;
data_elements = 4;
break;
case IMAGE_DATA_TYPE_BYTE:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT3:
case IMAGE_DATA_TYPE_NANOVDB_FLOAT4:
case IMAGE_DATA_TYPE_NANOVDB_FPN:
case IMAGE_DATA_TYPE_NANOVDB_FP16:
case IMAGE_DATA_TYPE_NANOVDB_EMPTY:
data_type = TYPE_UCHAR;
data_elements = 1;
break;
case IMAGE_DATA_TYPE_HALF4:
data_type = TYPE_HALF;
data_elements = 4;
break;
case IMAGE_DATA_TYPE_HALF:
data_type = TYPE_HALF;
data_elements = 1;
break;
case IMAGE_DATA_TYPE_USHORT4:
data_type = TYPE_UINT16;
data_elements = 4;
break;
case IMAGE_DATA_TYPE_USHORT:
data_type = TYPE_UINT16;
data_elements = 1;
break;
case IMAGE_DATA_NUM_TYPES:
assert(0);
return;
}
info.data_type = image_data_type;
info.interpolation = interpolation;
info.extension = extension;
}
device_image::~device_image()
{
host_and_device_free();
}
string device_image::log_name() const
{
const char *name = (name_) ? name_ : "unknown";
if (type == MEM_IMAGE_TEXTURE) {
return string_printf(
"%s_%s_%03u", name, name_from_type(ImageDataType(info.data_type)), image_info_id);
}
return name;
}
/* Host memory allocation. */
void *device_image::alloc(const size_t width, const size_t height)
{
const size_t new_size = size(width, height);
if (new_size != data_size) {
host_and_device_free();
host_pointer = host_alloc(data_elements * datatype_size(data_type) * new_size);
assert(device_pointer == 0);
}
data_size = new_size;
data_width = width;
data_height = height;
info.width = width;
info.height = height;
info.inv_width = (width > 0) ? 1.0f / (float)width : 0.0f;
info.inv_height = (height > 0) ? 1.0f / (float)height : 0.0f;
return host_pointer;
}
void device_image::copy_to_device()
{
device_copy_to();
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,663 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* Device Memory
*
* Data types for allocating, copying and freeing device memory. */
#include "util/array.h"
#include "util/half.h"
#include "util/string.h"
#include "util/types.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
class Device;
class GPUDevice;
class CUDADevice;
class OptiXDevice;
class HIPDevice;
class HIPRTDevice;
class MetalDevice;
class OneapiDevice;
enum MemoryType {
MEM_READ_ONLY,
MEM_READ_WRITE,
MEM_DEVICE_ONLY,
MEM_GLOBAL,
MEM_IMAGE_TEXTURE,
};
/* Supported Data Types */
enum DataType {
TYPE_UNKNOWN,
TYPE_UCHAR,
TYPE_UINT16,
TYPE_UINT,
TYPE_INT,
TYPE_FLOAT,
TYPE_HALF,
TYPE_UINT64,
};
static constexpr size_t datatype_size(DataType datatype)
{
switch (datatype) {
case TYPE_UNKNOWN:
return 1;
case TYPE_UCHAR:
return sizeof(uchar);
case TYPE_FLOAT:
return sizeof(float);
case TYPE_UINT:
return sizeof(uint);
case TYPE_UINT16:
return sizeof(uint16_t);
case TYPE_INT:
return sizeof(int);
case TYPE_HALF:
return sizeof(half);
case TYPE_UINT64:
return sizeof(uint64_t);
default:
return 0;
}
}
/* Traits for data types */
template<typename T> struct device_type_traits {
static const DataType data_type = TYPE_UNKNOWN;
static const size_t num_elements = sizeof(T);
};
template<> struct device_type_traits<uchar> {
static const DataType data_type = TYPE_UCHAR;
static const size_t num_elements = 1;
static_assert(sizeof(uchar) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uchar2> {
static const DataType data_type = TYPE_UCHAR;
static const size_t num_elements = 2;
static_assert(sizeof(uchar2) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uchar3> {
static const DataType data_type = TYPE_UCHAR;
static const size_t num_elements = 3;
static_assert(sizeof(uchar3) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uchar4> {
static const DataType data_type = TYPE_UCHAR;
static const size_t num_elements = 4;
static_assert(sizeof(uchar4) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uint> {
static const DataType data_type = TYPE_UINT;
static const size_t num_elements = 1;
static_assert(sizeof(uint) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uint2> {
static const DataType data_type = TYPE_UINT;
static const size_t num_elements = 2;
static_assert(sizeof(uint2) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uint3> {
/* uint3 has different size depending on the device, can't use it for interchanging
* memory between CPU and GPU.
*
* Leave body empty to trigger a compile error if used. */
};
template<> struct device_type_traits<uint4> {
static const DataType data_type = TYPE_UINT;
static const size_t num_elements = 4;
static_assert(sizeof(uint4) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<int> {
static const DataType data_type = TYPE_INT;
static const size_t num_elements = 1;
static_assert(sizeof(int) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<int2> {
static const DataType data_type = TYPE_INT;
static const size_t num_elements = 2;
static_assert(sizeof(int2) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<int3> {
/* int3 has different size depending on the device, can't use it for interchanging
* memory between CPU and GPU.
*
* Leave body empty to trigger a compile error if used. */
};
template<> struct device_type_traits<int4> {
static const DataType data_type = TYPE_INT;
static const size_t num_elements = 4;
static_assert(sizeof(int4) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<float> {
static const DataType data_type = TYPE_FLOAT;
static const size_t num_elements = 1;
static_assert(sizeof(float) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<float2> {
static const DataType data_type = TYPE_FLOAT;
static const size_t num_elements = 2;
static_assert(sizeof(float2) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<float3> {
/* float3 has different size depending on the device, can't use it for interchanging
* memory between CPU and GPU.
*
* Leave body empty to trigger a compile error if used. */
};
template<> struct device_type_traits<packed_float3> {
static const DataType data_type = TYPE_FLOAT;
static const size_t num_elements = 3;
static_assert(sizeof(packed_float3) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<float4> {
static const DataType data_type = TYPE_FLOAT;
static const size_t num_elements = 4;
static_assert(sizeof(float4) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<half> {
static const DataType data_type = TYPE_HALF;
static const size_t num_elements = 1;
static_assert(sizeof(half) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<ushort4> {
static const DataType data_type = TYPE_UINT16;
static const size_t num_elements = 4;
static_assert(sizeof(ushort4) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uint16_t> {
static const DataType data_type = TYPE_UINT16;
static const size_t num_elements = 1;
static_assert(sizeof(uint16_t) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<half4> {
static const DataType data_type = TYPE_HALF;
static const size_t num_elements = 4;
static_assert(sizeof(half4) == num_elements * datatype_size(data_type));
};
template<> struct device_type_traits<uint64_t> {
static const DataType data_type = TYPE_UINT64;
static const size_t num_elements = 1;
static_assert(sizeof(uint64_t) == num_elements * datatype_size(data_type));
};
/* Device Memory
*
* Base class for all device memory. This should not be allocated directly,
* instead the appropriate subclass can be used. */
class device_memory {
public:
size_t memory_size()
{
return data_size * data_elements * datatype_size(data_type);
}
size_t memory_elements_size(const int elements)
{
return elements * data_elements * datatype_size(data_type);
}
const char *global_name() const;
virtual string log_name() const;
/* Data information. */
DataType data_type;
int data_elements;
size_t data_size;
size_t device_size;
size_t data_width;
size_t data_height;
MemoryType type;
/* Pointers. */
Device *device;
device_ptr device_pointer;
void *host_pointer;
void *shared_pointer;
/* reference counter for shared_pointer */
int shared_counter;
bool move_to_host = false;
virtual ~device_memory();
void swap_device(Device *new_device, const size_t new_device_size, device_ptr new_device_ptr);
void restore_device();
bool is_resident(Device *sub_device) const;
bool is_shared(Device *sub_device) const;
/* No copying and allowed.
*
* This is because device implementation might need to register device memory in an allocation
* map of some sort and use pointer as a key to identify blocks. Moving data from one place to
* another bypassing device allocation routines will make those maps hard to maintain. */
device_memory(const device_memory &) = delete;
device_memory(device_memory &&other) noexcept = delete;
device_memory &operator=(const device_memory &) = delete;
device_memory &operator=(device_memory &&) = delete;
protected:
friend class Device;
friend class GPUDevice;
friend class CUDADevice;
friend class OptiXDevice;
friend class HIPDevice;
friend class HIPRTDevice;
friend class MetalDevice;
friend class OneapiDevice;
/* Only create through subclasses. */
device_memory(Device *device, const char *name, MemoryType type);
/* Host allocation on the device. All host_pointer memory should be
* allocated with these functions, for devices that support using
* the same pointer for host and device. */
void *host_alloc(const size_t size);
/* Device memory allocation and copying. */
void device_alloc();
void device_copy_to();
void device_move_to_host();
void device_copy_from(const size_t y, const size_t w, size_t h, const size_t elem);
void device_copy_merged_bitmap_from(const size_t y, const size_t w, size_t h);
void device_zero();
/* Memory can only be freed on host and device together. */
void host_and_device_free();
/* Free only the host buffer, leaving any device allocation intact. */
void host_only_free();
bool device_is_cpu();
const char *name_;
device_ptr original_device_ptr;
size_t original_device_size;
Device *original_device;
bool need_realloc_;
bool modified;
};
/* Device Only Memory
*
* Working memory only needed by the device, with no corresponding allocation
* on the host. Only used internally in the device implementations. */
template<typename T> class device_only_memory : public device_memory {
public:
device_only_memory(Device *device, const char *name, bool allow_host_memory_fallback = false)
: device_memory(device, name, allow_host_memory_fallback ? MEM_READ_WRITE : MEM_DEVICE_ONLY)
{
data_type = device_type_traits<T>::data_type;
data_elements = max(device_type_traits<T>::num_elements, size_t(1));
}
device_only_memory(device_only_memory &&other) noexcept : device_memory(std::move(other)) {}
~device_only_memory() override
{
free();
}
void alloc_to_device(const size_t num, bool shrink_to_fit = true)
{
size_t new_size = num;
bool reallocate;
if (shrink_to_fit) {
reallocate = (data_size != new_size);
}
else {
reallocate = (data_size < new_size);
}
if (reallocate) {
host_and_device_free();
data_size = new_size;
device_alloc();
}
}
void free()
{
host_and_device_free();
data_size = 0;
}
void zero_to_device()
{
device_zero();
}
};
/* Device Vector
*
* Data vector to exchange data between host and device. Memory will be
* allocated on the host first with alloc() and resize, and then filled
* in and copied to the device with copy_to_device(). Or alternatively
* allocated and set to zero on the device with zero_to_device().
*
* When using memory type MEM_GLOBAL, a pointer to this memory will be
* automatically attached to kernel globals, using the provided name
* matching an entry in kernel/data_arrays.h. */
template<typename T> class device_vector : public device_memory {
public:
device_vector(Device *device, const char *name, MemoryType type)
: device_memory(device, name, type)
{
data_type = device_type_traits<T>::data_type;
data_elements = device_type_traits<T>::num_elements;
modified = true;
need_realloc_ = true;
assert(data_elements > 0);
}
~device_vector() override
{
free();
}
/* Host memory allocation. */
T *alloc(const size_t width, const size_t height = 0)
{
size_t new_size = size(width, height);
if (new_size != data_size) {
host_and_device_free();
host_pointer = host_alloc(sizeof(T) * new_size);
modified = true;
assert(device_pointer == 0);
}
data_size = new_size;
data_width = width;
data_height = height;
return data();
}
/* Host memory resize. Only use this if the original data needs to be
* preserved or memory needs to be initialized, it is faster to call
* alloc() if it can be discarded. */
T *resize(const size_t width, const size_t height = 0)
{
size_t new_size = size(width, height);
if (new_size != data_size) {
void *new_ptr = host_alloc(sizeof(T) * new_size);
if (new_ptr) {
size_t min_size = (new_size < data_size) ? new_size : data_size;
for (size_t i = 0; i < min_size; i++) {
((T *)new_ptr)[i] = ((T *)host_pointer)[i];
}
for (size_t i = data_size; i < new_size; i++) {
((T *)new_ptr)[i] = T();
}
}
host_and_device_free();
host_pointer = new_ptr;
modified = true;
assert(device_pointer == 0);
}
data_size = new_size;
data_width = width;
data_height = height;
return data();
}
/* Host-only resize: grows the host buffer while leaving any existing device allocation
* untouched. Use this when a kernel may be reading from device_pointer and freeing it
* would be unsafe. The device buffer will be reallocated on the next copy_to_device()
* call once the device is idle. Only valid when not shrinking. */
T *host_only_resize(const size_t new_count)
{
assert(new_count >= data_size);
if (new_count != data_size) {
void *new_ptr = host_alloc(sizeof(T) * new_count);
if (new_ptr) {
for (size_t i = 0; i < data_size; i++) {
((T *)new_ptr)[i] = ((T *)host_pointer)[i];
}
for (size_t i = data_size; i < new_count; i++) {
((T *)new_ptr)[i] = T();
}
}
host_only_free();
host_pointer = new_ptr;
modified = true;
}
data_size = new_count;
data_width = new_count;
return data();
}
/* Take over data from an existing array. */
void steal_data(array<T> &from)
{
host_and_device_free();
data_size = from.size();
data_width = 0;
data_height = 0;
host_pointer = from.steal_pointer();
modified = true;
assert(device_pointer == 0);
}
/* Free device and host memory. */
void free()
{
host_and_device_free();
data_size = 0;
data_width = 0;
data_height = 0;
host_pointer = 0;
modified = true;
need_realloc_ = true;
assert(device_pointer == 0);
}
void free_if_need_realloc(bool force_free)
{
if (need_realloc_ || force_free) {
free();
}
}
bool is_modified() const
{
return modified;
}
bool need_realloc()
{
return need_realloc_;
}
void tag_modified()
{
modified = true;
}
void tag_realloc()
{
need_realloc_ = true;
tag_modified();
}
size_t size() const
{
return data_size;
}
T *data()
{
return (T *)host_pointer;
}
const T *data() const
{
return (T *)host_pointer;
}
T &operator[](size_t i)
{
assert(i < data_size);
return data()[i];
}
void copy_to_device()
{
if (data_size != 0) {
device_copy_to();
}
}
void copy_to_device_if_modified()
{
if (!modified) {
return;
}
copy_to_device();
}
void clear_modified()
{
modified = false;
need_realloc_ = false;
}
void copy_from_device()
{
device_copy_from(0, data_width, (data_height == 0) ? 1 : data_height, sizeof(T));
}
void copy_from_device(const size_t y, const size_t w, size_t h)
{
device_copy_from(y, w, h, sizeof(T));
}
/* Copy from all devices and OR into host memory. */
void copy_merged_bitmap_from_device()
{
device_copy_merged_bitmap_from(0, data_size, 1);
}
void zero_to_device()
{
device_zero();
}
protected:
size_t size(const size_t width, const size_t height)
{
return width * ((height == 0) ? 1 : height);
}
};
/* Device Sub Memory
*
* Pointer into existing memory. It is not allocated separately, but created
* from an already allocated base memory. It is freed automatically when it
* goes out of scope, which should happen before base memory is freed.
*
* NOTE: some devices require offset and size of the sub_ptr to be properly
* aligned to device->mem_address_alingment(). */
class device_sub_ptr {
public:
device_sub_ptr(device_memory &mem, const size_t offset, const size_t size);
~device_sub_ptr();
device_ptr operator*() const
{
return ptr;
}
protected:
/* No copying. */
device_sub_ptr &operator=(const device_sub_ptr &);
Device *device;
device_ptr ptr;
};
/* Device Image
*
* 2D or 3D image texture memory. */
class device_image : public device_memory {
public:
device_image(Device *device,
const char *name,
const uint image_info_id,
ImageDataType image_data_type,
InterpolationType interpolation,
ExtensionType extension);
~device_image() override;
string log_name() const override;
void *alloc(const size_t width, const size_t height);
template<typename T = void> T *data()
{
return reinterpret_cast<T *>(host_pointer);
}
void copy_to_device();
uint image_info_id = 0;
KernelImageInfo info;
protected:
size_t size(const size_t width, const size_t height)
{
return width * ((height == 0) ? 1 : height);
}
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_METAL
# include "bvh/bvh.h"
# include "bvh/params.h"
# include "device/memory.h"
# include <Metal/Metal.h>
CCL_NAMESPACE_BEGIN
class BVHMetal : public BVH {
public:
API_AVAILABLE(macos(11.0))
id<MTLAccelerationStructure> accel_struct = nil;
API_AVAILABLE(macos(11.0))
id<MTLAccelerationStructure> null_BLAS = nil;
API_AVAILABLE(macos(11.0))
vector<id<MTLAccelerationStructure>> blas_array;
API_AVAILABLE(macos(11.0))
vector<id<MTLAccelerationStructure>> unique_blas_array;
Device *device = nullptr;
bool motion_blur = false;
/* Per-component Motion Interpolation in macOS 15. */
bool use_pcmi = false;
bool extended_limits = false;
bool build(Progress &progress, id<MTLDevice> device, id<MTLCommandQueue> queue, bool refit);
BVHMetal(const BVHParams &params,
const vector<Geometry *> &geometry,
const vector<Object *> &objects,
Device *device);
~BVHMetal() override;
bool build_BLAS(Progress &progress, id<MTLDevice> device, id<MTLCommandQueue> queue, bool refit);
bool build_BLAS_mesh(Progress &progress,
id<MTLDevice> device,
id<MTLCommandQueue> queue,
Geometry *const geom,
bool refit);
bool build_BLAS_hair(Progress &progress,
id<MTLDevice> device,
id<MTLCommandQueue> queue,
Geometry *const geom,
bool refit);
bool build_BLAS_pointcloud(Progress &progress,
id<MTLDevice> device,
id<MTLCommandQueue> queue,
Geometry *const geom,
bool refit);
bool build_TLAS(Progress &progress, id<MTLDevice> device, id<MTLCommandQueue> queue, bool refit);
API_AVAILABLE(macos(11.0))
void set_accel_struct(id<MTLAccelerationStructure> new_accel_struct);
};
CCL_NAMESPACE_END
#endif /* WITH_METAL */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/string.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
bool device_metal_init();
void device_metal_exit();
unique_ptr<Device> device_metal_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
void device_metal_info(vector<DeviceInfo> &devices);
string device_metal_capabilities();
CCL_NAMESPACE_END

View File

@@ -0,0 +1,155 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/device.h"
#ifdef WITH_METAL
# include "device/metal/device.h"
# include "device/metal/device_impl.h"
# include "integrator/denoiser_oidn_gpu.h"
#endif
#include "util/debug.h"
#include "util/set.h"
#include "util/system.h"
CCL_NAMESPACE_BEGIN
#ifdef WITH_METAL
unique_ptr<Device> device_metal_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
return make_unique<MetalDevice>(info, stats, profiler, headless);
}
bool device_metal_init()
{
return true;
}
void device_metal_exit()
{
MetalDeviceKernels::static_deinitialize();
}
void device_metal_info(vector<DeviceInfo> &devices)
{
auto usable_devices = MetalInfo::get_usable_devices();
/* Devices are numbered consecutively across platforms. */
set<string> unique_ids;
int device_index = 0;
for (id<MTLDevice> &device : usable_devices) {
/* Compute unique ID for persistent user preferences. */
string device_name = MetalInfo::get_device_name(device);
string id = string("METAL_") + device_name;
/* Hardware ID might not be unique, add device number in that case. */
if (unique_ids.contains(id)) {
id += string_printf("_ID_%d", device_index);
}
unique_ids.insert(id);
/* Create DeviceInfo. */
DeviceInfo info;
info.type = DEVICE_METAL;
info.description = string_remove_trademark(string(device_name));
info.num = device_index;
/* We don't know if it's used for display, but assume it is. */
info.display_device = true;
info.denoisers = DENOISER_NONE;
info.id = id;
# if defined(WITH_OPENIMAGEDENOISE)
# if OIDN_VERSION >= 20300
if (oidnIsMetalDeviceSupported(device)) {
# else
if (OIDNDenoiserGPU::is_device_supported(info)) {
# endif
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
}
# endif
info.has_nanovdb = true;
/* MNEE caused "Compute function exceeds available temporary registers" in macOS < 13 due to a
* bug in spill buffer allocation sizing. */
info.has_mnee_ = false;
if (@available(macos 13.0, *)) {
info.has_mnee_ = true;
}
info.use_hardware_raytracing = false;
/* MetalRT now uses features exposed in Xcode versions corresponding to macOS 14+, so don't
* expose it in builds from older Xcode versions. */
# if defined(MAC_OS_VERSION_14_0)
if (@available(macos 14.0, *)) {
info.use_hardware_raytracing = device.supportsRaytracing;
/* Use hardware raytracing for faster rendering on architectures that support it. */
info.use_metalrt_by_default = device.supportsRaytracing &&
(MetalInfo::get_apple_gpu_architecture(device) >= APPLE_M3);
}
# endif
devices.push_back(info);
device_index++;
LOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE) {
LOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}
}
}
string device_metal_capabilities()
{
string result;
auto allDevices = MTLCopyAllDevices();
uint32_t num_devices = (uint32_t)allDevices.count;
if (num_devices == 0) {
return "No Metal devices found\n";
}
result += string_printf("Number of devices: %u\n", num_devices);
for (id<MTLDevice> device in allDevices) {
string device_name = MetalInfo::get_device_name(device);
result += string_printf("\t\tDevice: %s\n", device_name.c_str());
}
return result;
}
#else
unique_ptr<Device> device_metal_create(const DeviceInfo & /*info*/,
Stats & /*stats*/,
Profiler & /*profiler*/)
{
return nullptr;
}
bool device_metal_init()
{
return false;
}
void device_metal_info(vector<DeviceInfo> & /*devices*/) {}
string device_metal_capabilities()
{
return "";
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,217 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_METAL
# include "bvh/bvh.h"
# include "device/device.h"
# include "device/metal/bvh.h"
# include "device/metal/device.h"
# include "device/metal/kernel.h"
# include "device/metal/queue.h"
# include "device/metal/util.h"
# include <Metal/Metal.h>
CCL_NAMESPACE_BEGIN
class DeviceQueue;
class MetalDevice : public Device {
public:
id<MTLDevice> mtlDevice = nil;
id<MTLLibrary> mtlLibrary[PSO_NUM] = {nil};
id<MTLCommandQueue> mtlComputeCommandQueue = nil;
id<MTLCommandQueue> mtlGeneralCommandQueue = nil;
id<MTLCounterSampleBuffer> mtlCounterSampleBuffer = nil;
string source[PSO_NUM];
string kernels_md5[PSO_NUM];
string global_defines_md5[PSO_NUM];
bool capture_enabled = false;
/* Argument buffer for static data. */
id<MTLBuffer> launch_params_buffer = nil;
KernelParamsMetal *launch_params = nullptr;
/* MetalRT members ---------------------------------- */
bool use_metalrt = false;
bool use_metalrt_extended_limits = false;
bool motion_blur = false;
bool use_pcmi = false;
id<MTLBuffer> blas_buffer = nil;
API_AVAILABLE(macos(11.0))
vector<id<MTLAccelerationStructure>> unique_blas_array;
API_AVAILABLE(macos(11.0))
vector<id<MTLAccelerationStructure>> blas_array;
API_AVAILABLE(macos(11.0))
id<MTLAccelerationStructure> accel_struct = nil;
/* Residency sets -----------------------------------*/
void prepare_residency();
void metal_mem_alloc(id<MTLResource> allocation);
void metal_mem_free(id<MTLResource> allocation);
/* For externally-owned resources (e.g. graphics interop buffers) which need to be resident for
* kernels to access them, but shouldn't be included in our stats. */
void add_to_residency_set(id<MTLResource> allocation);
void remove_from_residency_set(id<MTLResource> allocation);
bool mtlResidencySet_enabled = false;
# if defined(MAC_OS_VERSION_15_0)
API_AVAILABLE(macos(15.0), ios(18.0))
id<MTLResidencySet> mtlResidencySet = nil;
bool mtlResidencySet_dirty = false;
/* Guards mtlResidencySet mutations (may be reached from multiple threads). */
std::mutex mtlResidencySet_mutex;
# endif
uint kernel_features = 0;
bool using_nanovdb = false;
int max_threads_per_threadgroup;
int mtlDevId = 0;
bool has_error = false;
struct MetalMem {
device_memory *mem = nullptr;
int pointer_index = -1;
id<MTLBuffer> mtlBuffer = nil;
id<MTLTexture> mtlTexture = nil;
uint64_t offset = 0;
uint64_t size = 0;
void *hostPtr = nullptr;
};
using MetalMemMap = map<device_memory *, unique_ptr<MetalMem>>;
MetalMemMap metal_mem_map;
std::vector<id<MTLResource>> delayed_free_list;
std::recursive_mutex metal_mem_map_mutex;
/* Bindless Textures */
bool is_texture(const KernelImageInfo &info);
device_vector<KernelImageInfo> image_info;
id<MTLBuffer> image_bindings = nil;
std::vector<id<MTLResource>> image_info_id_map;
MetalPipelineType kernel_specialization_level = PSO_GENERIC;
int device_id = 0;
static thread_mutex existing_devices_mutex;
static std::map<int, MetalDevice *> active_device_ids;
static bool is_device_cancelled(const int device_id);
static MetalDevice *get_device_by_ID(const int device_idID,
thread_scoped_lock &existing_devices_mutex_lock);
bool is_ready(string &status) const override;
void cancel() override;
BVHLayoutMask get_bvh_layout_mask(uint /*kernel_features*/) const override;
void set_error(const string &error) override;
MetalDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless);
~MetalDevice() override;
bool support_device(const uint /*kernel_features*/);
bool check_peer_access(Device *peer_device) override;
bool use_adaptive_compilation();
bool use_local_atomic_sort() const;
string preprocess_source(MetalPipelineType pso_type,
const uint kernel_features,
string *source = nullptr);
void refresh_source_and_kernels_md5(MetalPipelineType pso_type);
void make_source(MetalPipelineType pso_type, const uint kernel_features);
bool load_kernels(const uint kernel_features) override;
void load_image_info();
void erase_allocation(device_memory &mem);
bool should_use_graphics_interop(const GraphicsInteropDevice &interop_device,
const bool log) override;
void *get_native_buffer(device_ptr ptr) override;
unique_ptr<DeviceQueue> gpu_queue_create() override;
void build_bvh(BVH *bvh, Progress &progress, bool refit) override;
bool set_bvh_limits(size_t instance_count, size_t max_prim_count) override;
void optimize_for_scene(Scene *scene) override;
static void compile_and_load(const int device_id, MetalPipelineType pso_type);
/* ------------------------------------------------------------------ */
/* low-level memory management */
bool max_working_set_exceeded(const size_t safety_margin = 8 * 1024 * 1024) const;
MetalMem *generic_alloc(device_memory &mem);
void generic_copy_to(device_memory &mem);
void generic_free(device_memory &mem);
void mem_alloc(device_memory &mem) override;
void mem_copy_to(device_memory &mem) override;
void mem_move_to_host(device_memory &mem) override;
void mem_copy_from(device_memory &mem)
{
mem_copy_from(mem, -1, -1, -1, -1);
}
void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) override;
void mem_or_from_device(device_memory &mem) override;
void mem_zero(device_memory &mem) override;
void mem_free(device_memory &mem) override;
device_ptr mem_alloc_sub_ptr(device_memory &mem, const size_t offset, size_t /*size*/) override;
void const_copy_to(const char *name, void *host, const size_t size) override;
void global_alloc(device_memory &mem);
void global_free(device_memory &mem);
void image_alloc(device_image &mem);
void image_alloc_as_buffer(device_image &mem);
void image_copy_to(device_image &mem);
void image_free(device_image &mem);
bool has_unified_memory() const override;
void flush_delayed_free_list();
void free_bvh();
void update_bvh(BVHMetal *bvh_metal);
};
CCL_NAMESPACE_END
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_METAL
# include "device/graphics_interop.h"
# include "device/metal/device_impl.h"
# include "session/display_driver.h"
CCL_NAMESPACE_BEGIN
class MetalDevice;
class MetalDeviceQueue;
class MetalDeviceGraphicsInterop : public DeviceGraphicsInterop {
public:
explicit MetalDeviceGraphicsInterop(MetalDeviceQueue *queue);
MetalDeviceGraphicsInterop(const MetalDeviceGraphicsInterop &other) = delete;
MetalDeviceGraphicsInterop(MetalDeviceGraphicsInterop &&other) noexcept = delete;
~MetalDeviceGraphicsInterop() override;
MetalDeviceGraphicsInterop &operator=(const MetalDeviceGraphicsInterop &other) = delete;
MetalDeviceGraphicsInterop &operator=(MetalDeviceGraphicsInterop &&other) = delete;
void set_buffer(GraphicsInteropBuffer &interop_buffer) override;
device_ptr map() override;
void unmap() override;
protected:
MetalDeviceQueue *queue_ = nullptr;
MetalDevice *device_ = nullptr;
/* Native handle. */
MetalDevice::MetalMem mem_;
size_t size_ = 0;
/* The destination was requested to be cleared. */
bool need_zero_ = false;
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_METAL
# include "device/metal/graphics_interop.h"
# include "device/metal/device_impl.h"
CCL_NAMESPACE_BEGIN
MetalDeviceGraphicsInterop::MetalDeviceGraphicsInterop(MetalDeviceQueue *queue)
: queue_(queue), device_(static_cast<MetalDevice *>(queue->device))
{
}
MetalDeviceGraphicsInterop::~MetalDeviceGraphicsInterop()
{
device_->remove_from_residency_set(mem_.mtlBuffer);
}
void MetalDeviceGraphicsInterop::set_buffer(GraphicsInteropBuffer &interop_buffer)
{
if (interop_buffer.is_empty()) {
device_->remove_from_residency_set(mem_.mtlBuffer);
mem_.mtlBuffer = nullptr;
size_ = 0;
return;
}
need_zero_ |= interop_buffer.take_zero();
if (!interop_buffer.has_new_handle()) {
return;
}
/* The interop buffer is externally owned, so it doesn't go through metal_mem_alloc. We still
* need to put it in the residency set so kernels can write display output into it. */
device_->remove_from_residency_set(mem_.mtlBuffer);
mem_.mtlBuffer = reinterpret_cast<id<MTLBuffer>>(interop_buffer.take_handle());
size_ = interop_buffer.get_size();
device_->add_to_residency_set(mem_.mtlBuffer);
}
device_ptr MetalDeviceGraphicsInterop::map()
{
if (mem_.mtlBuffer && need_zero_) {
memset([mem_.mtlBuffer contents], 0, size_);
need_zero_ = false;
}
return device_ptr(&mem_);
}
void MetalDeviceGraphicsInterop::unmap() {}
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_METAL
# include "device/kernel.h"
# include <Metal/Metal.h>
CCL_NAMESPACE_BEGIN
class MetalDevice;
enum {
METALRT_TABLE_DEFAULT,
METALRT_TABLE_SHADOW,
METALRT_TABLE_SHADOW_ALL,
METALRT_TABLE_VOLUME,
METALRT_TABLE_LOCAL,
METALRT_TABLE_LOCAL_MBLUR,
METALRT_TABLE_LOCAL_SINGLE_HIT,
METALRT_TABLE_LOCAL_SINGLE_HIT_MBLUR,
METALRT_TABLE_NUM
};
/* Pipeline State Object types */
enum MetalPipelineType {
/* A kernel that can be used with all scenes, supporting all features.
* It is slow to compile, but only needs to be compiled once and is then
* cached for future render sessions. This allows a render to get underway
* on the GPU quickly.
*/
PSO_GENERIC,
/* A intersection kernel that is very quick to specialize and results in faster intersection
* kernel performance. It uses Metal function constants to replace several KernelData variables
* with fixed constants.
*/
PSO_SPECIALIZED_INTERSECT,
/* A shading kernel that is slow to specialize, but results in faster shading kernel performance
* rendered. It uses Metal function constants to replace several KernelData variables with fixed
* constants and short-circuit all unused SVM node case handlers.
*/
PSO_SPECIALIZED_SHADE,
PSO_NUM
};
# define METALRT_FEATURE_MASK \
(KERNEL_FEATURE_HAIR | KERNEL_FEATURE_HAIR_THICK | KERNEL_FEATURE_POINTCLOUD)
const char *kernel_type_as_string(MetalPipelineType pso_type);
/* A pipeline object that can be shared between multiple instances of MetalDeviceQueue. */
class MetalKernelPipeline {
public:
void compile();
int pipeline_id;
int originating_device_id;
id<MTLLibrary> mtlLibrary = nil;
MetalPipelineType pso_type;
string kernels_md5;
size_t usage_count = 0;
KernelData kernel_data_;
bool use_metalrt;
uint32_t kernel_features = 0;
int threads_per_threadgroup;
DeviceKernel device_kernel;
bool loaded = false;
id<MTLDevice> mtlDevice = nil;
id<MTLFunction> function = nil;
id<MTLComputePipelineState> pipeline = nil;
int num_threads_per_block = 0;
bool should_use_binary_archive() const;
id<MTLFunction> make_intersection_function(const char *function_name);
string error_str;
NSArray *table_functions[METALRT_TABLE_NUM] = {nil};
};
/* An actively instanced pipeline that can only be used by a single instance of MetalDeviceQueue.
*/
class MetalDispatchPipeline {
public:
~MetalDispatchPipeline();
bool update(MetalDevice *metal_device, DeviceKernel kernel);
void free_intersection_function_tables();
private:
friend class MetalDeviceQueue;
friend struct ShaderCache;
int pipeline_id = -1;
MetalDevice *metal_device = nullptr;
MetalPipelineType pso_type;
id<MTLComputePipelineState> pipeline = nil;
int num_threads_per_block = 0;
API_AVAILABLE(macos(11.0))
id<MTLIntersectionFunctionTable> intersection_func_table[METALRT_TABLE_NUM] = {nil};
};
/* Cache of Metal kernels for each DeviceKernel. */
namespace MetalDeviceKernels {
int num_incomplete_specialization_requests();
int get_loaded_kernel_count(const MetalDevice *device, MetalPipelineType pso_type);
bool should_load_kernels(const MetalDevice *device, MetalPipelineType pso_type);
bool load(MetalDevice *device, MetalPipelineType pso_type);
const MetalKernelPipeline *get_best_pipeline(const MetalDevice *device, DeviceKernel kernel);
void wait_for_all();
bool is_benchmark_warmup();
/* Deinitialize all static variables, so that no code would run on application exit. */
void static_deinitialize();
} /* namespace MetalDeviceKernels */
CCL_NAMESPACE_END
#endif /* WITH_METAL */

View File

@@ -0,0 +1,935 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_METAL
# include <algorithm>
# include <atomic>
# include <chrono>
# include <deque>
# include <thread>
# include <vector>
# include "device/metal/device_impl.h"
# include "device/metal/kernel.h"
# include "kernel/device/metal/function_constants.h"
# include "util/debug.h"
# include "util/md5.h"
# include "util/path.h"
# include "util/tbb.h"
# include "util/time.h"
# include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
const char *kernel_type_as_string(MetalPipelineType pso_type)
{
switch (pso_type) {
case PSO_GENERIC:
return "PSO_GENERIC";
case PSO_SPECIALIZED_INTERSECT:
return "PSO_SPECIALIZED_INTERSECT";
case PSO_SPECIALIZED_SHADE:
return "PSO_SPECIALIZED_SHADE";
default:
assert(0);
}
return "";
}
struct ShaderCache {
ShaderCache(id<MTLDevice> _mtlDevice) : mtlDevice(_mtlDevice)
{
/* Initialize occupancy tuning LUT. */
// TODO: Look into tuning for DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT and
// DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_*.
switch (MetalInfo::get_apple_gpu_architecture(mtlDevice)) {
default:
case APPLE_M3:
/* Peak occupancy is achieved through Dynamic Caching on M3 GPUs. */
for (size_t i = 0; i < DEVICE_KERNEL_NUM; i++) {
occupancy_tuning[i] = {64, 64};
}
break;
case APPLE_M2_BIG:
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES] = {384, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA] = {640, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST] = {1024, 64};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW] = {704, 704};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE] = {640, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY] = {896, 768};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND] = {512, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW] = {32, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE] = {768, 576};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY] = {896, 768};
break;
case APPLE_M2:
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES] = {32, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA] = {832, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST] = {64, 64};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW] = {64, 64};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE] = {704, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY] = {1024, 256};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND] = {64, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW] = {256, 256};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE] = {448, 384};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY] = {1024, 1024};
break;
case APPLE_M1:
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES] = {256, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA] = {768, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST] = {512, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW] = {384, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE] = {512, 64};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY] = {512, 256};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND] = {512, 128};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW] = {384, 32};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE] = {576, 384};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY] = {832, 832};
break;
}
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SORT_BUCKET_PASS] = {1024, 1024};
occupancy_tuning[DEVICE_KERNEL_INTEGRATOR_SORT_WRITE_PASS] = {1024, 1024};
}
~ShaderCache();
/* Get the fastest available pipeline for the specified kernel. */
MetalKernelPipeline *get_best_pipeline(DeviceKernel kernel, const MetalDevice *device);
/* Non-blocking request for a kernel, optionally specialized to the scene being rendered by
* device. */
void load_kernel(DeviceKernel kernel, MetalDevice *device, MetalPipelineType pso_type);
bool should_load_kernel(DeviceKernel device_kernel,
const MetalDevice *device,
MetalPipelineType pso_type);
void wait_for_all();
friend ShaderCache *get_shader_cache(id<MTLDevice> mtlDevice);
void compile_thread_func();
using PipelineCollection = std::vector<unique_ptr<MetalKernelPipeline>>;
struct OccupancyTuningParameters {
int threads_per_threadgroup = 0;
int num_threads_per_block = 0;
} occupancy_tuning[DEVICE_KERNEL_NUM];
std::mutex cache_mutex;
PipelineCollection pipelines[DEVICE_KERNEL_NUM];
id<MTLDevice> mtlDevice;
static bool running;
std::condition_variable cond_var;
std::deque<unique_ptr<MetalKernelPipeline>> request_queue;
std::vector<std::thread> compile_threads;
std::atomic_int incomplete_requests = 0;
std::atomic_int incomplete_specialization_requests = 0;
};
bool ShaderCache::running = true;
const int MAX_POSSIBLE_GPUS_ON_SYSTEM = 8;
using DeviceShaderCache = std::pair<id<MTLDevice>, unique_ptr<ShaderCache>>;
int g_shaderCacheCount = 0;
DeviceShaderCache g_shaderCache[MAX_POSSIBLE_GPUS_ON_SYSTEM];
/* Next UID for associating a MetalDispatchPipeline with an originating MetalKernelPipeline. */
static std::atomic_int g_next_pipeline_id = 0;
ShaderCache *get_shader_cache(id<MTLDevice> mtlDevice)
{
for (int i = 0; i < g_shaderCacheCount; i++) {
if (g_shaderCache[i].first == mtlDevice) {
return g_shaderCache[i].second.get();
}
}
static thread_mutex g_shaderCacheCountMutex;
g_shaderCacheCountMutex.lock();
int index = g_shaderCacheCount++;
g_shaderCacheCountMutex.unlock();
assert(index < MAX_POSSIBLE_GPUS_ON_SYSTEM);
g_shaderCache[index].first = mtlDevice;
g_shaderCache[index].second = make_unique<ShaderCache>(mtlDevice);
return g_shaderCache[index].second.get();
}
ShaderCache::~ShaderCache()
{
running = false;
cond_var.notify_all();
metal_printf("Waiting for ShaderCache threads... (incomplete_requests = %d)",
int(incomplete_requests));
for (auto &thread : compile_threads) {
thread.join();
}
metal_printf("ShaderCache shut down.");
}
void ShaderCache::wait_for_all()
{
while (incomplete_requests > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void ShaderCache::compile_thread_func()
{
while (running) {
/* wait for / acquire next request */
unique_ptr<MetalKernelPipeline> pipeline;
{
thread_scoped_lock lock(cache_mutex);
cond_var.wait(lock, [&] { return !running || !request_queue.empty(); });
if (!running || request_queue.empty()) {
continue;
}
pipeline = std::move(request_queue.front());
request_queue.pop_front();
}
/* Service the request. */
DeviceKernel device_kernel = pipeline->device_kernel;
MetalPipelineType pso_type = pipeline->pso_type;
if (MetalDevice::is_device_cancelled(pipeline->originating_device_id)) {
/* The originating MetalDevice is no longer active, so this request is obsolete. */
metal_printf("Cancelling compilation of %s (%s)",
device_kernel_as_string(device_kernel),
kernel_type_as_string(pso_type));
}
else {
/* Do the actual compilation. */
pipeline->compile();
thread_scoped_lock lock(cache_mutex);
auto &collection = pipelines[device_kernel];
/* Cache up to 3 kernel variants with the same pso_type in memory, purging oldest first. */
int max_entries_of_same_pso_type = 3;
for (int i = (int)collection.size() - 1; i >= 0; i--) {
if (collection[i]->pso_type == pso_type) {
max_entries_of_same_pso_type -= 1;
if (max_entries_of_same_pso_type == 0) {
metal_printf("Purging oldest %s:%s kernel from ShaderCache",
kernel_type_as_string(pso_type),
device_kernel_as_string(device_kernel));
collection.erase(collection.begin() + i);
break;
}
}
}
collection.push_back(std::move(pipeline));
}
incomplete_requests--;
if (pso_type != PSO_GENERIC) {
incomplete_specialization_requests--;
}
}
}
bool ShaderCache::should_load_kernel(DeviceKernel device_kernel,
const MetalDevice *device,
MetalPipelineType pso_type)
{
if (!running) {
return false;
}
if (!device_kernel_has_gpu_function(device_kernel)) {
/* Skip megakernel and other markers without a GPU function. */
return false;
}
if (device_kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE) {
if ((device->kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) == 0) {
/* Skip shade_surface_raytrace kernel if the scene doesn't require it. */
return false;
}
}
if (device_kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE) {
if ((device->kernel_features & KERNEL_FEATURE_MNEE) == 0) {
/* Skip the MNEE kernel if the scene doesn't require it. */
return false;
}
}
if (pso_type != PSO_GENERIC) {
/* Only specialize kernels where it can make an impact. */
if (device_kernel < DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST ||
device_kernel > DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL)
{
return false;
}
/* Only specialize shading / intersection kernels as requested. */
bool is_shade_kernel = (device_kernel >= DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
bool is_shade_pso = (pso_type == PSO_SPECIALIZED_SHADE);
if (is_shade_pso != is_shade_kernel) {
return false;
}
}
{
/* check whether the kernel has already been requested / cached */
thread_scoped_lock lock(cache_mutex);
for (auto &pipeline : pipelines[device_kernel]) {
if (pipeline->kernels_md5 == device->kernels_md5[pso_type]) {
return false;
}
}
}
return true;
}
void ShaderCache::load_kernel(DeviceKernel device_kernel,
MetalDevice *device,
MetalPipelineType pso_type)
{
{
/* create compiler threads on first run */
thread_scoped_lock lock(cache_mutex);
if (compile_threads.empty()) {
/* Limit to 2 MTLCompiler instances by default. In macOS >= 13.3 we can query the upper
* limit. */
int max_mtlcompiler_threads = 2;
# if defined(MAC_OS_VERSION_13_3)
if (@available(macOS 13.3, *)) {
/* Subtract one to avoid contention with the real-time GPU module. */
max_mtlcompiler_threads = max(2,
int([mtlDevice maximumConcurrentCompilationTaskCount]) - 1);
}
# endif
metal_printf("Spawning %d Cycles kernel compilation threads", max_mtlcompiler_threads);
for (int i = 0; i < max_mtlcompiler_threads; i++) {
compile_threads.emplace_back([this] { this->compile_thread_func(); });
}
}
}
if (!should_load_kernel(device_kernel, device, pso_type)) {
return;
}
incomplete_requests++;
if (pso_type != PSO_GENERIC) {
incomplete_specialization_requests++;
}
unique_ptr<MetalKernelPipeline> pipeline = make_unique<MetalKernelPipeline>();
/* Keep track of the originating device's ID so that we can cancel requests if the device ceases
* to be active. */
pipeline->pipeline_id = g_next_pipeline_id.fetch_add(1);
pipeline->originating_device_id = device->device_id;
pipeline->kernel_data_ = device->launch_params->data;
pipeline->pso_type = pso_type;
pipeline->mtlDevice = mtlDevice;
pipeline->kernels_md5 = device->kernels_md5[pso_type];
pipeline->mtlLibrary = device->mtlLibrary[pso_type];
pipeline->device_kernel = device_kernel;
pipeline->threads_per_threadgroup = device->max_threads_per_threadgroup;
if (occupancy_tuning[device_kernel].threads_per_threadgroup) {
pipeline->threads_per_threadgroup = occupancy_tuning[device_kernel].threads_per_threadgroup;
pipeline->num_threads_per_block = occupancy_tuning[device_kernel].num_threads_per_block;
}
/* metalrt options */
pipeline->use_metalrt = device->use_metalrt;
pipeline->kernel_features = device->kernel_features;
{
thread_scoped_lock lock(cache_mutex);
request_queue.push_back(std::move(pipeline));
}
cond_var.notify_one();
}
MetalKernelPipeline *ShaderCache::get_best_pipeline(DeviceKernel kernel, const MetalDevice *device)
{
while (running && !device->has_error) {
/* Search all loaded pipelines with matching kernels_md5 checksums. */
MetalKernelPipeline *best_match = nullptr;
{
thread_scoped_lock lock(cache_mutex);
for (auto &candidate : pipelines[kernel]) {
if (candidate->loaded &&
candidate->kernels_md5 == device->kernels_md5[candidate->pso_type])
{
/* Replace existing match if candidate is more specialized. */
if (!best_match || candidate->pso_type > best_match->pso_type) {
best_match = candidate.get();
}
}
}
}
if (best_match) {
if (best_match->usage_count == 0 && best_match->pso_type != PSO_GENERIC) {
metal_printf("Swapping in %s version of %s",
kernel_type_as_string(best_match->pso_type),
device_kernel_as_string(kernel));
}
best_match->usage_count += 1;
return best_match;
}
/* Spin until a matching kernel is loaded, or we're shutting down. */
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return nullptr;
}
bool MetalKernelPipeline::should_use_binary_archive() const
{
/* Issues with binary archives in older macOS versions. */
if (@available(macOS 15.4, *)) {
if (auto *str = getenv("CYCLES_METAL_DISABLE_BINARY_ARCHIVES")) {
if (atoi(str) != 0) {
/* Don't archive if we have opted out by env var. */
return false;
}
}
if (use_metalrt && device_kernel_has_intersection(device_kernel)) {
/* Binary linked functions aren't supported in binary archives. */
return false;
}
if (pso_type == PSO_GENERIC) {
/* Archive the generic kernels. */
return true;
}
if ((device_kernel >= DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND &&
device_kernel <= DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW) ||
(device_kernel >= DEVICE_KERNEL_SHADER_EVAL_DISPLACE &&
device_kernel <= DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY))
{
/* Archive all shade kernels - they take a long time to compile. */
return true;
}
/* The remaining kernels are all fast to compile. They may get cached by the system shader
* cache, but will be quick to regenerate if not. */
}
return false;
}
static MTLFunctionConstantValues *GetConstantValues(const KernelData *data = nullptr)
{
MTLFunctionConstantValues *constant_values = [MTLFunctionConstantValues new];
MTLDataType MTLDataType_int = MTLDataTypeInt;
MTLDataType MTLDataType_float = MTLDataTypeFloat;
MTLDataType MTLDataType_float2 = MTLDataTypeFloat2;
MTLDataType MTLDataType_float4 = MTLDataTypeFloat4;
KernelData zero_data = {0};
if (!data) {
data = &zero_data;
}
[constant_values setConstantValue:&zero_data type:MTLDataType_int atIndex:Kernel_DummyConstant];
bool next_member_is_specialized = true;
# define KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE next_member_is_specialized = false;
# define KERNEL_STRUCT_MEMBER(parent, _type, name) \
[constant_values setConstantValue:next_member_is_specialized ? (void *)&data->parent.name : \
(void *)&zero_data \
type:MTLDataType_##_type \
atIndex:KernelData_##parent##_##name]; \
next_member_is_specialized = true;
# include "kernel/data_template.h"
[constant_values setConstantValue:&data->kernel_features
type:MTLDataTypeInt
atIndex:KernelData_kernel_features];
return constant_values;
}
void MetalDispatchPipeline::free_intersection_function_tables()
{
for (int table = 0; table < METALRT_TABLE_NUM; table++) {
if (intersection_func_table[table]) {
/* Add the table to the delayed free list of the device that created it. */
metal_device->metal_mem_free(intersection_func_table[table]);
intersection_func_table[table] = nil;
}
}
}
MetalDispatchPipeline::~MetalDispatchPipeline()
{
free_intersection_function_tables();
}
bool MetalDispatchPipeline::update(MetalDevice *metal_device, DeviceKernel kernel)
{
this->metal_device = metal_device;
const MetalKernelPipeline *best_pipeline = MetalDeviceKernels::get_best_pipeline(metal_device,
kernel);
if (!best_pipeline) {
return false;
}
if (pipeline_id == best_pipeline->pipeline_id) {
/* The best pipeline is already active - nothing to do. */
return true;
}
pipeline_id = best_pipeline->pipeline_id;
pipeline = best_pipeline->pipeline;
pso_type = best_pipeline->pso_type;
num_threads_per_block = best_pipeline->num_threads_per_block;
/* Create the MTLIntersectionFunctionTables if needed. */
if (best_pipeline->use_metalrt && device_kernel_has_intersection(best_pipeline->device_kernel)) {
free_intersection_function_tables();
for (int table = 0; table < METALRT_TABLE_NUM; table++) {
@autoreleasepool {
MTLIntersectionFunctionTableDescriptor *ift_desc =
[[MTLIntersectionFunctionTableDescriptor alloc] init];
ift_desc.functionCount = best_pipeline->table_functions[table].count;
intersection_func_table[table] = [this->pipeline
newIntersectionFunctionTableWithDescriptor:ift_desc];
/* Finally write the function handles into this pipeline's table */
int size = int([best_pipeline->table_functions[table] count]);
for (int i = 0; i < size; i++) {
id<MTLFunctionHandle> handle = [pipeline
functionHandleWithFunction:best_pipeline->table_functions[table][i]];
[intersection_func_table[table] setFunction:handle atIndex:i];
}
/* Bind launch_params into the intersection function table once, when the table is
* (re)created. launch_params_buffer is allocated once and never moves, and the binding
* persists on the table, so there's no need to rebind it on every dispatch. */
[intersection_func_table[table] setBuffer:metal_device->launch_params_buffer
offset:0
atIndex:1];
metal_device->metal_mem_alloc(intersection_func_table[table]);
}
}
}
return true;
}
id<MTLFunction> MetalKernelPipeline::make_intersection_function(const char *function_name)
{
MTLFunctionDescriptor *desc = [MTLIntersectionFunctionDescriptor functionDescriptor];
desc.name = [@(function_name) copy];
if (pso_type != PSO_GENERIC) {
desc.constantValues = GetConstantValues(&kernel_data_);
}
else {
desc.constantValues = GetConstantValues();
}
NSError *error = nullptr;
id<MTLFunction> rt_intersection_function = [mtlLibrary newFunctionWithDescriptor:desc
error:&error];
if (rt_intersection_function == nil) {
NSString *err = [error localizedDescription];
string errors = [err UTF8String];
error_str = string_printf(
"Error getting intersection function \"%s\": %s", function_name, errors.c_str());
}
else {
rt_intersection_function.label = [@(function_name) copy];
}
return rt_intersection_function;
}
void MetalKernelPipeline::compile()
{
const std::string function_name = std::string("cycles_metal_") +
device_kernel_as_string(device_kernel);
NSError *error = nullptr;
MTLFunctionDescriptor *func_desc = [MTLIntersectionFunctionDescriptor functionDescriptor];
func_desc.name = [@(function_name.c_str()) copy];
if (pso_type != PSO_GENERIC) {
func_desc.constantValues = GetConstantValues(&kernel_data_);
}
else {
func_desc.constantValues = GetConstantValues();
}
function = [mtlLibrary newFunctionWithDescriptor:func_desc error:&error];
if (function == nil) {
NSString *err = [error localizedDescription];
string errors = [err UTF8String];
metal_printf("Error getting function \"%s\": %s", function_name.c_str(), errors.c_str());
return;
}
function.label = [@(function_name.c_str()) copy];
NSArray *linked_functions = nil;
if (use_metalrt && device_kernel_has_intersection(device_kernel)) {
NSMutableSet *unique_functions = [[NSMutableSet alloc] init];
auto add_intersection_functions = [&](int table_index,
const char *tri_fn,
const char *curve_fn = nullptr,
const char *point_fn = nullptr) {
table_functions[table_index] = [NSArray
arrayWithObjects:make_intersection_function(tri_fn),
curve_fn ? make_intersection_function(curve_fn) : nil,
point_fn ? make_intersection_function(point_fn) : nil,
nil];
[unique_functions addObjectsFromArray:table_functions[table_index]];
};
add_intersection_functions(METALRT_TABLE_DEFAULT,
"__intersection__tri",
"__intersection__curve",
"__intersection__point");
add_intersection_functions(METALRT_TABLE_SHADOW,
"__intersection__tri_shadow",
"__intersection__curve_shadow",
"__intersection__point_shadow");
add_intersection_functions(METALRT_TABLE_SHADOW_ALL,
"__intersection__tri_shadow_all",
"__intersection__curve_shadow_all",
"__intersection__point_shadow_all");
add_intersection_functions(METALRT_TABLE_VOLUME, "__intersection__volume_tri");
add_intersection_functions(METALRT_TABLE_LOCAL, "__intersection__local_tri");
add_intersection_functions(METALRT_TABLE_LOCAL_MBLUR, "__intersection__local_tri_mblur");
add_intersection_functions(METALRT_TABLE_LOCAL_SINGLE_HIT,
"__intersection__local_tri_single_hit");
add_intersection_functions(METALRT_TABLE_LOCAL_SINGLE_HIT_MBLUR,
"__intersection__local_tri_single_hit_mblur");
linked_functions = [[NSArray arrayWithArray:[unique_functions allObjects]]
sortedArrayUsingComparator:^NSComparisonResult(id<MTLFunction> f1, id<MTLFunction> f2) {
return [f1.label compare:f2.label];
}];
unique_functions = nil;
}
MTLComputePipelineDescriptor *computePipelineStateDescriptor =
[[MTLComputePipelineDescriptor alloc] init];
computePipelineStateDescriptor.buffers[0].mutability = MTLMutabilityImmutable;
computePipelineStateDescriptor.buffers[1].mutability = MTLMutabilityImmutable;
computePipelineStateDescriptor.buffers[2].mutability = MTLMutabilityImmutable;
computePipelineStateDescriptor.maxTotalThreadsPerThreadgroup = threads_per_threadgroup;
computePipelineStateDescriptor.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
computePipelineStateDescriptor.computeFunction = function;
/* Attach the additional functions to an MTLLinkedFunctions object */
if (linked_functions) {
computePipelineStateDescriptor.linkedFunctions = [[MTLLinkedFunctions alloc] init];
computePipelineStateDescriptor.linkedFunctions.functions = linked_functions;
}
computePipelineStateDescriptor.maxCallStackDepth = 1;
if (use_metalrt && device_kernel_has_intersection(device_kernel)) {
computePipelineStateDescriptor.maxCallStackDepth = 2;
}
MTLPipelineOption pipelineOptions = MTLPipelineOptionNone;
bool use_binary_archive = should_use_binary_archive();
bool loading_existing_archive = false;
bool creating_new_archive = false;
id<MTLBinaryArchive> archive = nil;
string metalbin_path;
string metalbin_name;
if (use_binary_archive) {
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
string osVersion = [[processInfo operatingSystemVersionString] UTF8String];
MD5Hash local_md5;
local_md5.append(kernels_md5);
local_md5.append(osVersion);
local_md5.append((uint8_t *)&this->threads_per_threadgroup,
sizeof(this->threads_per_threadgroup));
/* Replace non-alphanumerical characters with underscores. */
string device_name = [mtlDevice.name UTF8String];
for (char &c : device_name) {
if ((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) {
c = '_';
}
}
metalbin_name = device_name;
metalbin_name = path_join(metalbin_name, device_kernel_as_string(device_kernel));
metalbin_name = path_join(metalbin_name, kernel_type_as_string(pso_type));
metalbin_name = path_join(metalbin_name, local_md5.get_hex() + ".bin");
metalbin_path = path_cache_get(path_join("kernels", metalbin_name));
path_create_directories(metalbin_path);
/* Check if shader binary exists on disk, and if so, update the file timestamp for LRU purging
* to work as intended. */
loading_existing_archive = path_cache_kernel_exists_and_mark_used(metalbin_path);
creating_new_archive = !loading_existing_archive;
MTLBinaryArchiveDescriptor *archiveDesc = [[MTLBinaryArchiveDescriptor alloc] init];
if (loading_existing_archive) {
archiveDesc.url = [NSURL fileURLWithPath:@(metalbin_path.c_str())];
}
NSError *error = nil;
archive = [mtlDevice newBinaryArchiveWithDescriptor:archiveDesc error:&error];
if (!archive) {
const char *err = error ? [[error localizedDescription] UTF8String] : nullptr;
metal_printf("newBinaryArchiveWithDescriptor failed: %s", err ? err : "nil");
}
[archiveDesc release];
if (loading_existing_archive) {
pipelineOptions = MTLPipelineOptionFailOnBinaryArchiveMiss;
computePipelineStateDescriptor.binaryArchives = [NSArray arrayWithObjects:archive, nil];
}
}
bool recreate_archive = false;
/* Lambda to do the actual pipeline compilation. */
auto do_compilation = [&]() {
__block bool compilation_finished = false;
__block string error_str;
if (loading_existing_archive || !DebugFlags().metal.use_async_pso_creation) {
/* Use the blocking variant of newComputePipelineStateWithDescriptor if an archive exists on
* disk. It should load almost instantaneously, and will fail gracefully when loading a
* corrupt archive (unlike the async variant). */
NSError *error = nil;
pipeline = [mtlDevice newComputePipelineStateWithDescriptor:computePipelineStateDescriptor
options:pipelineOptions
reflection:nullptr
error:&error];
const char *err = error ? [[error localizedDescription] UTF8String] : nullptr;
error_str = err ? err : "nil";
}
else {
/* Use the async variant of newComputePipelineStateWithDescriptor if no archive exists on
* disk. This allows us to respond to app shutdown. */
[mtlDevice
newComputePipelineStateWithDescriptor:computePipelineStateDescriptor
options:pipelineOptions
completionHandler:^(id<MTLComputePipelineState> computePipelineState,
MTLComputePipelineReflection * /*reflection*/,
NSError *error) {
pipeline = computePipelineState;
/* Retain the pipeline so we can use it safely past the completion
* handler. */
if (pipeline) {
[pipeline retain];
}
const char *err = error ?
[[error localizedDescription] UTF8String] :
nullptr;
error_str = err ? err : "nil";
compilation_finished = true;
}];
/* Immediately wait for either the compilation to finish or for app shutdown. */
while (ShaderCache::running && !compilation_finished) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
if (creating_new_archive && pipeline) {
/* Add pipeline into the new archive. */
NSError *error;
if (![archive addComputePipelineFunctionsWithDescriptor:computePipelineStateDescriptor
error:&error])
{
NSString *errStr = [error localizedDescription];
metal_printf("Failed to add PSO to archive:\n%s", errStr ? [errStr UTF8String] : "nil");
}
}
if (!pipeline) {
metal_printf(
"newComputePipelineStateWithDescriptor failed for \"%s\"%s. "
"Error:\n%s\n",
device_kernel_as_string(device_kernel),
(archive && !recreate_archive) ? " Archive may be incomplete or corrupt - attempting "
"recreation.." :
"",
error_str.c_str());
}
};
double starttime = time_dt();
do_compilation();
/* An archive might have a corrupt entry and fail to materialize the pipeline. This shouldn't
* happen, but if it does we recreate it. */
if (pipeline == nil && archive) {
recreate_archive = true;
pipelineOptions = MTLPipelineOptionNone;
path_remove(metalbin_path);
do_compilation();
}
double duration = time_dt() - starttime;
if (pipeline == nil) {
metal_printf("%16s | %2d | %-55s | %7.2fs | FAILED!",
kernel_type_as_string(pso_type),
device_kernel,
device_kernel_as_string(device_kernel),
duration);
return;
}
if (!num_threads_per_block) {
num_threads_per_block = round_down(pipeline.maxTotalThreadsPerThreadgroup,
pipeline.threadExecutionWidth);
num_threads_per_block = std::max(num_threads_per_block, (int)pipeline.threadExecutionWidth);
}
if (ShaderCache::running) {
if (creating_new_archive || recreate_archive) {
if (![archive serializeToURL:[NSURL fileURLWithPath:@(metalbin_path.c_str())] error:&error])
{
metal_printf("Failed to save binary archive to %s, error:\n%s",
metalbin_path.c_str(),
[[error localizedDescription] UTF8String]);
}
else {
path_cache_kernel_mark_added_and_clear_old(metalbin_path);
}
}
}
this->loaded = true;
[computePipelineStateDescriptor release];
computePipelineStateDescriptor = nil;
if (!use_binary_archive) {
metal_printf("%16s | %2d | %-55s | %7.2fs",
kernel_type_as_string(pso_type),
int(device_kernel),
device_kernel_as_string(device_kernel),
duration);
}
else {
metal_printf("%16s | %2d | %-55s | %7.2fs | %s: %s",
kernel_type_as_string(pso_type),
device_kernel,
device_kernel_as_string(device_kernel),
duration,
creating_new_archive ? " new" : "load",
metalbin_name.c_str());
}
}
bool MetalDeviceKernels::load(MetalDevice *device, MetalPipelineType pso_type)
{
auto *shader_cache = get_shader_cache(device->mtlDevice);
for (int i = 0; i < DEVICE_KERNEL_NUM; i++) {
shader_cache->load_kernel((DeviceKernel)i, device, pso_type);
}
return true;
}
void MetalDeviceKernels::wait_for_all()
{
for (int i = 0; i < g_shaderCacheCount; i++) {
g_shaderCache[i].second->wait_for_all();
}
}
int MetalDeviceKernels::num_incomplete_specialization_requests()
{
/* Return true if any ShaderCaches have ongoing specialization requests (typically there will be
* only 1). */
int total = 0;
for (int i = 0; i < g_shaderCacheCount; i++) {
total += g_shaderCache[i].second->incomplete_specialization_requests;
}
return total;
}
int MetalDeviceKernels::get_loaded_kernel_count(const MetalDevice *device,
MetalPipelineType pso_type)
{
auto *shader_cache = get_shader_cache(device->mtlDevice);
int loaded_count = DEVICE_KERNEL_NUM;
for (int i = 0; i < DEVICE_KERNEL_NUM; i++) {
if (shader_cache->should_load_kernel((DeviceKernel)i, device, pso_type)) {
loaded_count -= 1;
}
}
return loaded_count;
}
bool MetalDeviceKernels::should_load_kernels(const MetalDevice *device, MetalPipelineType pso_type)
{
return get_loaded_kernel_count(device, pso_type) != DEVICE_KERNEL_NUM;
}
const MetalKernelPipeline *MetalDeviceKernels::get_best_pipeline(const MetalDevice *device,
DeviceKernel kernel)
{
return get_shader_cache(device->mtlDevice)->get_best_pipeline(kernel, device);
}
bool MetalDeviceKernels::is_benchmark_warmup()
{
NSArray *args = [[NSProcessInfo processInfo] arguments];
for (int i = 0; i < args.count; i++) {
if (const char *arg = [[args objectAtIndex:i] cStringUsingEncoding:NSASCIIStringEncoding]) {
if (!strcmp(arg, "--warm-up")) {
return true;
}
}
}
return false;
}
void MetalDeviceKernels::static_deinitialize()
{
for (int i = 0; i < g_shaderCacheCount; i++) {
g_shaderCache[i] = DeviceShaderCache();
}
}
CCL_NAMESPACE_END
#endif /* WITH_METAL */

View File

@@ -0,0 +1,130 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_METAL
# include "device/kernel.h"
# include "device/memory.h"
# include "device/queue.h"
# include "device/metal/util.h"
# include "kernel/device/metal/globals.h"
# define MAX_SAMPLE_BUFFER_LENGTH 4096
/* The number of resources to be contiguously encoded into the MetalAncillaries struct. */
# define ANCILLARY_SLOT_COUNT 11
CCL_NAMESPACE_BEGIN
class MetalDevice;
/* Base class for Metal queues. */
class MetalDeviceQueue : public DeviceQueue {
public:
MetalDeviceQueue(MetalDevice *device);
~MetalDeviceQueue() override;
int num_concurrent_states(const size_t /*state_size*/) const override;
int num_concurrent_busy_states(const size_t /*state_size*/) const override;
int num_sort_partitions(int max_num_paths, uint max_scene_shaders) const override;
bool supports_local_atomic_sort() const override;
void init_execution() override;
void load_image_info() override;
bool enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args) override;
bool synchronize() override;
void zero_to_device(device_memory &mem) override;
void copy_to_device(device_memory &mem) override;
void copy_from_device(device_memory &mem) override;
void *copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage) override;
void *native_queue() override;
unique_ptr<DeviceGraphicsInterop> graphics_interop_create() override;
protected:
void setup_capture();
void update_capture(DeviceKernel kernel);
void begin_capture();
void end_capture();
void prepare_resources();
id<MTLComputeCommandEncoder> get_compute_encoder(DeviceKernel kernel);
id<MTLBlitCommandEncoder> get_blit_encoder();
MetalDevice *metal_device_;
API_AVAILABLE(macos(11.0), ios(14.0))
MTLCommandBufferDescriptor *command_buffer_desc_ = nullptr;
id<MTLDevice> mtlDevice_ = nil;
id<MTLCommandQueue> mtlCommandQueue_ = nil;
id<MTLCommandBuffer> mtlCommandBuffer_ = nil;
id<MTLComputeCommandEncoder> mtlComputeEncoder_ = nil;
id<MTLBlitCommandEncoder> mtlBlitEncoder_ = nil;
API_AVAILABLE(macos(10.14), ios(14.0))
id<MTLSharedEvent> shared_event_ = nil;
API_AVAILABLE(macos(10.14), ios(14.0))
MTLSharedEventListener *shared_event_listener_ = nil;
MetalDispatchPipeline active_pipelines_[DEVICE_KERNEL_NUM];
dispatch_queue_t event_queue_;
dispatch_semaphore_t wait_semaphore_;
uint64_t shared_event_id_;
uint64_t command_buffers_submitted_ = 0;
uint64_t command_buffers_completed_ = 0;
Stats &stats_;
void close_compute_encoder();
void close_blit_encoder();
bool verbose_tracing_ = false;
bool label_command_encoders_ = false;
/* Per-kernel profiling (see CYCLES_METAL_PROFILING). */
struct TimingData {
DeviceKernel kernel;
int work_size;
uint64_t timing_id;
};
std::vector<TimingData> command_encoder_labels_;
bool profiling_enabled_ = false;
uint64_t current_encoder_idx_ = 0;
std::atomic<uint64_t> counter_sample_buffer_curr_idx_ = 0;
void flush_timing_stats();
struct TimingStats {
double total_time = 0.0;
uint64_t total_work_size = 0;
uint64_t num_dispatches = 0;
};
TimingStats timing_stats_[DEVICE_KERNEL_NUM];
double last_completion_time_ = 0.0;
/* .gputrace capture (see CYCLES_DEBUG_METAL_CAPTURE_...). */
id<MTLCaptureScope> mtlCaptureScope_ = nil;
DeviceKernel capture_kernel_;
int capture_dispatch_counter_ = 0;
bool capture_samples_ = false;
int capture_reset_counter_ = 0;
bool is_capturing_ = false;
bool is_capturing_to_disk_ = false;
bool has_captured_to_disk_ = false;
};
CCL_NAMESPACE_END
#endif /* WITH_METAL */

View File

@@ -0,0 +1,922 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_METAL
# include <algorithm>
# include <mutex>
# include "device/metal/queue.h"
# include "device/metal/device_impl.h"
# include "device/metal/graphics_interop.h"
# include "device/metal/kernel.h"
# include "util/path.h"
# include "util/string.h"
# include "util/time.h"
CCL_NAMESPACE_BEGIN
/* MetalDeviceQueue */
MetalDeviceQueue::MetalDeviceQueue(MetalDevice *device)
: DeviceQueue(device), metal_device_(device), stats_(device->stats)
{
@autoreleasepool {
command_buffer_desc_ = [[MTLCommandBufferDescriptor alloc] init];
command_buffer_desc_.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus;
mtlDevice_ = device->mtlDevice;
mtlCommandQueue_ = device->mtlComputeCommandQueue;
shared_event_ = [mtlDevice_ newSharedEvent];
shared_event_id_ = 1;
/* Shareable event listener */
event_queue_ = dispatch_queue_create("com.cycles.metal.event_queue", nullptr);
shared_event_listener_ = [[MTLSharedEventListener alloc] initWithDispatchQueue:event_queue_];
wait_semaphore_ = dispatch_semaphore_create(0);
if (auto *str = getenv("CYCLES_METAL_PROFILING")) {
if (atoi(str) && [mtlDevice_ supportsCounterSampling:MTLCounterSamplingPointAtStageBoundary])
{
/* Enable per-kernel timing breakdown (shown at end of render). */
profiling_enabled_ = true;
label_command_encoders_ = true;
}
}
if (getenv("CYCLES_METAL_DEBUG")) {
/* Enable very verbose tracing (shows every dispatch). */
verbose_tracing_ = true;
label_command_encoders_ = true;
}
setup_capture();
}
}
void MetalDeviceQueue::setup_capture()
{
capture_kernel_ = DeviceKernel(-1);
if (auto *capture_kernel_str = getenv("CYCLES_DEBUG_METAL_CAPTURE_KERNEL")) {
/* CYCLES_DEBUG_METAL_CAPTURE_KERNEL captures a single dispatch of the specified kernel. */
capture_kernel_ = DeviceKernel(atoi(capture_kernel_str));
printf("Capture kernel: %d = %s\n", capture_kernel_, device_kernel_as_string(capture_kernel_));
capture_dispatch_counter_ = 0;
if (auto *capture_dispatch_str = getenv("CYCLES_DEBUG_METAL_CAPTURE_DISPATCH")) {
capture_dispatch_counter_ = atoi(capture_dispatch_str);
printf("Capture dispatch number %d\n", capture_dispatch_counter_);
}
}
else if (auto *capture_samples_str = getenv("CYCLES_DEBUG_METAL_CAPTURE_SAMPLES")) {
/* CYCLES_DEBUG_METAL_CAPTURE_SAMPLES captures a block of dispatches from reset#(N) to
* reset#(N+1). */
capture_samples_ = true;
capture_reset_counter_ = atoi(capture_samples_str);
capture_dispatch_counter_ = INT_MAX;
if (auto *capture_limit_str = getenv("CYCLES_DEBUG_METAL_CAPTURE_LIMIT")) {
/* CYCLES_DEBUG_METAL_CAPTURE_LIMIT sets the maximum number of dispatches to capture. */
capture_dispatch_counter_ = atoi(capture_limit_str);
}
printf("Capturing sample block %d (dispatch limit: %d)\n",
capture_reset_counter_,
capture_dispatch_counter_);
}
else {
/* No capturing requested. */
return;
}
/* Enable .gputrace capture for the specified DeviceKernel. */
MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager];
mtlCaptureScope_ = [captureManager newCaptureScopeWithDevice:mtlDevice_];
mtlCaptureScope_.label = [NSString stringWithFormat:@"Cycles kernel dispatch"];
[captureManager setDefaultCaptureScope:mtlCaptureScope_];
label_command_encoders_ = true;
if (auto *capture_url = getenv("CYCLES_DEBUG_METAL_CAPTURE_URL")) {
if ([captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument]) {
MTLCaptureDescriptor *captureDescriptor = [[MTLCaptureDescriptor alloc] init];
captureDescriptor.captureObject = mtlCaptureScope_;
captureDescriptor.destination = MTLCaptureDestinationGPUTraceDocument;
captureDescriptor.outputURL = [NSURL fileURLWithPath:@(capture_url)];
NSError *error;
if (![captureManager startCaptureWithDescriptor:captureDescriptor error:&error]) {
NSString *err = [error localizedDescription];
printf("Start capture failed: %s\n", [err UTF8String]);
}
else {
printf("Capture started (URL: %s)\n", capture_url);
is_capturing_to_disk_ = true;
}
}
else {
printf("Capture to file is not supported\n");
}
}
}
void MetalDeviceQueue::update_capture(DeviceKernel kernel)
{
/* Handle capture end triggers. */
if (is_capturing_) {
capture_dispatch_counter_ -= 1;
if (capture_dispatch_counter_ <= 0 || kernel == DEVICE_KERNEL_INTEGRATOR_RESET) {
/* End capture if we've hit the dispatch limit or we hit a "reset". */
end_capture();
}
return;
}
if (capture_dispatch_counter_ < 0) {
/* We finished capturing. */
return;
}
/* Handle single-capture start trigger. */
if (kernel == capture_kernel_) {
/* Start capturing when we hit the Nth dispatch of the specified kernel. */
if (capture_dispatch_counter_ == 0) {
begin_capture();
}
capture_dispatch_counter_ -= 1;
return;
}
/* Handle multi-capture start trigger. */
if (capture_samples_) {
/* Start capturing when the reset countdown is at 0. */
if (capture_reset_counter_ == 0) {
begin_capture();
}
if (kernel == DEVICE_KERNEL_INTEGRATOR_RESET) {
capture_reset_counter_ -= 1;
}
return;
}
}
void MetalDeviceQueue::begin_capture()
{
/* Start gputrace capture. */
if (mtlCommandBuffer_) {
synchronize();
}
[mtlCaptureScope_ beginScope];
printf("[mtlCaptureScope_ beginScope]\n");
is_capturing_ = true;
}
void MetalDeviceQueue::end_capture()
{
[mtlCaptureScope_ endScope];
is_capturing_ = false;
printf("[mtlCaptureScope_ endScope]\n");
if (is_capturing_to_disk_) {
[[MTLCaptureManager sharedCaptureManager] stopCapture];
has_captured_to_disk_ = true;
is_capturing_to_disk_ = false;
is_capturing_ = false;
printf("Capture stopped\n");
}
}
MetalDeviceQueue::~MetalDeviceQueue()
{
/* Tidying up here isn't really practical - we should expect and require the work
* queue to be empty here. */
assert(mtlCommandBuffer_ == nil);
assert(command_buffers_submitted_ == command_buffers_completed_);
close_compute_encoder();
close_blit_encoder();
[shared_event_listener_ release];
[shared_event_ release];
[command_buffer_desc_ release];
if (mtlCaptureScope_) {
[mtlCaptureScope_ release];
}
double total_time = 0.0;
/* Show per-kernel timings, if gathered (see CYCLES_METAL_PROFILING). */
int64_t num_dispatches = 0;
int64_t num_pathtracing_dispatches = 0;
for (size_t i = 0; i < DEVICE_KERNEL_NUM; i++) {
auto &stat = timing_stats_[i];
bool pathtracing_kernel = (i <= DEVICE_KERNEL_INTEGRATOR_RESET) &&
(i != DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL);
total_time += stat.total_time;
num_dispatches += stat.num_dispatches;
num_pathtracing_dispatches += pathtracing_kernel ? stat.num_dispatches : 0;
}
bool has_extra = (num_pathtracing_dispatches && num_dispatches > num_pathtracing_dispatches);
if (num_dispatches) {
printf("\nMetal %sdispatch stats:\n", num_pathtracing_dispatches ? "path-tracing " : "");
auto header = string_printf("%-40s %16s %12s %12s %9s %9s",
"Kernel name",
"Total threads",
"Dispatches",
"Avg. T/D",
"Time/s",
"Time/%");
auto divider = string(header.length(), '-');
printf("%s\n%s\n%s\n", divider.c_str(), header.c_str(), divider.c_str());
for (size_t i = 0; i < DEVICE_KERNEL_NUM; i++) {
auto &stat = timing_stats_[i];
bool pathtracing_kernel = (i <= DEVICE_KERNEL_INTEGRATOR_RESET) &&
(i != DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL);
if ((pathtracing_kernel && num_pathtracing_dispatches) || stat.num_dispatches > 0) {
printf("%-40s %16llu %12llu %12llu %9.4f %9.2f\n",
device_kernel_as_string(DeviceKernel(i)),
stat.total_work_size,
stat.num_dispatches,
stat.total_work_size / stat.num_dispatches,
stat.total_time,
stat.total_time * 100.0 / total_time);
}
if (has_extra && i == DEVICE_KERNEL_INTEGRATOR_RESET) {
printf("%s\n", divider.c_str());
}
}
printf("%s\n", divider.c_str());
printf("%-40s %16s %12llu %12s %9.4f %9.2f\n", "", "", num_dispatches, "", total_time, 100.0);
printf("%s\n\n", divider.c_str());
}
}
int MetalDeviceQueue::num_concurrent_states(const size_t state_size) const
{
size_t state_count = 4194304;
/* Increasing the state count doesn't notably benefit M1-family systems. */
if (MetalInfo::get_apple_gpu_architecture(metal_device_->mtlDevice) != APPLE_M1) {
const size_t max_recommended_working_set =
[metal_device_->mtlDevice recommendedMaxWorkingSetSize];
/* Only use 90% of available working set for safety. */
size_t percent = 90;
if (auto *str = getenv("CYCLES_METAL_WORKING_SET_PERCENT")) {
percent = atoi(str);
}
const size_t max_working_set = (max_recommended_working_set * percent) / 100;
size_t max_safe_state_count = 0;
if (stats_.mem_used < max_working_set) {
const size_t headroom = max_working_set - stats_.mem_used;
max_safe_state_count = headroom / state_size;
}
/* Require a bare minimum of states to avoid pathological performance. */
if (max_safe_state_count >= 65536) {
/* If RAM is limited, we can still render with reduced state count. */
if (max_safe_state_count < state_count) {
metal_printf(
"Reducing state count to fit within available RAM. %zu -> %zu (%.1f%% of original "
"size)",
state_count,
max_safe_state_count,
double(max_safe_state_count) / double(state_count) * 100.0);
state_count = max_safe_state_count;
}
else {
/* Aggressive safety margin: only grow if it leaves us at < 50% max working set
* utilization. */
size_t grow_percent = 50;
if (auto *str = getenv("CYCLES_METAL_GROW_PERCENT")) {
grow_percent = atoi(str);
}
max_safe_state_count = (max_safe_state_count * grow_percent) / 100;
/* Limit to two "doublings" - we see diminishing returns after that. */
for (int i = 0; i < 2; i++) {
/* Determine whether we can double the state count, and leave enough GPU-available
* memory. Enlarging the state size allows us to keep dispatch sizes high and minimize
* work submission overheads. */
if (max_safe_state_count > state_count * 2) {
state_count *= 2;
metal_printf("Doubling state count to exploit available RAM (new size = %zu)",
state_count);
}
}
}
}
else {
metal_device_->set_error("Out of memory - couldn't allocate integrator state");
state_count = 0;
}
}
return state_count;
}
int MetalDeviceQueue::num_concurrent_busy_states(const size_t state_size) const
{
/* A 1:4 busy:total ratio gives best rendering performance, independent of total state count. */
return num_concurrent_states(state_size) / 4;
}
int MetalDeviceQueue::num_sort_partitions(int max_num_paths, uint max_scene_shaders) const
{
int sort_partition_elements = MetalInfo::optimal_sort_partition_elements();
/* Sort partitioning becomes less effective when more shaders are in the wavefront. In lieu of
* a more sophisticated heuristic we simply disable sort partitioning if the shader count is
* high.
*/
if (max_scene_shaders < 300 && sort_partition_elements > 0) {
return max(max_num_paths / sort_partition_elements, 1);
}
else {
return 1;
}
}
bool MetalDeviceQueue::supports_local_atomic_sort() const
{
return metal_device_->use_local_atomic_sort();
}
static void zero_resource(void *address_in_arg_buffer, int index = 0)
{
uint64_t *pptr = (uint64_t *)address_in_arg_buffer;
pptr[index] = 0;
}
template<class T> void write_resource(void *address_in_arg_buffer, T resource, int index = 0)
{
zero_resource(address_in_arg_buffer, index);
uint64_t *pptr = (uint64_t *)address_in_arg_buffer;
if (resource) {
pptr[index] = metal_gpuResourceID(resource);
}
}
template<> void write_resource(void *address_in_arg_buffer, id<MTLBuffer> buffer, int index)
{
zero_resource(address_in_arg_buffer, index);
uint64_t *pptr = (uint64_t *)address_in_arg_buffer;
if (buffer) {
pptr[index] = metal_gpuAddress(buffer);
}
}
static id<MTLBuffer> patch_resource(void *address_in_arg_buffer, int index = 0)
{
uint64_t *pptr = (uint64_t *)address_in_arg_buffer;
if (MetalDevice::MetalMem *mmem = (MetalDevice::MetalMem *)pptr[index]) {
write_resource<id<MTLBuffer>>(address_in_arg_buffer, mmem->mtlBuffer, index);
return mmem->mtlBuffer;
}
return nil;
}
void MetalDeviceQueue::init_execution()
{
/* Populate blas_array. */
uint64_t *blas_array = (uint64_t *)metal_device_->blas_buffer.contents;
for (uint64_t slot = 0; slot < metal_device_->blas_array.size(); ++slot) {
write_resource(blas_array, metal_device_->blas_array[slot], slot);
}
/* Populate image bindings. */
load_image_info();
/* Synchronize memory copies. */
synchronize();
}
void MetalDeviceQueue::load_image_info()
{
/* TODO: Can this be optimized to only update info ids that changed? Why is this done delayed
* instead of immediately when allocating the image? */
device_vector<KernelImageInfo> &image_info = metal_device_->image_info;
id<MTLBuffer> &image_bindings = metal_device_->image_bindings;
std::vector<id<MTLResource>> &image_info_id_map = metal_device_->image_info_id_map;
/* Ensure image_info is allocated before populating. */
image_info.copy_to_device();
/* Populate texture bindings. */
uint64_t *bindings = (uint64_t *)image_bindings.contents;
memset(bindings, 0, image_bindings.length);
for (int image_info_id = 0; image_info_id < image_info.size(); ++image_info_id) {
if (image_info_id_map[image_info_id]) {
if (metal_device_->is_texture(image_info[image_info_id])) {
write_resource(bindings, id<MTLTexture>(image_info_id_map[image_info_id]), image_info_id);
}
else {
/* The GPU address of a 1D buffer texture is written into the image_info_id data field. */
write_resource(
&image_info[image_info_id].data, id<MTLBuffer>(image_info_id_map[image_info_id]), 0);
}
}
}
}
bool MetalDeviceQueue::enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args)
{
@autoreleasepool {
update_capture(kernel);
if (metal_device_->have_error()) {
return false;
}
debug_enqueue_begin(kernel, work_size);
LOG_TRACE << "Metal queue launch " << device_kernel_as_string(kernel) << ", work_size "
<< work_size;
id<MTLComputeCommandEncoder> mtlComputeCommandEncoder = get_compute_encoder(kernel);
if (profiling_enabled_) {
command_encoder_labels_.push_back({kernel, work_size, current_encoder_idx_});
}
if (label_command_encoders_) {
/* Add human-readable labels if we're doing any form of debugging / profiling. */
mtlComputeCommandEncoder.label = [NSString
stringWithFormat:@"Metal queue launch %s, work_size %d",
device_kernel_as_string(kernel),
work_size];
}
if (!active_pipelines_[kernel].update(metal_device_, kernel)) {
metal_device_->set_error(
string_printf("Could not activate pipeline for %s\n", device_kernel_as_string(kernel)));
return false;
}
MetalDispatchPipeline &active_pipeline = active_pipelines_[kernel];
uint8_t dynamic_args[512] = {0};
/* Prepare the dynamic "enqueue" arguments */
size_t dynamic_bytes_written = 0;
size_t max_size_in_bytes = 0;
for (size_t i = 0; i < args.count; i++) {
size_t size_in_bytes = args.sizes[i];
max_size_in_bytes = max(max_size_in_bytes, size_in_bytes);
dynamic_bytes_written = round_up(dynamic_bytes_written, size_in_bytes);
memcpy(dynamic_args + dynamic_bytes_written, args.values[i], size_in_bytes);
if (args.types[i] == DeviceKernelArguments::POINTER) {
patch_resource(dynamic_args + dynamic_bytes_written);
}
dynamic_bytes_written += size_in_bytes;
}
/* Apply conventional struct alignment (stops asserts firing when API validation is enabled).
*/
dynamic_bytes_written = round_up(dynamic_bytes_written, max_size_in_bytes);
/* Check that the dynamic args didn't overflow. */
assert(dynamic_bytes_written <= sizeof(dynamic_args));
uint64_t ancillary_args[ANCILLARY_SLOT_COUNT] = {0};
/* Encode ancillaries */
int ancillary_index = 0;
write_resource(ancillary_args, metal_device_->image_bindings, ancillary_index++);
if (metal_device_->use_metalrt) {
write_resource(ancillary_args, metal_device_->accel_struct, ancillary_index++);
write_resource(ancillary_args, metal_device_->blas_buffer, ancillary_index++);
/* Write the intersection function table. */
for (int table_idx = 0; table_idx < METALRT_TABLE_NUM; table_idx++) {
write_resource(
ancillary_args, active_pipeline.intersection_func_table[table_idx], ancillary_index++);
}
assert(ancillary_index == ANCILLARY_SLOT_COUNT);
}
[mtlComputeCommandEncoder setBytes:dynamic_args length:dynamic_bytes_written atIndex:0];
[mtlComputeCommandEncoder setBuffer:metal_device_->launch_params_buffer offset:0 atIndex:1];
[mtlComputeCommandEncoder setBytes:ancillary_args length:sizeof(ancillary_args) atIndex:2];
/* Fallback path in case residency sets aren't supported:
* Call useResource for MetalRT resources not covered by prepare_resources(). */
if (!metal_device_->mtlResidencySet_enabled && metal_device_->use_metalrt &&
device_kernel_has_intersection(kernel))
{
if (@available(macos 12.0, *)) {
if (id<MTLAccelerationStructure> accel_struct = metal_device_->accel_struct) {
/* Mark all Accelerations resources as used */
[mtlComputeCommandEncoder useResource:accel_struct usage:MTLResourceUsageRead];
if (metal_device_->blas_buffer) {
[mtlComputeCommandEncoder useResource:metal_device_->blas_buffer
usage:MTLResourceUsageRead];
}
[mtlComputeCommandEncoder useResources:metal_device_->unique_blas_array.data()
count:metal_device_->unique_blas_array.size()
usage:MTLResourceUsageRead];
}
}
for (int table = 0; table < METALRT_TABLE_NUM; table++) {
if (active_pipeline.intersection_func_table[table]) {
[mtlComputeCommandEncoder useResource:active_pipeline.intersection_func_table[table]
usage:MTLResourceUsageRead];
}
}
}
[mtlComputeCommandEncoder setComputePipelineState:active_pipeline.pipeline];
/* Compute kernel launch parameters. */
const int num_threads_per_block = active_pipeline.num_threads_per_block;
int shared_mem_bytes = 0;
switch (kernel) {
case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_PATHS_ARRAY:
/* See parallel_active_index.h for why this amount of shared memory is needed.
* Rounded up to 16 bytes for Metal */
shared_mem_bytes = (int)round_up((num_threads_per_block + 1) * sizeof(int), 16);
break;
case DEVICE_KERNEL_INTEGRATOR_SORT_BUCKET_PASS:
case DEVICE_KERNEL_INTEGRATOR_SORT_WRITE_PASS: {
int key_count = metal_device_->launch_params->data.max_shaders;
shared_mem_bytes = (int)round_up(key_count * sizeof(int), 16);
break;
}
default:
break;
}
if (shared_mem_bytes) {
assert(shared_mem_bytes <= 32 * 1024);
[mtlComputeCommandEncoder setThreadgroupMemoryLength:shared_mem_bytes atIndex:0];
}
MTLSize size_threads_per_dispatch = MTLSizeMake(work_size, 1, 1);
MTLSize size_threads_per_threadgroup = MTLSizeMake(num_threads_per_block, 1, 1);
[mtlComputeCommandEncoder dispatchThreads:size_threads_per_dispatch
threadsPerThreadgroup:size_threads_per_threadgroup];
metal_device_->prepare_residency();
[mtlCommandBuffer_ addCompletedHandler:^(id<MTLCommandBuffer> command_buffer) {
/* Enhanced command buffer errors */
string str;
if (command_buffer.status != MTLCommandBufferStatusCompleted) {
str = string_printf("Command buffer not completed. status = %d. ",
int(command_buffer.status));
}
if (command_buffer.error) {
@autoreleasepool {
const char *errCStr = [[NSString stringWithFormat:@"%@", command_buffer.error]
UTF8String];
str += string_printf("(%s.%s):\n%s\n",
kernel_type_as_string(active_pipeline.pso_type),
device_kernel_as_string(kernel),
errCStr);
}
}
if (!str.empty()) {
metal_device_->set_error(str);
}
}];
if (verbose_tracing_ || is_capturing_) {
/* Force a sync we've enabled step-by-step verbose tracing or if we're capturing. */
synchronize();
/* Show queue counters and dispatch timing. */
if (verbose_tracing_) {
if (kernel == DEVICE_KERNEL_INTEGRATOR_RESET) {
printf(
"_____________________________________.____________________.______________._________"
"__"
"______________________________________\n");
}
printf("%-40s| %7d threads |%5.2fms | buckets [",
device_kernel_as_string(kernel),
work_size,
last_completion_time_ * 1000.0);
std::lock_guard<std::recursive_mutex> lock(metal_device_->metal_mem_map_mutex);
for (auto &it : metal_device_->metal_mem_map) {
const string c_integrator_queue_counter = "integrator_queue_counter";
if (it.first->global_name() == c_integrator_queue_counter) {
if (IntegratorQueueCounter *queue_counter = (IntegratorQueueCounter *)
it.first->host_pointer)
{
for (int i = 0; i < DEVICE_GPU_KERNEL_INTEGRATOR_NUM; i++) {
printf("%s%d", i == 0 ? "" : ",", queue_counter->num_queued[i]);
}
}
break;
}
}
printf("]\n");
}
}
debug_enqueue_end();
return !(metal_device_->have_error());
}
}
void MetalDeviceQueue::flush_timing_stats()
{
for (auto label : command_encoder_labels_) {
TimingStats &stat = timing_stats_[label.kernel];
double completion_time_gpu;
NSData *computeTimeStamps = [metal_device_->mtlCounterSampleBuffer
resolveCounterRange:NSMakeRange(label.timing_id, 2)];
MTLCounterResultTimestamp *timestamps = (MTLCounterResultTimestamp *)(computeTimeStamps.bytes);
uint64_t begTime = timestamps[0].timestamp;
uint64_t endTime = timestamps[1].timestamp;
completion_time_gpu = (endTime - begTime) / (double)NSEC_PER_SEC;
stat.num_dispatches++;
stat.total_time += completion_time_gpu;
stat.total_work_size += label.work_size;
last_completion_time_ = completion_time_gpu;
}
command_encoder_labels_.clear();
}
bool MetalDeviceQueue::synchronize()
{
@autoreleasepool {
if (has_captured_to_disk_ || metal_device_->have_error()) {
return false;
}
close_compute_encoder();
close_blit_encoder();
if (mtlCommandBuffer_) {
scoped_timer timer;
uint64_t shared_event_id_ = this->shared_event_id_++;
__block dispatch_semaphore_t block_sema = wait_semaphore_;
[shared_event_ notifyListener:shared_event_listener_
atValue:shared_event_id_
block:^(id<MTLSharedEvent> /*sharedEvent*/, uint64_t /*value*/) {
dispatch_semaphore_signal(block_sema);
}];
[mtlCommandBuffer_ encodeSignalEvent:shared_event_ value:shared_event_id_];
[mtlCommandBuffer_ commit];
dispatch_semaphore_wait(wait_semaphore_, DISPATCH_TIME_FOREVER);
[mtlCommandBuffer_ release];
metal_device_->flush_delayed_free_list();
mtlCommandBuffer_ = nil;
flush_timing_stats();
}
debug_synchronize();
return !(metal_device_->have_error());
}
}
void MetalDeviceQueue::zero_to_device(device_memory &mem)
{
@autoreleasepool {
if (metal_device_->have_error()) {
return;
}
assert(mem.type != MEM_IMAGE_TEXTURE);
if (mem.memory_size() == 0) {
return;
}
/* Allocate on demand. */
if (mem.device_pointer == 0) {
metal_device_->mem_alloc(mem);
}
/* Zero memory on device. */
assert(mem.device_pointer != 0);
std::lock_guard<std::recursive_mutex> lock(metal_device_->metal_mem_map_mutex);
MetalDevice::MetalMem &mmem = *metal_device_->metal_mem_map.at(&mem);
if (mmem.mtlBuffer) {
id<MTLBlitCommandEncoder> blitEncoder = get_blit_encoder();
[blitEncoder fillBuffer:mmem.mtlBuffer range:NSMakeRange(mmem.offset, mmem.size) value:0];
}
else {
metal_device_->mem_zero(mem);
}
}
}
void MetalDeviceQueue::copy_to_device(device_memory &mem)
{
@autoreleasepool {
if (metal_device_->have_error()) {
return;
}
if (mem.memory_size() == 0) {
return;
}
/* Allocate on demand. */
if (mem.device_pointer == 0) {
metal_device_->mem_alloc(mem);
}
assert(mem.device->mem_device_ptr(mem, metal_device_) != 0);
assert(mem.host_pointer != nullptr);
/* No need to copy - Apple Silicon has Unified Memory Architecture. */
}
}
void MetalDeviceQueue::copy_from_device(device_memory & /*mem*/)
{
/* No need to copy - Apple Silicon has Unified Memory Architecture. */
}
void *MetalDeviceQueue::copy_from_device_synchronized(device_memory &mem,
vector<uint8_t> & /*storage*/)
{
if (mem.memory_size() == 0) {
return nullptr;
}
/* Wait until kernels have finished before returning from unified memory. */
synchronize();
device_ptr d_ptr = mem.device->mem_device_ptr(mem, metal_device_);
return (d_ptr) ? reinterpret_cast<MetalDevice::MetalMem *>(d_ptr)->hostPtr : nullptr;
}
void MetalDeviceQueue::prepare_resources()
{
if (metal_device_->mtlResidencySet_enabled) {
/* All resources are already resident — skip per-encoder useResource calls. */
return;
}
std::lock_guard<std::recursive_mutex> lock(metal_device_->metal_mem_map_mutex);
/* declare resource usage */
for (auto &it : metal_device_->metal_mem_map) {
device_memory *mem = it.first;
MTLResourceUsage usage = MTLResourceUsageRead;
if (mem->type != MEM_GLOBAL && mem->type != MEM_READ_ONLY && mem->type != MEM_IMAGE_TEXTURE) {
usage |= MTLResourceUsageWrite;
}
if (it.second->mtlBuffer) {
/* METAL_WIP - use array version (i.e. useResources) */
[mtlComputeEncoder_ useResource:it.second->mtlBuffer usage:usage];
}
else if (it.second->mtlTexture) {
/* METAL_WIP - use array version (i.e. useResources) */
[mtlComputeEncoder_ useResource:it.second->mtlTexture usage:usage | MTLResourceUsageSample];
}
}
/* ancillaries */
[mtlComputeEncoder_ useResource:metal_device_->image_bindings usage:MTLResourceUsageRead];
}
id<MTLComputeCommandEncoder> MetalDeviceQueue::get_compute_encoder(DeviceKernel kernel)
{
bool concurrent = int(kernel) < int(DEVICE_GPU_KERNEL_INTEGRATOR_NUM);
if (profiling_enabled_) {
/* Close the current encoder to ensure we're able to capture per-encoder timing data. */
close_compute_encoder();
}
if (mtlComputeEncoder_) {
if (mtlComputeEncoder_.dispatchType == concurrent ? MTLDispatchTypeConcurrent :
MTLDispatchTypeSerial)
{
/* declare usage of MTLBuffers etc */
prepare_resources();
return mtlComputeEncoder_;
}
close_compute_encoder();
}
close_blit_encoder();
if (!mtlCommandBuffer_) {
mtlCommandBuffer_ = [mtlCommandQueue_ commandBuffer];
[mtlCommandBuffer_ retain];
}
if (profiling_enabled_) {
MTLComputePassDescriptor *desc = [[MTLComputePassDescriptor alloc] init];
current_encoder_idx_ = (counter_sample_buffer_curr_idx_.fetch_add(2) %
MAX_SAMPLE_BUFFER_LENGTH);
[desc.sampleBufferAttachments[0] setSampleBuffer:metal_device_->mtlCounterSampleBuffer];
[desc.sampleBufferAttachments[0] setStartOfEncoderSampleIndex:current_encoder_idx_];
[desc.sampleBufferAttachments[0] setEndOfEncoderSampleIndex:current_encoder_idx_ + 1];
[desc setDispatchType:concurrent ? MTLDispatchTypeConcurrent : MTLDispatchTypeSerial];
mtlComputeEncoder_ = [mtlCommandBuffer_ computeCommandEncoderWithDescriptor:desc];
}
else {
mtlComputeEncoder_ = [mtlCommandBuffer_
computeCommandEncoderWithDispatchType:concurrent ? MTLDispatchTypeConcurrent :
MTLDispatchTypeSerial];
}
[mtlComputeEncoder_ retain];
[mtlComputeEncoder_ setLabel:@(device_kernel_as_string(kernel))];
/* declare usage of MTLBuffers etc */
prepare_resources();
return mtlComputeEncoder_;
}
id<MTLBlitCommandEncoder> MetalDeviceQueue::get_blit_encoder()
{
if (mtlBlitEncoder_) {
return mtlBlitEncoder_;
}
close_compute_encoder();
if (!mtlCommandBuffer_) {
mtlCommandBuffer_ = [mtlCommandQueue_ commandBuffer];
[mtlCommandBuffer_ retain];
}
mtlBlitEncoder_ = [mtlCommandBuffer_ blitCommandEncoder];
[mtlBlitEncoder_ retain];
return mtlBlitEncoder_;
}
void MetalDeviceQueue::close_compute_encoder()
{
if (mtlComputeEncoder_) {
[mtlComputeEncoder_ endEncoding];
[mtlComputeEncoder_ release];
mtlComputeEncoder_ = nil;
}
}
void MetalDeviceQueue::close_blit_encoder()
{
if (mtlBlitEncoder_) {
[mtlBlitEncoder_ endEncoding];
[mtlBlitEncoder_ release];
mtlBlitEncoder_ = nil;
}
}
void *MetalDeviceQueue::native_queue()
{
return mtlCommandQueue_;
}
unique_ptr<DeviceGraphicsInterop> MetalDeviceQueue::graphics_interop_create()
{
return make_unique<MetalDeviceGraphicsInterop>(this);
}
CCL_NAMESPACE_END
#endif /* WITH_METAL */

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_METAL
# include <Metal/Metal.h>
# include <string>
# include "device/metal/device.h"
# include "device/metal/kernel.h"
# include "device/queue.h"
# include "util/thread.h"
# define metal_printf LOG_TRACE << string_printf
CCL_NAMESPACE_BEGIN
enum AppleGPUArchitecture {
APPLE_M1,
APPLE_M2,
APPLE_M2_BIG,
APPLE_M3,
/* Keep APPLE_UNKNOWN at the end of this enum to ensure that unknown future architectures get
* the most recent defaults when using comparison operators. */
APPLE_UNKNOWN,
};
/* Contains static Metal helper functions. */
struct MetalInfo {
static const vector<id<MTLDevice>> &get_usable_devices();
static int get_apple_gpu_core_count(id<MTLDevice> device);
static AppleGPUArchitecture get_apple_gpu_architecture(id<MTLDevice> device);
static int optimal_sort_partition_elements();
static string get_device_name(id<MTLDevice> device);
};
void metal_gpu_address_helper_init(id<MTLDevice> device);
uint64_t metal_gpuAddress(id<MTLBuffer> buffer);
uint64_t metal_gpuResourceID(id<MTLTexture> texture);
uint64_t metal_gpuResourceID(id<MTLAccelerationStructure> accel_struct);
uint64_t metal_gpuResourceID(id<MTLIntersectionFunctionTable> ift);
CCL_NAMESPACE_END
#endif /* WITH_METAL */

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_METAL
# include "device/metal/util.h"
# include "device/metal/device_impl.h"
# include "util/md5.h"
# include "util/path.h"
# include "util/string.h"
# include "util/time.h"
# include <IOKit/IOKitLib.h>
# include <ctime>
# include <pwd.h>
# include <sys/shm.h>
CCL_NAMESPACE_BEGIN
/* Comment this out to test workaround for getting gpuAddress and gpuResourceID on macOS < 13.0. */
# define CYCLES_USE_TIER2D_BINDLESS
string MetalInfo::get_device_name(id<MTLDevice> device)
{
string device_name = [device.name UTF8String];
/* Append the GPU core count so we can distinguish between GPU variants in benchmarks. */
int gpu_core_count = get_apple_gpu_core_count(device);
device_name += string_printf(gpu_core_count ? " (GPU - %d cores)" : " (GPU)", gpu_core_count);
return device_name;
}
int MetalInfo::get_apple_gpu_core_count(id<MTLDevice> device)
{
int core_count = 0;
if (@available(macos 12.0, *)) {
io_service_t gpu_service = IOServiceGetMatchingService(
kIOMainPortDefault, IORegistryEntryIDMatching(device.registryID));
if (CFNumberRef numberRef = (CFNumberRef)IORegistryEntryCreateCFProperty(
gpu_service, CFSTR("gpu-core-count"), nullptr, 0))
{
if (CFGetTypeID(numberRef) == CFNumberGetTypeID()) {
CFNumberGetValue(numberRef, kCFNumberSInt32Type, &core_count);
}
CFRelease(numberRef);
}
}
return core_count;
}
AppleGPUArchitecture MetalInfo::get_apple_gpu_architecture(id<MTLDevice> device)
{
const char *device_name = [device.name UTF8String];
if (strstr(device_name, "M1")) {
return APPLE_M1;
}
if (strstr(device_name, "M2")) {
return get_apple_gpu_core_count(device) <= 10 ? APPLE_M2 : APPLE_M2_BIG;
}
if (strstr(device_name, "M3")) {
return APPLE_M3;
}
return APPLE_UNKNOWN;
}
int MetalInfo::optimal_sort_partition_elements()
{
if (auto *str = getenv("CYCLES_METAL_SORT_PARTITION_ELEMENTS")) {
return atoi(str);
}
/* On M1 and M2 GPUs, we see better cache utilization if we partition the active indices before
* sorting each partition by material. Partitioning into chunks of 65536 elements results in an
* overall render time speedup of up to 15%. */
return 65536;
}
const vector<id<MTLDevice>> &MetalInfo::get_usable_devices()
{
static vector<id<MTLDevice>> usable_devices;
static bool already_enumerated = false;
if (already_enumerated) {
return usable_devices;
}
metal_printf("Usable Metal devices:");
for (id<MTLDevice> device in MTLCopyAllDevices()) {
string device_name = get_device_name(device);
bool usable = false;
if (@available(macos 12.2, *)) {
const char *device_name_char = [device.name UTF8String];
if (!(strstr(device_name_char, "Intel") || strstr(device_name_char, "AMD")) &&
strstr(device_name_char, "Apple"))
{
/* TODO: Implement a better way to identify device vendor instead of relying on name. */
/* We only support Apple Silicon GPUs which all have unified memory, but explicitly check
* just in case it ever changes. */
usable = [device hasUnifiedMemory];
}
}
if (usable) {
metal_printf("- %s", device_name.c_str());
[device retain];
usable_devices.push_back(device);
}
else {
metal_printf(" (skipping \"%s\")", device_name.c_str());
}
}
if (usable_devices.empty()) {
metal_printf(" No usable Metal devices found");
}
already_enumerated = true;
return usable_devices;
}
struct GPUAddressHelper {
id<MTLBuffer> resource_buffer = nil;
id<MTLArgumentEncoder> address_encoder = nil;
/* One time setup of arg encoder. */
void init(id<MTLDevice> device)
{
if (resource_buffer) {
/* No setup required - already initialised. */
return;
}
# ifdef CYCLES_USE_TIER2D_BINDLESS
if (@available(macos 13.0, *)) {
/* No setup required - there's an API now! */
return;
}
# endif
/* Setup a tiny buffer to encode the GPU address / resourceID into. */
resource_buffer = [device newBufferWithLength:8 options:MTLResourceStorageModeShared];
/* Create an encoder to extract a gpuAddress from a MTLBuffer. */
MTLArgumentDescriptor *encoder_params = [[MTLArgumentDescriptor alloc] init];
encoder_params.arrayLength = 1;
encoder_params.access = MTLBindingAccessReadWrite;
encoder_params.dataType = MTLDataTypePointer;
address_encoder = [device newArgumentEncoderWithArguments:@[ encoder_params ]];
[address_encoder setArgumentBuffer:resource_buffer offset:0];
};
uint64_t gpuAddress(id<MTLBuffer> buffer)
{
# ifdef CYCLES_USE_TIER2D_BINDLESS
if (@available(macos 13.0, *)) {
return buffer.gpuAddress;
}
# endif
[address_encoder setBuffer:buffer offset:0 atIndex:0];
return *(uint64_t *)[resource_buffer contents];
}
uint64_t gpuResourceID(id<MTLTexture> texture)
{
# ifdef CYCLES_USE_TIER2D_BINDLESS
if (@available(macos 13.0, *)) {
MTLResourceID resourceID = texture.gpuResourceID;
return (uint64_t &)resourceID;
}
# endif
[address_encoder setTexture:texture atIndex:0];
return *(uint64_t *)[resource_buffer contents];
}
uint64_t gpuResourceID(id<MTLAccelerationStructure> accel_struct)
{
# ifdef CYCLES_USE_TIER2D_BINDLESS
if (@available(macos 13.0, *)) {
MTLResourceID resourceID = accel_struct.gpuResourceID;
return (uint64_t &)resourceID;
}
# endif
[address_encoder setAccelerationStructure:accel_struct atIndex:0];
return *(uint64_t *)[resource_buffer contents];
}
uint64_t gpuResourceID(id<MTLIntersectionFunctionTable> ift)
{
# ifdef CYCLES_USE_TIER2D_BINDLESS
if (@available(macos 13.0, *)) {
MTLResourceID resourceID = ift.gpuResourceID;
return (uint64_t &)resourceID;
}
# endif
[address_encoder setIntersectionFunctionTable:ift atIndex:0];
return *(uint64_t *)[resource_buffer contents];
}
};
GPUAddressHelper g_gpu_address_helper;
void metal_gpu_address_helper_init(id<MTLDevice> device)
{
g_gpu_address_helper.init(device);
}
uint64_t metal_gpuAddress(id<MTLBuffer> buffer)
{
return g_gpu_address_helper.gpuAddress(buffer);
}
uint64_t metal_gpuResourceID(id<MTLTexture> texture)
{
return g_gpu_address_helper.gpuResourceID(texture);
}
uint64_t metal_gpuResourceID(id<MTLAccelerationStructure> accel_struct)
{
return g_gpu_address_helper.gpuResourceID(accel_struct);
}
uint64_t metal_gpuResourceID(id<MTLIntersectionFunctionTable> ift)
{
return g_gpu_address_helper.gpuResourceID(ift);
}
CCL_NAMESPACE_END
#endif /* WITH_METAL */

View File

@@ -0,0 +1,643 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/multi/device.h"
#include "device/device.h"
#include "device/queue.h"
#include <cstdlib>
#include <functional>
#include "bvh/multi.h"
#include "scene/geometry.h"
#include "util/list.h"
#include "util/map.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
class MultiDevice : public Device {
public:
struct SubDevice {
Stats stats;
unique_ptr<Device> device;
map<device_ptr, device_ptr> ptr_map;
int peer_island_index = -1;
};
list<SubDevice> devices;
device_ptr unique_key = 1;
vector<vector<SubDevice *>> peer_islands;
MultiDevice(const DeviceInfo &info_, Stats &stats, Profiler &profiler, bool headless)
: Device(info_, stats, profiler, headless)
{
verify_hardware_raytracing();
for (const DeviceInfo &subinfo : this->info.multi_devices) {
/* Always add CPU devices at the back since GPU devices can change
* host memory pointers, which CPU uses as device pointer. */
SubDevice *sub;
if (subinfo.type == DEVICE_CPU) {
devices.emplace_back();
sub = &devices.back();
}
else {
devices.emplace_front();
sub = &devices.front();
}
/* The pointer to 'sub->stats' will stay valid even after new devices
* are added, since 'devices' is a linked list. */
sub->device = Device::create(subinfo, sub->stats, profiler, headless);
}
/* Build a list of peer islands for the available render devices */
for (SubDevice &sub : devices) {
/* First ensure that every device is in at least once peer island */
if (sub.peer_island_index < 0) {
peer_islands.emplace_back();
sub.peer_island_index = (int)peer_islands.size() - 1;
peer_islands[sub.peer_island_index].push_back(&sub);
}
if (!info.has_peer_memory) {
continue;
}
/* Second check peer access between devices and fill up the islands accordingly */
for (SubDevice &peer_sub : devices) {
if (peer_sub.peer_island_index < 0 &&
peer_sub.device->info.type == sub.device->info.type &&
peer_sub.device->check_peer_access(sub.device.get()))
{
peer_sub.peer_island_index = sub.peer_island_index;
peer_islands[sub.peer_island_index].push_back(&peer_sub);
}
}
}
}
void verify_hardware_raytracing()
{
/* Determine if we can use hardware ray-tracing. It is only supported if all selected
* GPU devices support it. Both the backends and scene update code do not support mixed
* BVH2 and hardware raytracing. The CPU device will ignore this setting. */
bool have_disabled_hardware_rt = false;
bool have_enabled_hardware_rt = false;
for (const DeviceInfo &subinfo : info.multi_devices) {
if (subinfo.type != DEVICE_CPU) {
if (subinfo.use_hardware_raytracing) {
have_enabled_hardware_rt = true;
}
else {
have_disabled_hardware_rt = true;
}
}
}
info.use_hardware_raytracing = have_enabled_hardware_rt && !have_disabled_hardware_rt;
for (DeviceInfo &subinfo : info.multi_devices) {
if (subinfo.type != DEVICE_CPU) {
subinfo.use_hardware_raytracing = info.use_hardware_raytracing;
}
}
}
const string &error_message() override
{
error_msg.clear();
for (SubDevice &sub : devices) {
error_msg += sub.device->error_message();
}
return error_msg;
}
BVHLayoutMask get_bvh_layout_mask(const uint kernel_features) const override
{
BVHLayoutMask bvh_layout_mask = BVH_LAYOUT_ALL;
BVHLayoutMask bvh_layout_mask_all = BVH_LAYOUT_NONE;
for (const SubDevice &sub_device : devices) {
BVHLayoutMask device_bvh_layout_mask = sub_device.device->get_bvh_layout_mask(
kernel_features);
bvh_layout_mask &= device_bvh_layout_mask;
bvh_layout_mask_all |= device_bvh_layout_mask;
}
/* With multiple OptiX devices, every device needs its own acceleration structure */
if (bvh_layout_mask == BVH_LAYOUT_OPTIX) {
return BVH_LAYOUT_MULTI_OPTIX;
}
/* With multiple Metal devices, every device needs its own acceleration structure */
if (bvh_layout_mask == BVH_LAYOUT_METAL) {
return BVH_LAYOUT_MULTI_METAL;
}
if (bvh_layout_mask == BVH_LAYOUT_HIPRT) {
return BVH_LAYOUT_MULTI_HIPRT;
}
/* With multiple oneAPI devices, every device needs its own acceleration structure */
if (bvh_layout_mask == BVH_LAYOUT_EMBREEGPU) {
return BVH_LAYOUT_MULTI_EMBREEGPU;
}
/* When devices do not share a common BVH layout, fall back to creating one for each */
const BVHLayoutMask BVH_LAYOUT_OPTIX_EMBREE = (BVH_LAYOUT_OPTIX | BVH_LAYOUT_EMBREE);
if ((bvh_layout_mask_all & BVH_LAYOUT_OPTIX_EMBREE) == BVH_LAYOUT_OPTIX_EMBREE) {
return BVH_LAYOUT_MULTI_OPTIX_EMBREE;
}
const BVHLayoutMask BVH_LAYOUT_METAL_EMBREE = (BVH_LAYOUT_METAL | BVH_LAYOUT_EMBREE);
if ((bvh_layout_mask_all & BVH_LAYOUT_METAL_EMBREE) == BVH_LAYOUT_METAL_EMBREE) {
return BVH_LAYOUT_MULTI_METAL_EMBREE;
}
const BVHLayoutMask BVH_LAYOUT_EMBREEGPU_EMBREE = (BVH_LAYOUT_EMBREEGPU | BVH_LAYOUT_EMBREE);
if ((bvh_layout_mask_all & BVH_LAYOUT_EMBREEGPU_EMBREE) == BVH_LAYOUT_EMBREEGPU_EMBREE) {
return BVH_LAYOUT_MULTI_EMBREEGPU_EMBREE;
}
const BVHLayoutMask BVH_LAYOUT_HIPRT_EMBREE = (BVH_LAYOUT_HIPRT | BVH_LAYOUT_EMBREE);
if ((bvh_layout_mask_all & BVH_LAYOUT_HIPRT_EMBREE) == BVH_LAYOUT_HIPRT_EMBREE) {
return BVH_LAYOUT_MULTI_HIPRT_EMBREE;
}
return bvh_layout_mask;
}
bool load_kernels(const uint kernel_features) override
{
for (SubDevice &sub : devices) {
if (!sub.device->load_kernels(kernel_features)) {
return false;
}
}
return true;
}
bool load_osl_kernels() override
{
for (SubDevice &sub : devices) {
if (!sub.device->load_osl_kernels()) {
return false;
}
}
return true;
}
void build_bvh(BVH *bvh, Progress &progress, bool refit) override
{
/* Try to build and share a single acceleration structure, if possible */
if (bvh->params.bvh_layout == BVH_LAYOUT_BVH2 || bvh->params.bvh_layout == BVH_LAYOUT_EMBREE) {
devices.back().device->build_bvh(bvh, progress, refit);
return;
}
assert(bvh->params.bvh_layout == BVH_LAYOUT_MULTI_OPTIX ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_METAL ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_HIPRT ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_EMBREEGPU ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_OPTIX_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_METAL_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_HIPRT_EMBREE ||
bvh->params.bvh_layout == BVH_LAYOUT_MULTI_EMBREEGPU_EMBREE);
BVHMulti *const bvh_multi = static_cast<BVHMulti *>(bvh);
bvh_multi->sub_bvhs.resize(devices.size());
/* Temporarily move ownership of BVH on geometry to this vector, to swap
* it for each sub device. Need to find a better way to handle this. */
vector<unique_ptr<BVH>> geom_bvhs;
geom_bvhs.reserve(bvh->geometry.size());
for (Geometry *geom : bvh->geometry) {
geom_bvhs.push_back(std::move(geom->bvh));
}
/* Broadcast acceleration structure build to all render devices */
size_t i = 0;
for (SubDevice &sub : devices) {
/* Change geometry BVH pointers to the sub BVH */
for (size_t k = 0; k < bvh->geometry.size(); ++k) {
bvh->geometry[k]->bvh.release(); // NOLINT: was not actually the owner
bvh->geometry[k]->bvh.reset(
static_cast<BVHMulti *>(geom_bvhs[k].get())->sub_bvhs[i].get());
}
if (!bvh_multi->sub_bvhs[i]) {
BVHParams params = bvh->params;
if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_OPTIX) {
params.bvh_layout = BVH_LAYOUT_OPTIX;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_METAL) {
params.bvh_layout = BVH_LAYOUT_METAL;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_HIPRT) {
params.bvh_layout = BVH_LAYOUT_HIPRT;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_EMBREEGPU) {
params.bvh_layout = BVH_LAYOUT_EMBREEGPU;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_OPTIX_EMBREE) {
params.bvh_layout = sub.device->info.type == DEVICE_OPTIX ? BVH_LAYOUT_OPTIX :
BVH_LAYOUT_EMBREE;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_METAL_EMBREE) {
params.bvh_layout = sub.device->info.type == DEVICE_METAL ? BVH_LAYOUT_METAL :
BVH_LAYOUT_EMBREE;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_HIPRT_EMBREE) {
params.bvh_layout = sub.device->info.type == DEVICE_HIP ? BVH_LAYOUT_HIPRT :
BVH_LAYOUT_EMBREE;
}
else if (bvh->params.bvh_layout == BVH_LAYOUT_MULTI_EMBREEGPU_EMBREE) {
params.bvh_layout = sub.device->info.type == DEVICE_ONEAPI ? BVH_LAYOUT_EMBREEGPU :
BVH_LAYOUT_EMBREE;
}
/* Skip building a bottom level acceleration structure for non-instanced geometry on Embree
* (since they are put into the top level directly, see bvh_embree.cpp) */
if (!params.top_level && params.bvh_layout == BVH_LAYOUT_EMBREE &&
!bvh->geometry[0]->is_instanced())
{
i++;
continue;
}
bvh_multi->sub_bvhs[i] = BVH::create(
params, bvh->geometry, bvh->objects, sub.device.get());
}
sub.device->build_bvh(bvh_multi->sub_bvhs[i].get(), progress, refit);
i++;
}
/* Change BVH ownership back to Geometry. */
for (size_t k = 0; k < bvh->geometry.size(); ++k) {
bvh->geometry[k]->bvh.release(); // NOLINT: was not actually the owner
bvh->geometry[k]->bvh = std::move(geom_bvhs[k]);
}
}
OSLGlobals *get_cpu_osl_memory() override
{
/* Always return the OSL memory of the CPU device (this works since the constructor above
* guarantees that CPU devices are always added to the back). */
if (devices.size() > 1 && devices.back().device->info.type != DEVICE_CPU) {
return nullptr;
}
return devices.back().device->get_cpu_osl_memory();
}
device_ptr mem_device_ptr(const device_memory &mem, Device *sub_device) override
{
if (mem.device == sub_device) {
return mem.device_pointer;
}
device_ptr key = mem.device_pointer;
for (SubDevice &sub : devices) {
if (sub.device.get() == sub_device) {
auto it = sub.ptr_map.find(key);
return (it != sub.ptr_map.end()) ? it->second : device_ptr(0);
}
}
assert(!"MultiDevice::mem_device_ptr could not find sub_device");
return device_ptr(0);
}
void set_image_cache_func(KernelImageLoadRequestedCPU image_load_requested_cpu,
KernelImageLoadRequestedGPU image_load_requested_gpu) override
{
for (SubDevice &sub : devices) {
sub.device->set_image_cache_func(image_load_requested_cpu, image_load_requested_gpu);
}
}
bool is_resident(device_ptr key, Device *sub_device) override
{
for (SubDevice &sub : devices) {
if (sub.device.get() == sub_device) {
return find_matching_mem_device(key, sub)->device.get() == sub_device;
}
}
return false;
}
SubDevice *find_matching_mem_device(device_ptr key, SubDevice &sub)
{
assert(key != 0 && (sub.peer_island_index >= 0 || sub.ptr_map.find(key) != sub.ptr_map.end()));
/* Get the memory owner of this key (first try current device, then peer devices) */
SubDevice *owner_sub = &sub;
if (!owner_sub->ptr_map.contains(key)) {
for (SubDevice *island_sub : peer_islands[sub.peer_island_index]) {
if (island_sub != owner_sub && island_sub->ptr_map.contains(key)) {
owner_sub = island_sub;
}
}
}
return owner_sub;
}
SubDevice *find_suitable_mem_device(device_ptr key, const vector<SubDevice *> &island)
{
assert(!island.empty());
/* Get the memory owner of this key or the device with the lowest memory usage when new */
SubDevice *owner_sub = island.front();
for (SubDevice *island_sub : island) {
if (key ? (island_sub->ptr_map.contains(key)) :
(island_sub->device->stats.mem_used < owner_sub->device->stats.mem_used))
{
owner_sub = island_sub;
}
}
return owner_sub;
}
device_ptr find_matching_mem(device_ptr key, SubDevice &sub)
{
return find_matching_mem_device(key, sub)->ptr_map[key];
}
void *host_alloc(const MemoryType type, const size_t size) override
{
for (SubDevice &sub : devices) {
if (sub.device->info.type != DEVICE_CPU) {
return sub.device->host_alloc(type, size);
}
}
return Device::host_alloc(type, size);
}
void host_free(const MemoryType type, void *host_pointer, const size_t size) override
{
for (SubDevice &sub : devices) {
if (sub.device->info.type != DEVICE_CPU) {
sub.device->host_free(type, host_pointer, size);
return;
}
}
Device::host_free(type, host_pointer, size);
}
void mem_alloc(device_memory &mem) override
{
device_ptr key = unique_key++;
assert(mem.type == MEM_READ_ONLY || mem.type == MEM_READ_WRITE || mem.type == MEM_DEVICE_ONLY);
/* The remaining memory types can be distributed across devices */
for (const vector<SubDevice *> &island : peer_islands) {
SubDevice *owner_sub = find_suitable_mem_device(key, island);
mem.device = owner_sub->device.get();
mem.device_pointer = 0;
mem.device_size = 0;
owner_sub->device->mem_alloc(mem);
owner_sub->ptr_map[key] = mem.device_pointer;
}
mem.device = this;
mem.device_pointer = key;
stats.mem_alloc(mem.device_size);
}
void mem_copy_to(device_memory &mem) override
{
device_ptr existing_key = mem.device_pointer;
device_ptr key = (existing_key) ? existing_key : unique_key++;
size_t existing_size = mem.device_size;
for (const vector<SubDevice *> &island : peer_islands) {
SubDevice *owner_sub = find_suitable_mem_device(existing_key, island);
mem.device = owner_sub->device.get();
mem.device_pointer = (existing_key) ? owner_sub->ptr_map[existing_key] : 0;
mem.device_size = existing_size;
owner_sub->device->mem_copy_to(mem);
owner_sub->ptr_map[key] = mem.device_pointer;
if (mem.type == MEM_GLOBAL || mem.type == MEM_IMAGE_TEXTURE) {
/* Need to create texture objects and update pointer in kernel globals on all devices */
for (SubDevice *island_sub : island) {
if (island_sub != owner_sub) {
island_sub->device->mem_copy_to(mem);
}
}
}
}
mem.device = this;
mem.device_pointer = key;
stats.mem_alloc(mem.device_size - existing_size);
}
void mem_move_to_host(device_memory &mem) override
{
assert(mem.type == MEM_GLOBAL || mem.type == MEM_IMAGE_TEXTURE);
device_ptr existing_key = mem.device_pointer;
device_ptr key = (existing_key) ? existing_key : unique_key++;
size_t existing_size = mem.device_size;
for (const vector<SubDevice *> &island : peer_islands) {
SubDevice *owner_sub = find_suitable_mem_device(existing_key, island);
mem.device = owner_sub->device.get();
mem.device_pointer = (existing_key) ? owner_sub->ptr_map[existing_key] : 0;
mem.device_size = existing_size;
if (!owner_sub->device->is_shared(
mem.shared_pointer, mem.device_pointer, owner_sub->device.get()))
{
owner_sub->device->mem_move_to_host(mem);
owner_sub->ptr_map[key] = mem.device_pointer;
/* Need to create texture objects and update pointer in kernel globals on all devices */
for (SubDevice *island_sub : island) {
if (island_sub != owner_sub) {
island_sub->device->mem_move_to_host(mem);
}
}
}
}
mem.device = this;
mem.device_pointer = key;
stats.mem_alloc(mem.device_size - existing_size);
}
bool is_shared(const void *shared_pointer, const device_ptr key, Device *sub_device) override
{
if (key == 0) {
return false;
}
for (const SubDevice &sub : devices) {
if (sub.device.get() == sub_device) {
return sub_device->is_shared(shared_pointer, sub.ptr_map.at(key), sub_device);
}
}
assert(!"is_shared failed to find matching device");
return false;
}
void mem_or_from_device(device_memory &mem) override
{
device_ptr key = mem.device_pointer;
for (const vector<SubDevice *> &island : peer_islands) {
SubDevice *owner_sub = find_matching_mem_device(key, *island.front());
mem.device = owner_sub->device.get();
mem.device_pointer = owner_sub->ptr_map[key];
owner_sub->device->mem_or_from_device(mem);
}
mem.device = this;
mem.device_pointer = key;
}
void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) override
{
device_ptr key = mem.device_pointer;
const size_t sub_h = h / devices.size();
size_t i = 0;
for (SubDevice &sub : devices) {
size_t sy = y + i * sub_h;
size_t sh = (i == (size_t)devices.size() - 1) ? h - sub_h * i : sub_h;
SubDevice *owner_sub = find_matching_mem_device(key, sub);
mem.device = owner_sub->device.get();
mem.device_pointer = owner_sub->ptr_map[key];
owner_sub->device->mem_copy_from(mem, sy, w, sh, elem);
i++;
}
mem.device = this;
mem.device_pointer = key;
}
void mem_zero(device_memory &mem) override
{
device_ptr existing_key = mem.device_pointer;
device_ptr key = (existing_key) ? existing_key : unique_key++;
size_t existing_size = mem.device_size;
for (const vector<SubDevice *> &island : peer_islands) {
SubDevice *owner_sub = find_suitable_mem_device(existing_key, island);
mem.device = owner_sub->device.get();
mem.device_pointer = (existing_key) ? owner_sub->ptr_map[existing_key] : 0;
mem.device_size = existing_size;
owner_sub->device->mem_zero(mem);
owner_sub->ptr_map[key] = mem.device_pointer;
}
mem.device = this;
mem.device_pointer = key;
stats.mem_alloc(mem.device_size - existing_size);
}
void mem_free(device_memory &mem) override
{
device_ptr key = mem.device_pointer;
size_t existing_size = mem.device_size;
/* Free memory that was allocated for all devices (see above) on each device */
for (const vector<SubDevice *> &island : peer_islands) {
SubDevice *owner_sub = find_matching_mem_device(key, *island.front());
mem.device = owner_sub->device.get();
mem.device_pointer = owner_sub->ptr_map[key];
mem.device_size = existing_size;
owner_sub->device->mem_free(mem);
owner_sub->ptr_map.erase(owner_sub->ptr_map.find(key));
if (mem.type == MEM_IMAGE_TEXTURE) {
/* Free texture objects on all devices */
for (SubDevice *island_sub : island) {
if (island_sub != owner_sub) {
island_sub->device->mem_free(mem);
}
}
}
}
mem.device = this;
mem.device_pointer = 0;
mem.device_size = 0;
stats.mem_free(existing_size);
}
void const_copy_to(const char *name, void *host, const size_t size) override
{
for (SubDevice &sub : devices) {
sub.device->const_copy_to(name, host, size);
}
}
int device_number(Device *sub_device) override
{
int i = 0;
for (SubDevice &sub : devices) {
if (sub.device.get() == sub_device) {
return i;
}
i++;
}
return -1;
}
void foreach_device(const std::function<void(Device *)> &callback) override
{
for (SubDevice &sub : devices) {
sub.device->foreach_device(callback);
}
}
bool has_unified_memory() const override
{
for (const SubDevice &sub : devices) {
if (sub.device->has_unified_memory()) {
return true;
}
}
return false;
}
bool has_unified_image_memory() const override
{
for (const SubDevice &sub : devices) {
if (sub.device->has_unified_image_memory()) {
return true;
}
}
return false;
}
};
unique_ptr<Device> device_multi_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
return make_unique<MultiDevice>(info, stats, profiler, headless);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
unique_ptr<Device> device_multi_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
CCL_NAMESPACE_END

View File

@@ -0,0 +1,183 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/oneapi/device.h"
#include "device/device.h"
#include "util/log.h"
#ifdef WITH_ONEAPI
# include "device/oneapi/device_impl.h"
# include "integrator/denoiser_oidn_gpu.h" // IWYU pragma: keep
# include "util/string.h"
# ifdef __linux__
# include <dlfcn.h>
# endif
#endif /* WITH_ONEAPI */
CCL_NAMESPACE_BEGIN
bool device_oneapi_init()
{
#if !defined(WITH_ONEAPI)
return false;
#else
/* NOTE(@nsirgien): we need to enable JIT cache from here and
* right now this cache policy is controlled by env. variables. */
/* NOTE(@xavierh-intel) we enable the use of copy engine, incl. for fill
* operations as it lowers the overhead from zeCommandListAppendMemoryFill
* when running paths_array kernels on Linux+A750.
* All these env variable can be set beforehand by end-users and
* will in that case -not- be overwritten. */
/* By default, enable only Level-Zero and if all devices are allowed, also CUDA and HIP.
* OpenCL backend isn't currently well supported. */
# ifdef _WIN32
if (getenv("SYCL_CACHE_PERSISTENT") == nullptr) {
_putenv_s("SYCL_CACHE_PERSISTENT", "1");
}
if (getenv("SYCL_CACHE_THRESHOLD") == nullptr) {
_putenv_s("SYCL_CACHE_THRESHOLD", "0");
}
if (getenv("ONEAPI_DEVICE_SELECTOR") == nullptr) {
if (getenv("CYCLES_ONEAPI_ALL_DEVICES") == nullptr) {
_putenv_s("ONEAPI_DEVICE_SELECTOR", "level_zero:*");
}
else {
_putenv_s("ONEAPI_DEVICE_SELECTOR", "!opencl:*");
}
}
/* SYSMAN is needed for free_memory queries. */
if (getenv("ZES_ENABLE_SYSMAN") == nullptr) {
_putenv_s("ZES_ENABLE_SYSMAN", "1");
}
if (getenv("UR_L0_USE_COPY_ENGINE") == nullptr) {
_putenv_s("UR_L0_USE_COPY_ENGINE", "1");
}
if (getenv("UR_L0_USE_COPY_ENGINE_FOR_FILL") == nullptr) {
_putenv_s("UR_L0_USE_COPY_ENGINE_FOR_FILL", "1");
}
# elif __linux__
setenv("SYCL_CACHE_PERSISTENT", "1", false);
setenv("SYCL_CACHE_THRESHOLD", "0", false);
if (getenv("CYCLES_ONEAPI_ALL_DEVICES") == nullptr) {
setenv("ONEAPI_DEVICE_SELECTOR", "level_zero:*", false);
}
else {
setenv("ONEAPI_DEVICE_SELECTOR", "!opencl:*", false);
}
/* SYSMAN is needed for free_memory queries. */
setenv("ZES_ENABLE_SYSMAN", "1", false);
setenv("UR_L0_USE_COPY_ENGINE", "1", false);
setenv("UR_L0_USE_COPY_ENGINE_FOR_FILL", "1", false);
# endif
return true;
#endif
}
unique_ptr<Device> device_oneapi_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
#ifdef WITH_ONEAPI
return make_unique<OneapiDevice>(info, stats, profiler, headless);
#else
(void)info;
(void)stats;
(void)profiler;
(void)headless;
LOG_FATAL << "Requested to create oneAPI device while not enabled for this build.";
return nullptr;
#endif
}
#ifdef WITH_ONEAPI
static void device_iterator_cb(const char *id,
const char *name,
const int num,
bool hwrt_support,
bool oidn_support,
bool has_execution_optimization,
void *user_ptr)
{
vector<DeviceInfo> *devices = (vector<DeviceInfo> *)user_ptr;
DeviceInfo info;
info.type = DEVICE_ONEAPI;
info.description = name;
info.num = num;
/* NOTE(@nsirgien): Should be unique at least on proper oneapi installation. */
info.id = id;
info.has_nanovdb = true;
# if defined(WITH_OPENIMAGEDENOISE)
# if OIDN_VERSION >= 20300
if (oidn_support) {
# else
if (OIDNDenoiserGPU::is_device_supported(info)) {
# endif
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
}
# endif
(void)oidn_support;
info.has_gpu_queue = true;
/* NOTE(@nsirgien): oneAPI right now is focused on one device usage. In future it maybe will
* change, but right now peer access from one device to another device is not supported. */
info.has_peer_memory = false;
/* NOTE(@nsirgien): Seems not possible to know from SYCL/oneAPI or Level0. */
info.display_device = false;
# ifdef WITH_EMBREE_GPU
info.use_hardware_raytracing = hwrt_support;
# else
info.use_hardware_raytracing = false;
(void)hwrt_support;
# endif
info.has_execution_optimization = has_execution_optimization;
devices->push_back(info);
LOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE) {
LOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}
}
#endif
void device_oneapi_info(vector<DeviceInfo> &devices)
{
#ifdef WITH_ONEAPI
OneapiDevice::iterate_devices(device_iterator_cb, &devices);
#else /* WITH_ONEAPI */
(void)devices;
#endif /* WITH_ONEAPI */
}
string device_oneapi_capabilities()
{
string capabilities;
#ifdef WITH_ONEAPI
char *c_capabilities = OneapiDevice::device_capabilities();
if (c_capabilities) {
capabilities = c_capabilities;
free(c_capabilities);
}
#endif
return capabilities;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/string.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
bool device_oneapi_init();
unique_ptr<Device> device_oneapi_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
void device_oneapi_info(vector<DeviceInfo> &devices);
string device_oneapi_capabilities();
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
/* SPDX-FileCopyrightText: 2021-2025 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_ONEAPI
# include "device/device.h"
# include "device/oneapi/device.h"
# include "device/oneapi/queue.h"
# include "kernel/device/oneapi/kernel.h"
# include "util/map.h"
# include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
class DeviceQueue;
using OneAPIDeviceIteratorCallback =
void (*)(const char *, const char *, const int, bool, bool, bool, void *);
class OneapiDevice : public GPUDevice {
private:
SyclQueue *device_queue_ = nullptr;
# ifdef WITH_EMBREE_GPU
RTCDevice embree_device = nullptr;
# if RTC_VERSION >= 40400
RTCTraversable embree_traversable = nullptr;
# else
RTCScene embree_traversable = nullptr;
# endif
# if RTC_VERSION >= 40302
thread_mutex scene_data_mutex;
vector<RTCScene> all_embree_scenes;
# endif
# endif
using ConstMemMap = map<string, unique_ptr<device_vector<uchar>>>;
ConstMemMap const_mem_map_;
void *kg_memory_ = nullptr;
void *kg_memory_device_ = nullptr;
size_t kg_memory_size_ = 0;
size_t max_memory_on_device_ = 0;
std::string oneapi_error_string_;
bool use_hardware_raytracing = false;
unsigned int kernel_features = 0;
int scene_max_shaders_ = 0;
/* On some driver versions, usage of Intel oneAPI extension for host->GPU copy optimization
* is causing crashes and failures. As a result, to ensure functionality, we disable
* this extension as a workaround. This class member variable is controlling this behavior
* and is set appropriately during device enumeration, based on the presented devices
* and their driver version (at the moment not checked, no public driver with a fix exists yet).
*/
bool use_intel_copy_optimization = true;
size_t get_free_mem() const;
public:
BVHLayoutMask get_bvh_layout_mask(const uint requested_features) const override;
OneapiDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless);
~OneapiDevice() override;
# ifdef WITH_EMBREE_GPU
void build_bvh(BVH *bvh, Progress &progress, bool refit) override;
# endif
bool check_peer_access(Device *peer_device) override;
bool load_kernels(const uint requested_features) override;
void reserve_private_memory(const uint kernel_features);
string oneapi_error_message();
int scene_max_shaders();
void *kernel_globals_device_pointer();
/* All memory types. */
void mem_alloc(device_memory &mem) override;
void mem_copy_to(device_memory &mem) override;
void mem_move_to_host(device_memory &mem) override;
void mem_copy_from(device_memory &mem,
const size_t y,
size_t w,
const size_t h,
size_t elem,
void *host_pointer);
void mem_copy_from(
device_memory &mem, const size_t y, size_t w, const size_t h, size_t elem) override;
void mem_copy_from(device_memory &mem);
void mem_zero(device_memory &mem) override;
void mem_free(device_memory &mem) override;
device_ptr mem_alloc_sub_ptr(device_memory &mem, const size_t offset, size_t /*size*/) override;
/* Global memory. */
void global_alloc(device_memory &mem);
void global_copy_to(device_memory &mem);
void global_free(device_memory &mem);
/* Image memory. */
void image_alloc(device_image &mem);
void image_copy_to(device_image &mem);
void image_free(device_image &mem);
/* Host side memory, override for more efficient copies. */
void *host_alloc(const MemoryType type, const size_t size) override;
void host_free(const MemoryType type, void *host_pointer, const size_t size) override;
/* Device side memory. */
void get_device_memory_info(size_t &total, size_t &free) override;
bool alloc_device(void *&device_pointer, const size_t size) override;
void free_device(void *device_pointer) override;
/* Shared memory. */
bool shared_alloc(void *&shared_pointer, const size_t size) override;
void shared_free(void *shared_pointer) override;
void *shared_to_device_pointer(const void *shared_pointer) override;
/* Memory copy. */
void copy_host_to_device(void *device_pointer, void *host_pointer, const size_t size) override;
void const_copy_to(const char *name, void *host, const size_t size) override;
/* Graphics resources interoperability. */
bool should_use_graphics_interop(const GraphicsInteropDevice &interop_device,
const bool log) override;
unique_ptr<DeviceQueue> gpu_queue_create() override;
/* NOTE(@nsirgien): Create this methods to avoid some compilation problems on Windows with host
* side compilation (MSVC). */
void *usm_aligned_alloc_host(const size_t memory_size, const size_t alignment);
void usm_free(void *usm_ptr);
static void architecture_information(const SyclDevice *device, string &name, bool &is_optimized);
static char *device_capabilities();
static void iterate_devices(OneAPIDeviceIteratorCallback cb, void *user_ptr);
size_t get_memcapacity();
int get_num_multiprocessors();
int get_max_num_threads_per_multiprocessor();
bool queue_synchronize(SyclQueue *queue);
bool kernel_globals_size(size_t &kernel_global_size);
void set_global_memory(SyclQueue *queue,
void *kernel_globals,
const char *memory_name,
void *memory_device_pointer);
bool enqueue_kernel(KernelContext *kernel_context,
const int kernel,
const size_t global_size,
const size_t local_size,
void **args);
void get_adjusted_global_and_local_sizes(SyclQueue *queue,
const DeviceKernel kernel,
size_t &kernel_global_size,
size_t &kernel_local_size);
SyclQueue *sycl_queue();
protected:
bool can_use_hardware_raytracing_for_features(const uint requested_features) const;
void check_usm(SyclQueue *queue, const void *usm_ptr, bool allow_host);
bool create_queue(SyclQueue *&external_queue,
const int device_index,
void *embree_device,
bool *multiple_level_zero_platforms_detected_pointer);
void free_queue(SyclQueue *queue);
void *usm_aligned_alloc_host(SyclQueue *queue, const size_t memory_size, const size_t alignment);
void *usm_alloc_device(SyclQueue *queue, const size_t memory_size);
void usm_free(SyclQueue *queue, void *usm_ptr);
bool usm_memcpy(SyclQueue *queue, void *dest, void *src, const size_t num_bytes);
bool usm_memset(SyclQueue *queue, void *usm_ptr, unsigned char value, const size_t num_bytes);
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,168 @@
/* SPDX-FileCopyrightText: 2025 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#if defined(WITH_ONEAPI) && defined(SYCL_LINEAR_MEMORY_INTEROP_AVAILABLE)
# include "device/oneapi/graphics_interop.h"
# include "device/oneapi/device.h"
# include "device/oneapi/device_impl.h"
# include "device/oneapi/queue.h"
# include "session/display_driver.h"
# ifdef _WIN32
# include "util/windows.h"
# else
# include <unistd.h>
# endif
CCL_NAMESPACE_BEGIN
OneapiDeviceGraphicsInterop::OneapiDeviceGraphicsInterop(OneapiDeviceQueue *queue)
: queue_(queue), device_(static_cast<OneapiDevice *>(queue->device))
{
}
OneapiDeviceGraphicsInterop::~OneapiDeviceGraphicsInterop()
{
free();
}
void OneapiDeviceGraphicsInterop::set_buffer(GraphicsInteropBuffer &interop_buffer)
{
if (interop_buffer.is_empty()) {
free();
return;
}
need_zero_ |= interop_buffer.take_zero();
if (!interop_buffer.has_new_handle()) {
return;
}
free();
if (interop_buffer.get_type() != GraphicsInteropDevice::VULKAN) {
/* SYCL only supports interop with Vulkan and D3D. */
LOG_ERROR
<< "oneAPI interop set_buffer called for invalid graphics API. Only Vulkan is supported.";
return;
}
# ifdef _WIN32
/* import_external_memory will not take ownership of the handle. */
vulkan_windows_handle_ = reinterpret_cast<void *>(interop_buffer.take_handle());
auto sycl_mem_handle_type =
sycl::ext::oneapi::experimental::external_mem_handle_type::win32_nt_handle;
sycl::ext::oneapi::experimental::external_mem_descriptor<
sycl::ext::oneapi::experimental::resource_win32_handle>
sycl_external_mem_descriptor{{vulkan_windows_handle_}, sycl_mem_handle_type};
# else
/* import_external_memory will take ownership of the file descriptor. */
auto sycl_mem_handle_type = sycl::ext::oneapi::experimental::external_mem_handle_type::opaque_fd;
sycl::ext::oneapi::experimental::external_mem_descriptor<
sycl::ext::oneapi::experimental::resource_fd>
sycl_external_mem_descriptor{{static_cast<int>(interop_buffer.take_handle())},
sycl_mem_handle_type};
# endif
sycl::queue *sycl_queue = reinterpret_cast<sycl::queue *>(device_->sycl_queue());
try {
sycl_external_memory_ = sycl::ext::oneapi::experimental::import_external_memory(
sycl_external_mem_descriptor, *sycl_queue);
}
catch (sycl::exception &e) {
# ifdef _WIN32
CloseHandle(HANDLE(vulkan_windows_handle_));
vulkan_windows_handle_ = nullptr;
# else
close(sycl_external_mem_descriptor.external_resource.file_descriptor);
# endif
LOG_ERROR << "Error importing Vulkan memory: " << e.what();
return;
}
buffer_size_ = interop_buffer.get_size();
/* Like the CUDA/HIP backend, we map the buffer persistently. */
try {
sycl_memory_ptr_ = sycl::ext::oneapi::experimental::map_external_linear_memory(
sycl_external_memory_, 0, buffer_size_, *sycl_queue);
}
catch (sycl::exception &e) {
try {
sycl::ext::oneapi::experimental::release_external_memory(sycl_external_memory_, *sycl_queue);
}
catch (sycl::exception &e) {
LOG_ERROR << "Could not release external Vulkan memory: " << e.what();
}
sycl_external_memory_ = {};
buffer_size_ = 0;
/* Only need to close Windows handle, as file descriptor is owned by compute API. */
# ifdef _WIN32
CloseHandle(HANDLE(vulkan_windows_handle_));
vulkan_windows_handle_ = nullptr;
# endif
LOG_ERROR << "Error mapping external Vulkan memory: " << e.what();
return;
}
}
device_ptr OneapiDeviceGraphicsInterop::map()
{
if (sycl_memory_ptr_ && need_zero_) {
try {
/* We do not wait on the returned event here, as CUDA also uses "cuMemsetD8Async". */
sycl::queue *sycl_queue = reinterpret_cast<sycl::queue *>(device_->sycl_queue());
sycl_queue->memset(sycl_memory_ptr_, 0, buffer_size_);
}
catch (sycl::exception &e) {
LOG_ERROR << "Error clearing external Vulkan memory: " << e.what();
return device_ptr(0);
}
need_zero_ = false;
}
return reinterpret_cast<device_ptr>(sycl_memory_ptr_);
}
void OneapiDeviceGraphicsInterop::unmap() {}
void OneapiDeviceGraphicsInterop::free()
{
if (sycl_external_memory_.raw_handle) {
sycl::queue *sycl_queue = reinterpret_cast<sycl::queue *>(device_->sycl_queue());
try {
sycl::ext::oneapi::experimental::unmap_external_linear_memory(sycl_memory_ptr_, *sycl_queue);
}
catch (sycl::exception &e) {
LOG_ERROR << "Could not unmap external Vulkan memory: " << e.what();
}
try {
sycl::ext::oneapi::experimental::release_external_memory(sycl_external_memory_, *sycl_queue);
}
catch (sycl::exception &e) {
LOG_ERROR << "Could not release external Vulkan memory: " << e.what();
}
sycl_memory_ptr_ = {};
sycl_external_memory_ = {};
}
# ifdef _WIN32
if (vulkan_windows_handle_) {
CloseHandle(HANDLE(vulkan_windows_handle_));
vulkan_windows_handle_ = nullptr;
}
# endif
buffer_size_ = 0;
need_zero_ = false;
}
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2025 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#if defined(WITH_ONEAPI) && defined(SYCL_LINEAR_MEMORY_INTEROP_AVAILABLE)
# include <sycl/sycl.hpp>
# include "device/graphics_interop.h"
# include "session/display_driver.h"
# include "device/oneapi/device.h"
# include "device/oneapi/queue.h"
CCL_NAMESPACE_BEGIN
class OneapiDevice;
class OneapiDeviceQueue;
class OneapiDeviceGraphicsInterop : public DeviceGraphicsInterop {
public:
explicit OneapiDeviceGraphicsInterop(OneapiDeviceQueue *queue);
OneapiDeviceGraphicsInterop(const OneapiDeviceGraphicsInterop &other) = delete;
OneapiDeviceGraphicsInterop(OneapiDeviceGraphicsInterop &&other) noexcept = delete;
~OneapiDeviceGraphicsInterop() override;
OneapiDeviceGraphicsInterop &operator=(const OneapiDeviceGraphicsInterop &other) = delete;
OneapiDeviceGraphicsInterop &operator=(OneapiDeviceGraphicsInterop &&other) = delete;
void set_buffer(GraphicsInteropBuffer &interop_buffer) override;
device_ptr map() override;
void unmap() override;
protected:
OneapiDeviceQueue *queue_ = nullptr;
OneapiDevice *device_ = nullptr;
/* Size of the buffer in bytes. */
size_t buffer_size_ = 0;
/* The destination was requested to be cleared. */
bool need_zero_ = false;
/* OneAPI resources. */
sycl::ext::oneapi::experimental::external_mem sycl_external_memory_{};
void *sycl_memory_ptr_ = nullptr;
/* Vulkan handle to free. */
# ifdef _WIN32
void *vulkan_windows_handle_ = nullptr;
# endif
void free();
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2021-2025 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_ONEAPI
# include "device/oneapi/queue.h"
# include "device/oneapi/device_impl.h"
# include "device/oneapi/graphics_interop.h"
# include "util/log.h"
# include "kernel/device/oneapi/kernel.h"
CCL_NAMESPACE_BEGIN
struct KernelExecutionInfo {
double elapsed_summary = 0.0;
int enqueue_count = 0;
};
/* OneapiDeviceQueue */
OneapiDeviceQueue::OneapiDeviceQueue(OneapiDevice *device)
: DeviceQueue(device), oneapi_device_(device)
{
}
int OneapiDeviceQueue::num_concurrent_states(const size_t state_size) const
{
int num_states = 4 * num_concurrent_busy_states(state_size);
LOG_TRACE << "GPU queue concurrent states: " << num_states << ", using up to "
<< string_human_readable_size(num_states * state_size);
return num_states;
}
int OneapiDeviceQueue::num_concurrent_busy_states(const size_t /*state_size*/) const
{
const int max_num_threads = oneapi_device_->get_num_multiprocessors() *
oneapi_device_->get_max_num_threads_per_multiprocessor();
return 4 * max(8 * max_num_threads, 65536);
}
int OneapiDeviceQueue::num_sort_partitions(int max_num_paths, uint /*max_scene_shaders*/) const
{
int sort_partition_elements = (oneapi_device_->get_max_num_threads_per_multiprocessor() >= 128) ?
65536 :
8192;
/* Sort partitioning with local sorting on Intel GPUs is currently the most effective solution no
* matter the number of shaders. */
return max(max_num_paths / sort_partition_elements, 1);
}
void OneapiDeviceQueue::init_execution()
{
oneapi_device_->load_image_info(nullptr);
SyclQueue *device_queue = oneapi_device_->sycl_queue();
void *kg_dptr = oneapi_device_->kernel_globals_device_pointer();
assert(device_queue);
assert(kg_dptr);
kernel_context_ = make_unique<KernelContext>();
kernel_context_->queue = device_queue;
kernel_context_->kernel_globals = kg_dptr;
debug_init_execution();
}
void OneapiDeviceQueue::load_image_info()
{
oneapi_device_->load_image_info(this);
}
bool OneapiDeviceQueue::enqueue(DeviceKernel kernel,
const int signed_kernel_work_size,
const DeviceKernelArguments &_args)
{
if (oneapi_device_->have_error()) {
return false;
}
/* Update image info in case memory moved to host. */
if (oneapi_device_->load_image_info(nullptr)) {
if (!synchronize()) {
return false;
}
}
void **args = const_cast<void **>(_args.values);
debug_enqueue_begin(kernel, signed_kernel_work_size);
assert(signed_kernel_work_size >= 0);
size_t kernel_global_size = (size_t)signed_kernel_work_size;
size_t kernel_local_size;
assert(kernel_context_);
kernel_context_->scene_max_shaders = oneapi_device_->scene_max_shaders();
oneapi_device_->get_adjusted_global_and_local_sizes(
kernel_context_->queue, kernel, kernel_global_size, kernel_local_size);
/* Call the oneAPI kernel DLL to launch the requested kernel. */
bool is_finished_ok = oneapi_device_->enqueue_kernel(
kernel_context_.get(), kernel, kernel_global_size, kernel_local_size, args);
if (is_finished_ok == false) {
oneapi_device_->set_error("oneAPI kernel \"" + std::string(device_kernel_as_string(kernel)) +
"\" execution error: got runtime exception \"" +
oneapi_device_->oneapi_error_message() + "\"");
}
debug_enqueue_end();
return is_finished_ok;
}
bool OneapiDeviceQueue::synchronize()
{
if (oneapi_device_->have_error()) {
return false;
}
bool is_finished_ok = oneapi_device_->queue_synchronize(oneapi_device_->sycl_queue());
if (is_finished_ok == false) {
oneapi_device_->set_error("oneAPI unknown kernel execution error: got runtime exception \"" +
oneapi_device_->oneapi_error_message() + "\"");
}
debug_synchronize();
return !(oneapi_device_->have_error());
}
void OneapiDeviceQueue::zero_to_device(device_memory &mem)
{
oneapi_device_->mem_zero(mem);
}
void OneapiDeviceQueue::copy_to_device(device_memory &mem)
{
oneapi_device_->mem_copy_to(mem);
}
void OneapiDeviceQueue::copy_from_device(device_memory &mem)
{
oneapi_device_->mem_copy_from(mem);
}
void *OneapiDeviceQueue::copy_from_device_synchronized(device_memory &mem,
vector<uint8_t> &storage)
{
if (mem.memory_size() == 0) {
return nullptr;
}
storage.resize(mem.memory_size());
oneapi_device_->mem_copy_from(mem, 0, 0, 0, 0, storage.data());
synchronize();
return storage.data();
}
# ifdef SYCL_LINEAR_MEMORY_INTEROP_AVAILABLE
unique_ptr<DeviceGraphicsInterop> OneapiDeviceQueue::graphics_interop_create()
{
return make_unique<OneapiDeviceGraphicsInterop>(this);
}
# endif
CCL_NAMESPACE_END
#endif /* WITH_ONEAPI */

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2021-2025 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_ONEAPI
# include "device/memory.h"
# include "device/queue.h"
# include "kernel/device/oneapi/kernel.h"
# include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
class OneapiDevice;
class device_memory;
/* Base class for OneAPI queues. */
class OneapiDeviceQueue : public DeviceQueue {
public:
explicit OneapiDeviceQueue(OneapiDevice *device);
int num_concurrent_states(const size_t state_size) const override;
int num_concurrent_busy_states(const size_t state_size) const override;
int num_sort_partitions(int max_num_paths, uint max_scene_shaders) const override;
void init_execution() override;
void load_image_info() override;
bool enqueue(DeviceKernel kernel,
const int kernel_work_size,
const DeviceKernelArguments &args) override;
bool synchronize() override;
void zero_to_device(device_memory &mem) override;
void copy_to_device(device_memory &mem) override;
void copy_from_device(device_memory &mem) override;
void *copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage) override;
bool supports_local_atomic_sort() const override
{
return true;
}
# ifdef SYCL_LINEAR_MEMORY_INTEROP_AVAILABLE
unique_ptr<DeviceGraphicsInterop> graphics_interop_create() override;
# endif
protected:
OneapiDevice *oneapi_device_;
unique_ptr<KernelContext> kernel_context_;
};
CCL_NAMESPACE_END
#endif /* WITH_ONEAPI */

View File

@@ -0,0 +1,123 @@
/* SPDX-FileCopyrightText: 2019 NVIDIA Corporation
* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/optix/device.h"
#include "device/cuda/device.h"
#include "device/device.h"
#ifdef WITH_OSL
# include <OSL/oslconfig.h>
# include <OSL/oslversion.h>
#endif
#ifdef WITH_OPTIX
# include "device/optix/device_impl.h"
# include "integrator/denoiser_oidn_gpu.h" // IWYU pragma: keep
# include <optix_function_table_definition.h>
#endif
#include "util/log.h"
#ifndef OPTIX_FUNCTION_TABLE_SYMBOL
# define OPTIX_FUNCTION_TABLE_SYMBOL g_optixFunctionTable
#endif
CCL_NAMESPACE_BEGIN
bool device_optix_init()
{
#ifdef WITH_OPTIX
if (OPTIX_FUNCTION_TABLE_SYMBOL.optixDeviceContextCreate != nullptr) {
/* Already initialized function table. */
return true;
}
/* Need to initialize CUDA as well. */
if (!device_cuda_init()) {
return false;
}
const OptixResult result = optixInit();
if (result == OPTIX_ERROR_UNSUPPORTED_ABI_VERSION) {
LOG_WARNING << "OptiX initialization failed because the installed NVIDIA driver is too old. "
"Please update to the latest driver first!";
return false;
}
if (result != OPTIX_SUCCESS) {
LOG_WARNING << "OptiX initialization failed with error code " << (unsigned int)result;
return false;
}
/* Loaded OptiX successfully! */
return true;
#else
return false;
#endif
}
void device_optix_info(const vector<DeviceInfo> &cuda_devices, vector<DeviceInfo> &devices)
{
#ifdef WITH_OPTIX
devices.reserve(cuda_devices.size());
/* Simply add all supported CUDA devices as OptiX devices again. */
for (DeviceInfo info : cuda_devices) {
assert(info.type == DEVICE_CUDA);
int major;
cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, info.num);
if (major < 5) {
/* Only Maxwell and up are supported by OptiX. */
continue;
}
info.type = DEVICE_OPTIX;
info.id += "_OptiX";
# if defined(WITH_OSL) && defined(OSL_USE_OPTIX) && \
(OSL_VERSION_MINOR >= 13 || OSL_VERSION_MAJOR > 1)
info.has_osl = true;
# endif
info.denoisers |= DENOISER_OPTIX;
# if defined(WITH_OPENIMAGEDENOISE)
# if OIDN_VERSION >= 20300
if (oidnIsCUDADeviceSupported(info.num)) {
# else
if (OIDNDenoiserGPU::is_device_supported(info)) {
# endif
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
}
# endif
devices.push_back(info);
}
#else
(void)cuda_devices;
(void)devices;
#endif
}
unique_ptr<Device> device_optix_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless)
{
#ifdef WITH_OPTIX
return make_unique<OptiXDevice>(info, stats, profiler, headless);
#else
(void)info;
(void)stats;
(void)profiler;
(void)headless;
LOG_FATAL << "Request to create OptiX device without compiled-in support. Should never happen.";
return nullptr;
#endif
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Device;
class DeviceInfo;
class Profiler;
class Stats;
bool device_optix_init();
unique_ptr<Device> device_optix_create(const DeviceInfo &info,
Stats &stats,
Profiler &profiler,
bool headless);
void device_optix_info(const vector<DeviceInfo> &cuda_devices, vector<DeviceInfo> &devices);
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,164 @@
/* SPDX-FileCopyrightText: 2019 NVIDIA Corporation
* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_OPTIX
# include "device/cuda/device_impl.h"
# include "device/optix/util.h" // IWYU pragma: keep
# include "kernel/osl/globals.h"
# include "util/task.h"
CCL_NAMESPACE_BEGIN
class BVHOptiX;
struct KernelParamsOptiX;
/* List of OptiX program groups. */
enum {
/* Ray generation */
PG_RGEN_INTERSECT_CLOSEST,
PG_RGEN_INTERSECT_SHADOW,
PG_RGEN_INTERSECT_SUBSURFACE,
PG_RGEN_INTERSECT_VOLUME_STACK,
PG_RGEN_INTERSECT_DEDICATED_LIGHT,
PG_RGEN_INTERSECT_MNEE,
PG_RGEN_SHADE_BACKGROUND,
PG_RGEN_SHADE_LIGHT_NEE,
PG_RGEN_SHADE_LIGHT_FORWARD,
PG_RGEN_SHADE_SURFACE,
PG_RGEN_SHADE_SURFACE_RAYTRACE,
PG_RGEN_SHADE_VOLUME,
PG_RGEN_SHADE_VOLUME_RAY_MARCHING,
PG_RGEN_SHADE_SHADOW,
PG_RGEN_SHADE_DEDICATED_LIGHT,
PG_RGEN_EVAL_DISPLACE,
PG_RGEN_EVAL_BACKGROUND,
PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY,
PG_RGEN_INIT_FROM_CAMERA,
PG_RGEN_EVAL_VOLUME_DENSITY,
/* Miss */
PG_MISS,
/* Hit */
PG_HITD, /* Default hit group. */
PG_HITS, /* __SHADOW_RECORD_ALL__ hit group. */
PG_HITL, /* __BVH_LOCAL__ hit group (only used for triangles). */
PG_HITV, /* __VOLUME__ hit group. */
PG_HITD_MOTION,
PG_HITS_MOTION,
PG_HITL_MOTION,
PG_HITV_MOTION,
PG_HITD_CURVE_LINEAR,
PG_HITS_CURVE_LINEAR,
PG_HITV_CURVE_LINEAR,
PG_HITL_CURVE_LINEAR,
PG_HITD_CURVE_LINEAR_MOTION,
PG_HITS_CURVE_LINEAR_MOTION,
PG_HITV_CURVE_LINEAR_MOTION,
PG_HITL_CURVE_LINEAR_MOTION,
PG_HITD_CURVE_RIBBON,
PG_HITS_CURVE_RIBBON,
PG_HITV_CURVE_RIBBON,
PG_HITL_CURVE_RIBBON,
PG_HITD_POINTCLOUD,
PG_HITS_POINTCLOUD,
PG_HITV_POINTCLOUD,
PG_HITL_POINTCLOUD,
/* Callable */
PG_CALL_SVM_AO,
PG_CALL_SVM_BEVEL,
NUM_PROGRAM_GROUPS
};
static const int MISS_PROGRAM_GROUP_OFFSET = PG_MISS;
static const int NUM_MISS_PROGRAM_GROUPS = 1;
static const int HIT_PROGAM_GROUP_OFFSET = PG_HITD;
static const int NUM_HIT_PROGRAM_GROUPS = 24;
static const int CALLABLE_PROGRAM_GROUPS_BASE = PG_CALL_SVM_AO;
static const int NUM_CALLABLE_PROGRAM_GROUPS = 2;
/* List of OptiX pipelines. */
enum { PIP_SHADE, PIP_INTERSECT, NUM_PIPELINES };
/* A single shader binding table entry. */
struct SbtRecord {
char header[OPTIX_SBT_RECORD_HEADER_SIZE];
};
class OptiXDevice : public CUDADevice {
public:
OptixDeviceContext context = nullptr;
OptixModule optix_module = nullptr;
OptixModule mnee_module = nullptr;
OptixModule shader_raytrace_module = nullptr;
OptixModule builtin_modules[4] = {};
OptixPipeline pipelines[NUM_PIPELINES] = {};
OptixProgramGroup groups[NUM_PROGRAM_GROUPS] = {};
OptixPipelineCompileOptions pipeline_options = {};
# ifdef WITH_OSL
OSLGlobals osl_globals;
vector<OptixModule> osl_modules;
vector<OptixProgramGroup> osl_groups;
OptixModule osl_camera_module = nullptr;
OptixModule osl_volume_module = nullptr;
device_vector<uint8_t> osl_colorsystem;
# endif
device_vector<SbtRecord> sbt_data;
device_only_memory<KernelParamsOptiX> launch_params;
private:
OptixTraversableHandle tlas_handle = 0;
vector<unique_ptr<device_only_memory<char>>> delayed_free_bvh_memory;
thread_mutex delayed_free_bvh_mutex;
public:
OptiXDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless);
~OptiXDevice() override;
BVHLayoutMask get_bvh_layout_mask(uint /*kernel_features*/) const override;
string compile_kernel_get_common_cflags(const uint kernel_features);
void create_optix_module(TaskPool &pool,
OptixModuleCompileOptions &module_options,
string &ptx_data,
OptixModule &module,
OptixResult &failure_reason);
bool load_kernels(const uint kernel_features) override;
bool load_osl_kernels() override;
bool build_optix_bvh(BVHOptiX *bvh,
OptixBuildOperation operation,
const OptixBuildInput &build_input,
uint16_t num_motion_steps);
void build_bvh(BVH *bvh, Progress &progress, bool refit) override;
void release_bvh(BVH *bvh) override;
void free_bvh_memory_delayed();
void const_copy_to(const char *name, void *host, const size_t size) override;
void update_launch_params(const size_t offset, void *data, const size_t data_size);
unique_ptr<DeviceQueue> gpu_queue_create() override;
OSLGlobals *get_cpu_osl_memory() override;
};
CCL_NAMESPACE_END
#endif /* WITH_OPTIX */

View File

@@ -0,0 +1,231 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_OPTIX
# include "device/optix/queue.h"
# include "device/optix/device_impl.h"
# define __KERNEL_OPTIX__
# include "kernel/device/optix/globals.h"
CCL_NAMESPACE_BEGIN
/* CUDADeviceQueue */
OptiXDeviceQueue::OptiXDeviceQueue(OptiXDevice *device) : CUDADeviceQueue(device) {}
void OptiXDeviceQueue::init_execution()
{
CUDADeviceQueue::init_execution();
}
static bool is_optix_specific_kernel(DeviceKernel kernel, bool osl_shading, bool osl_camera)
{
# ifdef WITH_OSL
/* OSL uses direct callables to execute, so shading needs to be done in OptiX if OSL is used. */
if (osl_shading && device_kernel_has_shading(kernel)) {
return true;
}
if (osl_camera && kernel == DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA) {
return true;
}
# else
(void)osl_shading;
(void)osl_camera;
# endif
return device_kernel_has_intersection(kernel);
}
bool OptiXDeviceQueue::enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args)
{
OptiXDevice *const optix_device = static_cast<OptiXDevice *>(cuda_device_);
# ifdef WITH_OSL
const OSLGlobals *og = static_cast<const OSLGlobals *>(optix_device->get_cpu_osl_memory());
const bool osl_shading = og->use_shading;
const bool osl_camera = og->use_camera;
# else
const bool osl_shading = false;
const bool osl_camera = false;
# endif
if (!is_optix_specific_kernel(kernel, osl_shading, osl_camera)) {
return CUDADeviceQueue::enqueue(kernel, work_size, args);
}
if (cuda_device_->have_error()) {
return false;
}
debug_enqueue_begin(kernel, work_size);
const CUDAContextScope scope(cuda_device_);
const device_ptr sbt_data_ptr = optix_device->sbt_data.device_pointer;
const device_ptr launch_params_ptr = optix_device->launch_params.device_pointer;
auto set_launch_param = [&](size_t offset, size_t size, int arg) {
cuda_device_assert(
cuda_device_,
cuMemcpyHtoDAsync(launch_params_ptr + offset, args.values[arg], size, cuda_stream_));
};
set_launch_param(offsetof(KernelParamsOptiX, path_index_array), sizeof(device_ptr), 0);
if (kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST || device_kernel_has_shading(kernel)) {
set_launch_param(offsetof(KernelParamsOptiX, render_buffer), sizeof(device_ptr), 1);
}
if (kernel == DEVICE_KERNEL_SHADER_EVAL_DISPLACE ||
kernel == DEVICE_KERNEL_SHADER_EVAL_BACKGROUND ||
kernel == DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY ||
kernel == DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY)
{
set_launch_param(offsetof(KernelParamsOptiX, shader_eval_cache_miss), sizeof(device_ptr), 2);
set_launch_param(offsetof(KernelParamsOptiX, shader_eval_offset), sizeof(int32_t), 3);
}
if (kernel == DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA) {
set_launch_param(offsetof(KernelParamsOptiX, num_tiles), sizeof(int32_t), 1);
set_launch_param(offsetof(KernelParamsOptiX, render_buffer), sizeof(device_ptr), 2);
set_launch_param(offsetof(KernelParamsOptiX, max_tile_work_size), sizeof(int32_t), 3);
}
cuda_device_assert(cuda_device_, cuStreamSynchronize(cuda_stream_));
OptixPipeline pipeline = nullptr;
OptixShaderBindingTable sbt_params = {};
switch (kernel) {
case DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_BACKGROUND * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_LIGHT_NEE * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_LIGHT_FORWARD * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SURFACE * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SURFACE_RAYTRACE * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_MNEE * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_VOLUME * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME_RAY_MARCHING:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr +
PG_RGEN_SHADE_VOLUME_RAY_MARCHING * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SHADOW * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_DEDICATED_LIGHT * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST:
pipeline = optix_device->pipelines[PIP_INTERSECT];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_CLOSEST * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW:
pipeline = optix_device->pipelines[PIP_INTERSECT];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_SHADOW * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE:
pipeline = optix_device->pipelines[PIP_INTERSECT];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_SUBSURFACE * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK:
pipeline = optix_device->pipelines[PIP_INTERSECT];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_VOLUME_STACK * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT:
pipeline = optix_device->pipelines[PIP_INTERSECT];
sbt_params.raygenRecord = sbt_data_ptr +
PG_RGEN_INTERSECT_DEDICATED_LIGHT * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_SHADER_EVAL_DISPLACE:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_EVAL_DISPLACE * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_SHADER_EVAL_BACKGROUND:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_EVAL_BACKGROUND * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr +
PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_EVAL_VOLUME_DENSITY * sizeof(SbtRecord);
break;
case DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA:
pipeline = optix_device->pipelines[PIP_SHADE];
sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INIT_FROM_CAMERA * sizeof(SbtRecord);
break;
default:
LOG_ERROR << "Invalid kernel " << device_kernel_as_string(kernel)
<< " is attempted to be enqueued.";
return false;
}
sbt_params.missRecordBase = sbt_data_ptr + MISS_PROGRAM_GROUP_OFFSET * sizeof(SbtRecord);
sbt_params.missRecordStrideInBytes = sizeof(SbtRecord);
sbt_params.missRecordCount = NUM_MISS_PROGRAM_GROUPS;
sbt_params.hitgroupRecordBase = sbt_data_ptr + HIT_PROGAM_GROUP_OFFSET * sizeof(SbtRecord);
sbt_params.hitgroupRecordStrideInBytes = sizeof(SbtRecord);
sbt_params.hitgroupRecordCount = NUM_HIT_PROGRAM_GROUPS;
sbt_params.callablesRecordBase = sbt_data_ptr + CALLABLE_PROGRAM_GROUPS_BASE * sizeof(SbtRecord);
sbt_params.callablesRecordCount = NUM_CALLABLE_PROGRAM_GROUPS;
sbt_params.callablesRecordStrideInBytes = sizeof(SbtRecord);
# ifdef WITH_OSL
if (osl_shading || osl_camera) {
sbt_params.callablesRecordCount += static_cast<unsigned int>(optix_device->osl_groups.size());
}
# endif
/* Launch the ray generation program. */
optix_device_assert(optix_device,
optixLaunch(pipeline,
cuda_stream_,
launch_params_ptr,
optix_device->launch_params.data_elements,
&sbt_params,
work_size,
1,
1));
debug_enqueue_end();
return !(optix_device->have_error());
}
CCL_NAMESPACE_END
#endif /* WITH_OPTIX */

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_OPTIX
# include "device/cuda/queue.h"
CCL_NAMESPACE_BEGIN
class OptiXDevice;
/* Base class for CUDA queues. */
class OptiXDeviceQueue : public CUDADeviceQueue {
public:
OptiXDeviceQueue(OptiXDevice *device);
void init_execution() override;
bool enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args) override;
};
CCL_NAMESPACE_END
#endif /* WITH_OPTIX */

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_OPTIX
# include "device/cuda/util.h"
# ifdef WITH_CUDA_DYNLOAD
# include <cuew.h> // IWYU pragma: export
// Do not use CUDA SDK headers when using CUEW
# define OPTIX_DONT_INCLUDE_CUDA
# endif
# include <optix_stubs.h> // IWYU pragma: export
/* Utility for checking return values of OptiX function calls. */
# define optix_device_assert(optix_device, stmt) \
{ \
OptixResult result = stmt; \
if (result != OPTIX_SUCCESS) { \
const char *name = optixGetErrorName(result); \
optix_device->set_error( \
string_printf("%s in %s (%s:%d)", name, #stmt, __FILE__, __LINE__)); \
} \
} \
(void)0
# define optix_assert(stmt) optix_device_assert(this, stmt)
#endif /* WITH_OPTIX */

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include <iomanip>
#include "device/kernel.h"
#include "device/queue.h"
#include "util/algorithm.h"
#include "util/log.h"
#include "util/time.h"
CCL_NAMESPACE_BEGIN
DeviceQueue::DeviceQueue(Device *device) : device(device)
{
DCHECK_NE(device, nullptr);
is_per_kernel_performance_ = getenv("CYCLES_DEBUG_PER_KERNEL_PERFORMANCE");
}
DeviceQueue::~DeviceQueue()
{
if (LOG_IS_ON(LOG_LEVEL_TRACE)) {
/* Print kernel execution times sorted by time. */
vector<pair<DeviceKernelMask, double>> stats_sorted;
for (const auto &stat : stats_kernel_time_) {
stats_sorted.push_back(stat);
}
sort(stats_sorted.begin(),
stats_sorted.end(),
[](const pair<DeviceKernelMask, double> &a, const pair<DeviceKernelMask, double> &b) {
return a.second > b.second;
});
LOG_TRACE << "GPU queue stats:";
double total_time = 0.0;
for (const auto &[mask, time] : stats_sorted) {
total_time += time;
LOG_TRACE << " " << std::setfill(' ') << std::setw(10) << std::fixed << std::setprecision(5)
<< std::right << time << "s: " << device_kernel_mask_as_string(mask);
}
if (is_per_kernel_performance_) {
LOG_TRACE << "GPU queue total time: " << std::fixed << std::setprecision(5) << total_time;
}
}
}
void DeviceQueue::debug_init_execution()
{
if (LOG_IS_ON(LOG_LEVEL_TRACE)) {
last_sync_time_ = time_dt();
}
last_kernels_enqueued_.reset();
}
void DeviceQueue::debug_enqueue_begin(DeviceKernel kernel, const int work_size)
{
if (LOG_IS_ON(LOG_LEVEL_TRACE)) {
LOG_TRACE << "GPU queue launch " << device_kernel_as_string(kernel) << ", work_size "
<< work_size;
}
last_kernels_enqueued_.set(kernel, true);
}
void DeviceQueue::debug_enqueue_end()
{
if (LOG_IS_ON(LOG_LEVEL_TRACE) && is_per_kernel_performance_) {
synchronize();
}
}
void DeviceQueue::debug_synchronize()
{
if (LOG_IS_ON(LOG_LEVEL_TRACE)) {
const double new_time = time_dt();
const double elapsed_time = new_time - last_sync_time_;
LOG_TRACE << "GPU queue synchronize, elapsed " << std::setw(10) << elapsed_time << "s";
/* There is no sense to have an entries in the performance data
* container without related kernel information. */
if (last_kernels_enqueued_.any()) {
stats_kernel_time_[last_kernels_enqueued_] += elapsed_time;
}
last_sync_time_ = new_time;
}
last_kernels_enqueued_.reset();
}
string DeviceQueue::debug_active_kernels()
{
return device_kernel_mask_as_string(last_kernels_enqueued_);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,200 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "device/kernel.h"
#include "device/graphics_interop.h"
#include "util/log.h"
#include "util/map.h"
#include "util/string.h"
#include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
class Device;
class device_memory;
struct KernelWorkTile;
/* Container for device kernel arguments with type correctness ensured by API. */
struct DeviceKernelArguments {
enum Type {
POINTER,
INT32,
FLOAT32,
KERNEL_FILM_CONVERT,
HIPRT_GLOBAL_STACK,
};
static const int MAX_ARGS = 19;
Type types[MAX_ARGS];
void *values[MAX_ARGS];
size_t sizes[MAX_ARGS];
size_t count = 0;
DeviceKernelArguments() = default;
template<class T> DeviceKernelArguments(const T *arg)
{
add(arg);
}
template<class T, class... Args> DeviceKernelArguments(const T *first, Args... args)
{
add(first);
add(args...);
}
void add(const KernelFilmConvert *value)
{
add(KERNEL_FILM_CONVERT, value, sizeof(KernelFilmConvert));
}
void add(const device_ptr *value)
{
add(POINTER, value, sizeof(device_ptr));
}
void add(const int32_t *value)
{
add(INT32, value, sizeof(int32_t));
}
void add(const float *value)
{
add(FLOAT32, value, sizeof(float));
}
void add(const Type type, const void *value, const size_t size)
{
assert(count < MAX_ARGS);
types[count] = type;
values[count] = (void *)value;
sizes[count] = size;
count++;
}
template<typename T, typename... Args> void add(const T *first, Args... args)
{
add(first);
add(args...);
}
};
/* Abstraction of a command queue for a device.
* Provides API to schedule kernel execution in a specific queue with minimal possible overhead
* from driver side.
*
* This class encapsulates all properties needed for commands execution. */
class DeviceQueue {
public:
virtual ~DeviceQueue();
/* Number of concurrent states to process for integrator,
* based on number of cores and/or available memory. */
virtual int num_concurrent_states(const size_t state_size) const = 0;
/* Number of states which keeps the device occupied with work without losing performance.
* The renderer will add more work (when available) when number of active paths falls below this
* value. */
virtual int num_concurrent_busy_states(const size_t state_size) const = 0;
/* Number of partitions of sorted shaders, that improves memory locality of
* integrator state fetch at the cost of decreased coherence for shader kernel execution. */
virtual int num_sort_partitions(int max_num_paths, uint max_scene_shaders) const
{
/* Sort partitioning becomes less effective when more shaders are in the wavefront. In lieu of
* a more sophisticated heuristic we simply disable sort partitioning if the shader count is
* high.
*/
if (max_scene_shaders < 300) {
return max(max_num_paths / 65536, 1);
}
else {
return 1;
}
}
/* Does device support local atomic sorting kernels (INTEGRATOR_SORT_BUCKET_PASS and
* INTEGRATOR_SORT_WRITE_PASS)? */
virtual bool supports_local_atomic_sort() const
{
return false;
}
/* Initialize execution of kernels on this queue.
*
* Will, for example, load all data required by the kernels from Device to global or path state.
*
* Use this method after device synchronization has finished before enqueueing any kernels. */
virtual void init_execution() = 0;
/* Update device-specific image state after allocating device_image. */
virtual void load_image_info() = 0;
/* Enqueue kernel execution.
*
* Execute the kernel work_size times on the device.
* Supported arguments types:
* - int: pass pointer to the int
* - device memory: pass pointer to device_memory.device_pointer
* Return false if there was an error executing this or a previous kernel. */
virtual bool enqueue(DeviceKernel kernel,
const int work_size,
const DeviceKernelArguments &args) = 0;
/* Wait unit all enqueued kernels have finished execution.
* Return false if there was an error executing any of the enqueued kernels. */
virtual bool synchronize() = 0;
/* Copy memory to/from device as part of the command queue, to ensure
* operations are done in order without having to synchronize. */
virtual void zero_to_device(device_memory &mem) = 0;
virtual void copy_to_device(device_memory &mem) = 0;
virtual void copy_from_device(device_memory &mem) = 0;
virtual void *copy_from_device_synchronized(device_memory &mem, vector<uint8_t> &storage) = 0;
/* Graphics resources interoperability.
*
* The interoperability comes here by the meaning that the device is capable of computing result
* directly into an OpenGL (or other graphics library) buffer. */
/* Create graphics interoperability context which will be taking care of mapping graphics
* resource as a buffer writable by kernels of this device. */
virtual unique_ptr<DeviceGraphicsInterop> graphics_interop_create()
{
LOG_FATAL << "Request of GPU interop of a device which does not support it.";
return nullptr;
}
/* Device this queue has been created for. */
Device *device = nullptr;
virtual void *native_queue()
{
return nullptr;
}
protected:
/* Hide construction so that allocation via `Device` API is enforced. */
explicit DeviceQueue(Device *device);
/* Implementations call these from the corresponding methods to generate debugging logs. */
void debug_init_execution();
void debug_enqueue_begin(DeviceKernel kernel, const int work_size);
void debug_enqueue_end();
void debug_synchronize();
string debug_active_kernels();
/* Combination of kernels enqueued together sync last synchronize. */
DeviceKernelMask last_kernels_enqueued_ = {false};
/* Time of synchronize call. */
double last_sync_time_ = 0.0;
/* Accumulated execution time for combinations of kernels launched together. */
map<DeviceKernelMask, double> stats_kernel_time_;
/* If it is true, then a performance statistics in the debugging logs will have focus on kernels
* and an explicit queue synchronization will be added after each kernel execution. */
bool is_per_kernel_performance_ = false;
};
CCL_NAMESPACE_END