Add Chromium-only Blender WebEngine parity work
This commit is contained in:
253
blender-5.2.0/intern/cycles/device/CMakeLists.txt
Normal file
253
blender-5.2.0/intern/cycles/device/CMakeLists.txt
Normal 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})
|
||||
54
blender-5.2.0/intern/cycles/device/cpu/device.cpp
Normal file
54
blender-5.2.0/intern/cycles/device/cpu/device.cpp
Normal 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
|
||||
27
blender-5.2.0/intern/cycles/device/cpu/device.h
Normal file
27
blender-5.2.0/intern/cycles/device/cpu/device.h
Normal 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
|
||||
369
blender-5.2.0/intern/cycles/device/cpu/device_impl.cpp
Normal file
369
blender-5.2.0/intern/cycles/device/cpu/device_impl.cpp
Normal 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
|
||||
101
blender-5.2.0/intern/cycles/device/cpu/device_impl.h
Normal file
101
blender-5.2.0/intern/cycles/device/cpu/device_impl.h
Normal 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
|
||||
59
blender-5.2.0/intern/cycles/device/cpu/kernel.cpp
Normal file
59
blender-5.2.0/intern/cycles/device/cpu/kernel.cpp
Normal 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
|
||||
137
blender-5.2.0/intern/cycles/device/cpu/kernel.h
Normal file
137
blender-5.2.0/intern/cycles/device/cpu/kernel.h
Normal 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
|
||||
71
blender-5.2.0/intern/cycles/device/cpu/kernel_function.h
Normal file
71
blender-5.2.0/intern/cycles/device/cpu/kernel_function.h
Normal 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
|
||||
360
blender-5.2.0/intern/cycles/device/cuda/device.cpp
Normal file
360
blender-5.2.0/intern/cycles/device/cuda/device.cpp
Normal 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
|
||||
29
blender-5.2.0/intern/cycles/device/cuda/device.h
Normal file
29
blender-5.2.0/intern/cycles/device/cuda/device.h
Normal 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
|
||||
1099
blender-5.2.0/intern/cycles/device/cuda/device_impl.cpp
Normal file
1099
blender-5.2.0/intern/cycles/device/cuda/device_impl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
111
blender-5.2.0/intern/cycles/device/cuda/device_impl.h
Normal file
111
blender-5.2.0/intern/cycles/device/cuda/device_impl.h
Normal 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
|
||||
186
blender-5.2.0/intern/cycles/device/cuda/graphics_interop.cpp
Normal file
186
blender-5.2.0/intern/cycles/device/cuda/graphics_interop.cpp
Normal 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
|
||||
63
blender-5.2.0/intern/cycles/device/cuda/graphics_interop.h
Normal file
63
blender-5.2.0/intern/cycles/device/cuda/graphics_interop.h
Normal 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
|
||||
56
blender-5.2.0/intern/cycles/device/cuda/kernel.cpp
Normal file
56
blender-5.2.0/intern/cycles/device/cuda/kernel.cpp
Normal 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 */
|
||||
44
blender-5.2.0/intern/cycles/device/cuda/kernel.h
Normal file
44
blender-5.2.0/intern/cycles/device/cuda/kernel.h
Normal 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 */
|
||||
271
blender-5.2.0/intern/cycles/device/cuda/queue.cpp
Normal file
271
blender-5.2.0/intern/cycles/device/cuda/queue.cpp
Normal 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 */
|
||||
58
blender-5.2.0/intern/cycles/device/cuda/queue.h
Normal file
58
blender-5.2.0/intern/cycles/device/cuda/queue.h
Normal 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 */
|
||||
49
blender-5.2.0/intern/cycles/device/cuda/util.cpp
Normal file
49
blender-5.2.0/intern/cycles/device/cuda/util.cpp
Normal 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 */
|
||||
63
blender-5.2.0/intern/cycles/device/cuda/util.h
Normal file
63
blender-5.2.0/intern/cycles/device/cuda/util.h
Normal 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 */
|
||||
90
blender-5.2.0/intern/cycles/device/denoise.cpp
Normal file
90
blender-5.2.0/intern/cycles/device/denoise.cpp
Normal 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
|
||||
98
blender-5.2.0/intern/cycles/device/denoise.h
Normal file
98
blender-5.2.0/intern/cycles/device/denoise.h
Normal 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
|
||||
896
blender-5.2.0/intern/cycles/device/device.cpp
Normal file
896
blender-5.2.0/intern/cycles/device/device.cpp
Normal 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
|
||||
451
blender-5.2.0/intern/cycles/device/device.h
Normal file
451
blender-5.2.0/intern/cycles/device/device.h
Normal 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
|
||||
54
blender-5.2.0/intern/cycles/device/dummy/device.cpp
Normal file
54
blender-5.2.0/intern/cycles/device/dummy/device.cpp
Normal 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
|
||||
21
blender-5.2.0/intern/cycles/device/dummy/device.h
Normal file
21
blender-5.2.0/intern/cycles/device/dummy/device.h
Normal 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
|
||||
9
blender-5.2.0/intern/cycles/device/graphics_interop.cpp
Normal file
9
blender-5.2.0/intern/cycles/device/graphics_interop.cpp
Normal 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
|
||||
30
blender-5.2.0/intern/cycles/device/graphics_interop.h
Normal file
30
blender-5.2.0/intern/cycles/device/graphics_interop.h
Normal 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
|
||||
325
blender-5.2.0/intern/cycles/device/hip/device.cpp
Normal file
325
blender-5.2.0/intern/cycles/device/hip/device.cpp
Normal 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
|
||||
29
blender-5.2.0/intern/cycles/device/hip/device.h
Normal file
29
blender-5.2.0/intern/cycles/device/hip/device.h
Normal 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
|
||||
1027
blender-5.2.0/intern/cycles/device/hip/device_impl.cpp
Normal file
1027
blender-5.2.0/intern/cycles/device/hip/device_impl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
112
blender-5.2.0/intern/cycles/device/hip/device_impl.h
Normal file
112
blender-5.2.0/intern/cycles/device/hip/device_impl.h
Normal 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
|
||||
120
blender-5.2.0/intern/cycles/device/hip/graphics_interop.cpp
Normal file
120
blender-5.2.0/intern/cycles/device/hip/graphics_interop.cpp
Normal 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
|
||||
54
blender-5.2.0/intern/cycles/device/hip/graphics_interop.h
Normal file
54
blender-5.2.0/intern/cycles/device/hip/graphics_interop.h
Normal 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
|
||||
81
blender-5.2.0/intern/cycles/device/hip/kernel.cpp
Normal file
81
blender-5.2.0/intern/cycles/device/hip/kernel.cpp
Normal 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 */
|
||||
45
blender-5.2.0/intern/cycles/device/hip/kernel.h
Normal file
45
blender-5.2.0/intern/cycles/device/hip/kernel.h
Normal 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 */
|
||||
267
blender-5.2.0/intern/cycles/device/hip/queue.cpp
Normal file
267
blender-5.2.0/intern/cycles/device/hip/queue.cpp
Normal 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 */
|
||||
58
blender-5.2.0/intern/cycles/device/hip/queue.h
Normal file
58
blender-5.2.0/intern/cycles/device/hip/queue.h
Normal 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 */
|
||||
77
blender-5.2.0/intern/cycles/device/hip/util.cpp
Normal file
77
blender-5.2.0/intern/cycles/device/hip/util.cpp
Normal 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 */
|
||||
92
blender-5.2.0/intern/cycles/device/hip/util.h
Normal file
92
blender-5.2.0/intern/cycles/device/hip/util.h
Normal 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 */
|
||||
1317
blender-5.2.0/intern/cycles/device/hiprt/device_impl.cpp
Normal file
1317
blender-5.2.0/intern/cycles/device/hiprt/device_impl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
144
blender-5.2.0/intern/cycles/device/hiprt/device_impl.h
Normal file
144
blender-5.2.0/intern/cycles/device/hiprt/device_impl.h
Normal 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
|
||||
86
blender-5.2.0/intern/cycles/device/hiprt/queue.cpp
Normal file
86
blender-5.2.0/intern/cycles/device/hiprt/queue.cpp
Normal 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 */
|
||||
32
blender-5.2.0/intern/cycles/device/hiprt/queue.h
Normal file
32
blender-5.2.0/intern/cycles/device/hiprt/queue.h
Normal 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 */
|
||||
232
blender-5.2.0/intern/cycles/device/kernel.cpp
Normal file
232
blender-5.2.0/intern/cycles/device/kernel.cpp
Normal 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
|
||||
38
blender-5.2.0/intern/cycles/device/kernel.h
Normal file
38
blender-5.2.0/intern/cycles/device/kernel.h
Normal 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
|
||||
314
blender-5.2.0/intern/cycles/device/memory.cpp
Normal file
314
blender-5.2.0/intern/cycles/device/memory.cpp
Normal 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
|
||||
663
blender-5.2.0/intern/cycles/device/memory.h
Normal file
663
blender-5.2.0/intern/cycles/device/memory.h
Normal 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
|
||||
72
blender-5.2.0/intern/cycles/device/metal/bvh.h
Normal file
72
blender-5.2.0/intern/cycles/device/metal/bvh.h
Normal 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 ¶ms,
|
||||
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 */
|
||||
1416
blender-5.2.0/intern/cycles/device/metal/bvh.mm
Normal file
1416
blender-5.2.0/intern/cycles/device/metal/bvh.mm
Normal file
File diff suppressed because it is too large
Load Diff
30
blender-5.2.0/intern/cycles/device/metal/device.h
Normal file
30
blender-5.2.0/intern/cycles/device/metal/device.h
Normal 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
|
||||
155
blender-5.2.0/intern/cycles/device/metal/device.mm
Normal file
155
blender-5.2.0/intern/cycles/device/metal/device.mm
Normal 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
|
||||
217
blender-5.2.0/intern/cycles/device/metal/device_impl.h
Normal file
217
blender-5.2.0/intern/cycles/device/metal/device_impl.h
Normal 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
|
||||
1413
blender-5.2.0/intern/cycles/device/metal/device_impl.mm
Normal file
1413
blender-5.2.0/intern/cycles/device/metal/device_impl.mm
Normal file
File diff suppressed because it is too large
Load Diff
48
blender-5.2.0/intern/cycles/device/metal/graphics_interop.h
Normal file
48
blender-5.2.0/intern/cycles/device/metal/graphics_interop.h
Normal 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
|
||||
60
blender-5.2.0/intern/cycles/device/metal/graphics_interop.mm
Normal file
60
blender-5.2.0/intern/cycles/device/metal/graphics_interop.mm
Normal 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
|
||||
134
blender-5.2.0/intern/cycles/device/metal/kernel.h
Normal file
134
blender-5.2.0/intern/cycles/device/metal/kernel.h
Normal 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 */
|
||||
935
blender-5.2.0/intern/cycles/device/metal/kernel.mm
Normal file
935
blender-5.2.0/intern/cycles/device/metal/kernel.mm
Normal 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 */
|
||||
130
blender-5.2.0/intern/cycles/device/metal/queue.h
Normal file
130
blender-5.2.0/intern/cycles/device/metal/queue.h
Normal 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 */
|
||||
922
blender-5.2.0/intern/cycles/device/metal/queue.mm
Normal file
922
blender-5.2.0/intern/cycles/device/metal/queue.mm
Normal 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 */
|
||||
50
blender-5.2.0/intern/cycles/device/metal/util.h
Normal file
50
blender-5.2.0/intern/cycles/device/metal/util.h
Normal 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 */
|
||||
232
blender-5.2.0/intern/cycles/device/metal/util.mm
Normal file
232
blender-5.2.0/intern/cycles/device/metal/util.mm
Normal 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 */
|
||||
643
blender-5.2.0/intern/cycles/device/multi/device.cpp
Normal file
643
blender-5.2.0/intern/cycles/device/multi/device.cpp
Normal 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 = ⊂
|
||||
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
|
||||
21
blender-5.2.0/intern/cycles/device/multi/device.h
Normal file
21
blender-5.2.0/intern/cycles/device/multi/device.h
Normal 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
|
||||
183
blender-5.2.0/intern/cycles/device/oneapi/device.cpp
Normal file
183
blender-5.2.0/intern/cycles/device/oneapi/device.cpp
Normal 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
|
||||
29
blender-5.2.0/intern/cycles/device/oneapi/device.h
Normal file
29
blender-5.2.0/intern/cycles/device/oneapi/device.h
Normal 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
|
||||
1856
blender-5.2.0/intern/cycles/device/oneapi/device_impl.cpp
Normal file
1856
blender-5.2.0/intern/cycles/device/oneapi/device_impl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
175
blender-5.2.0/intern/cycles/device/oneapi/device_impl.h
Normal file
175
blender-5.2.0/intern/cycles/device/oneapi/device_impl.h
Normal 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
|
||||
168
blender-5.2.0/intern/cycles/device/oneapi/graphics_interop.cpp
Normal file
168
blender-5.2.0/intern/cycles/device/oneapi/graphics_interop.cpp
Normal 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
|
||||
61
blender-5.2.0/intern/cycles/device/oneapi/graphics_interop.h
Normal file
61
blender-5.2.0/intern/cycles/device/oneapi/graphics_interop.h
Normal 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
|
||||
173
blender-5.2.0/intern/cycles/device/oneapi/queue.cpp
Normal file
173
blender-5.2.0/intern/cycles/device/oneapi/queue.cpp
Normal 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 */
|
||||
62
blender-5.2.0/intern/cycles/device/oneapi/queue.h
Normal file
62
blender-5.2.0/intern/cycles/device/oneapi/queue.h
Normal 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 */
|
||||
123
blender-5.2.0/intern/cycles/device/optix/device.cpp
Normal file
123
blender-5.2.0/intern/cycles/device/optix/device.cpp
Normal 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
|
||||
26
blender-5.2.0/intern/cycles/device/optix/device.h
Normal file
26
blender-5.2.0/intern/cycles/device/optix/device.h
Normal 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
|
||||
1995
blender-5.2.0/intern/cycles/device/optix/device_impl.cpp
Normal file
1995
blender-5.2.0/intern/cycles/device/optix/device_impl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
164
blender-5.2.0/intern/cycles/device/optix/device_impl.h
Normal file
164
blender-5.2.0/intern/cycles/device/optix/device_impl.h
Normal 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 */
|
||||
231
blender-5.2.0/intern/cycles/device/optix/queue.cpp
Normal file
231
blender-5.2.0/intern/cycles/device/optix/queue.cpp
Normal 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 */
|
||||
29
blender-5.2.0/intern/cycles/device/optix/queue.h
Normal file
29
blender-5.2.0/intern/cycles/device/optix/queue.h
Normal 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 */
|
||||
33
blender-5.2.0/intern/cycles/device/optix/util.h
Normal file
33
blender-5.2.0/intern/cycles/device/optix/util.h
Normal 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 */
|
||||
101
blender-5.2.0/intern/cycles/device/queue.cpp
Normal file
101
blender-5.2.0/intern/cycles/device/queue.cpp
Normal 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
|
||||
200
blender-5.2.0/intern/cycles/device/queue.h
Normal file
200
blender-5.2.0/intern/cycles/device/queue.h
Normal 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
|
||||
Reference in New Issue
Block a user