diff options
Diffstat (limited to 'source/blender/gpu/metal/mtl_backend.mm')
-rw-r--r-- | source/blender/gpu/metal/mtl_backend.mm | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm new file mode 100644 index 00000000000..c4a35218158 --- /dev/null +++ b/source/blender/gpu/metal/mtl_backend.mm @@ -0,0 +1,404 @@ +/** \file + * \ingroup gpu + */ + +#include "BKE_global.h" + +#include "gpu_backend.hh" +#include "mtl_backend.hh" + +#include "gpu_capabilities_private.hh" +#include "gpu_platform_private.hh" + +#include <Cocoa/Cocoa.h> +#include <Metal/Metal.h> +#include <QuartzCore/QuartzCore.h> + +namespace blender { +namespace gpu { + +/* Global per-thread AutoReleasePool. */ +thread_local NSAutoreleasePool *g_autoreleasepool = nil; +thread_local int g_autoreleasepool_depth = 0; + +/* -------------------------------------------------------------------- */ +/** \name Metal Backend + * \{ */ + +void MTLBackend::samplers_update(){ + /* Placeholder -- Handled in MTLContext. */ +}; + +Context *MTLBackend::context_alloc(void *ghost_window) +{ + /* TODO(Metal): Implement MTLContext. */ + return nullptr; +}; + +Batch *MTLBackend::batch_alloc() +{ + /* TODO(Metal): Implement MTLBatch. */ + return nullptr; +}; + +DrawList *MTLBackend::drawlist_alloc(int list_length) +{ + /* TODO(Metal): Implement MTLDrawList. */ + return nullptr; +}; + +FrameBuffer *MTLBackend::framebuffer_alloc(const char *name) +{ + /* TODO(Metal): Implement MTLFrameBuffer. */ + return nullptr; +}; + +IndexBuf *MTLBackend::indexbuf_alloc() +{ + /* TODO(Metal): Implement MTLIndexBuf. */ + return nullptr; +}; + +QueryPool *MTLBackend::querypool_alloc() +{ + /* TODO(Metal): Implement MTLQueryPool. */ + return nullptr; +}; + +Shader *MTLBackend::shader_alloc(const char *name) +{ + /* TODO(Metal): Implement MTLShader. */ + return nullptr; +}; + +Texture *MTLBackend::texture_alloc(const char *name) +{ + /* TODO(Metal): Implement MTLTexture. */ + return nullptr; +} + +UniformBuf *MTLBackend::uniformbuf_alloc(int size, const char *name) +{ + /* TODO(Metal): Implement MTLUniformBuf. */ + return nullptr; +}; + +VertBuf *MTLBackend::vertbuf_alloc() +{ + /* TODO(Metal): Implement MTLVertBuf. */ + return nullptr; +} + +void MTLBackend::render_begin() +{ + /* All Rendering must occur within a render boundary */ + /* Track a call-count for nested calls, used to ensure we are inside an + * autoreleasepool from all rendering path. */ + BLI_assert(g_autoreleasepool_depth >= 0); + + if (g_autoreleasepool == nil) { + g_autoreleasepool = [[NSAutoreleasePool alloc] init]; + } + g_autoreleasepool_depth++; + BLI_assert(g_autoreleasepool_depth > 0); +} + +void MTLBackend::render_end() +{ + /* If call-count reaches zero, drain auto release pool. + * Esures temporary objects are freed within a frame's + * lifetime. */ + BLI_assert(g_autoreleasepool != nil); + g_autoreleasepool_depth--; + BLI_assert(g_autoreleasepool_depth >= 0); + + if (g_autoreleasepool_depth == 0) { + [g_autoreleasepool drain]; + g_autoreleasepool = nil; + } +} + +void MTLBackend::render_step() +{ + /* Placeholder */ +} + +bool MTLBackend::is_inside_render_boundary() +{ + return (g_autoreleasepool != nil); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Platform + * \{ */ + +/* For Metal, platform_init needs to be called after MTLContext initialisation. */ +void MTLBackend::platform_init(MTLContext *ctx) +{ + if (GPG.initialized) { + return; + } + + eGPUDeviceType device = GPU_DEVICE_UNKNOWN; + eGPUOSType os = GPU_OS_ANY; + eGPUDriverType driver = GPU_DRIVER_ANY; + eGPUSupportLevel support_level = GPU_SUPPORT_LEVEL_SUPPORTED; + + BLI_assert(ctx); + id<MTLDevice> mtl_device = nil; /*ctx->device; TODO(Metal): Implement MTLContext. */ + BLI_assert(device); + + NSString *gpu_name = [mtl_device name]; + const char *vendor = [gpu_name UTF8String]; + const char *renderer = "Metal API"; + const char *version = "1.2"; + printf("METAL API - DETECTED GPU: %s\n", vendor); + + /* macOS is the only supported platform, but check to ensure we are not building with Metal + * enablement on another platform. */ +#ifdef _WIN32 + os = GPU_OS_WIN; +#elif defined(__APPLE__) + os = GPU_OS_MAC; +#else + os = GPU_OS_UNIX; +#endif + + BLI_assert(os == GPU_OS_MAC && "Platform must be macOS"); + + /* Determine Vendor from name. */ + if (strstr(vendor, "ATI") || strstr(vendor, "AMD")) { + device = GPU_DEVICE_ATI; + driver = GPU_DRIVER_OFFICIAL; + } + else if (strstr(vendor, "NVIDIA")) { + device = GPU_DEVICE_NVIDIA; + driver = GPU_DRIVER_OFFICIAL; + } + else if (strstr(vendor, "Intel")) { + device = GPU_DEVICE_INTEL; + driver = GPU_DRIVER_OFFICIAL; + } + else if (strstr(vendor, "Apple") || strstr(vendor, "APPLE")) { + /* Apple Silicon. */ + device = GPU_DEVICE_APPLE; + driver = GPU_DRIVER_OFFICIAL; + } + else if (strstr(renderer, "Apple Software Renderer")) { + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; + } + else if (strstr(renderer, "llvmpipe") || strstr(renderer, "softpipe")) { + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; + } + else { + printf("Warning: Could not find a matching GPU name. Things may not behave as expected.\n"); + printf("Detected configuration:\n"); + printf("Vendor: %s\n", vendor); + printf("Renderer: %s\n", renderer); + } + + GPG.init(device, os, driver, support_level, GPU_BACKEND_METAL, vendor, renderer, version); +} + +void MTLBackend::platform_exit() +{ + BLI_assert(GPG.initialized); + GPG.clear(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Capabilities + * \{ */ +MTLCapabilities MTLBackend::capabilities = {}; + +static const char *mtl_extensions_get_null(int i) +{ + return nullptr; +} + +bool supports_barycentric_whitelist(id<MTLDevice> device) +{ + NSString *gpu_name = [device name]; + BLI_assert([gpu_name length]); + const char *vendor = [gpu_name UTF8String]; + + /* Verify GPU support. */ + bool supported_gpu = [device supportsFamily:MTLGPUFamilyMac2]; + bool should_support_barycentrics = false; + + /* Known good configs. */ + if (strstr(vendor, "AMD") || strstr(vendor, "Apple") || strstr(vendor, "APPLE")) { + should_support_barycentrics = true; + } + + /* Explicit support for Intel-based platforms. */ + if ((strstr(vendor, "Intel") || strstr(vendor, "INTEL"))) { + should_support_barycentrics = true; + } + return supported_gpu && should_support_barycentrics; +} + +bool MTLBackend::metal_is_supported() +{ + /* Device compatibility information using Metal Feature-set tables. + * See: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */ + + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + + /* Metal Viewport requires macOS Version 10.15 onwards. */ + bool supported_os_version = version.majorVersion >= 11 || + (version.majorVersion == 10 ? version.minorVersion >= 15 : false); + if (!supported_os_version) { + printf( + "OS Version too low to run minimum required metal version. Required at least 10.15, got " + "%ld.%ld \n", + (long)version.majorVersion, + (long)version.minorVersion); + return false; + } + + if (@available(macOS 10.15, *)) { + id<MTLDevice> device = MTLCreateSystemDefaultDevice(); + + /* Debug: Enable low power GPU with Environment Var: METAL_FORCE_INTEL. */ + static const char *forceIntelStr = getenv("METAL_FORCE_INTEL"); + bool forceIntel = forceIntelStr ? (atoi(forceIntelStr) != 0) : false; + + if (forceIntel) { + NSArray<id<MTLDevice>> *allDevices = MTLCopyAllDevices(); + for (id<MTLDevice> _device in allDevices) { + if (_device.lowPower) { + device = _device; + } + } + } + + /* Metal Viewport requires argument buffer tier-2 support and Barycentric Coordinates. + * These are available on most hardware configurations supporting Metal 2.2. */ + bool supports_argument_buffers_tier2 = ([device argumentBuffersSupport] == + MTLArgumentBuffersTier2); + bool supports_barycentrics = [device supportsShaderBarycentricCoordinates] || + supports_barycentric_whitelist(device); + bool supported_metal_version = [device supportsFamily:MTLGPUFamilyMac2]; + + bool result = supports_argument_buffers_tier2 && supports_barycentrics && + supported_os_version && supported_metal_version; + + if (!supports_argument_buffers_tier2) { + printf("[Metal] Device does not support argument buffers tier 2\n"); + } + if (!supports_barycentrics) { + printf("[Metal] Device does not support barycentrics coordinates\n"); + } + if (!supported_metal_version) { + printf("[Metal] Device does not support metal 2.2 or higher\n"); + } + + if (result) { + printf("Device with name %s supports metal minimum requirements\n", + [[device name] UTF8String]); + } + + return result; + } + return false; +} + +void MTLBackend::capabilities_init(MTLContext *ctx) +{ + BLI_assert(ctx); + id<MTLDevice> device = nil; /*ctx->device TODO(Metal): Implement MTLContext. */ + BLI_assert(device); + + /* Initialise Capabilities. */ + MTLBackend::capabilities.supports_argument_buffers_tier2 = ([device argumentBuffersSupport] == + MTLArgumentBuffersTier2); + MTLBackend::capabilities.supports_family_mac1 = [device supportsFamily:MTLGPUFamilyMac1]; + MTLBackend::capabilities.supports_family_mac2 = [device supportsFamily:MTLGPUFamilyMac2]; + MTLBackend::capabilities.supports_family_mac_catalyst1 = [device + supportsFamily:MTLGPUFamilyMacCatalyst1]; + MTLBackend::capabilities.supports_family_mac_catalyst2 = [device + supportsFamily:MTLGPUFamilyMacCatalyst2]; + + /* Common Global Capabilities. */ + GCaps.max_texture_size = ([device supportsFamily:MTLGPUFamilyApple3] || + MTLBackend::capabilities.supports_family_mac1) ? + 16384 : + 8192; + GCaps.max_texture_3d_size = 2048; + GCaps.max_texture_layers = 2048; + GCaps.max_textures = (MTLBackend::capabilities.supports_family_mac1) ? + 128 : + (([device supportsFamily:MTLGPUFamilyApple4]) ? 96 : 31); + if (GCaps.max_textures <= 32) { + BLI_assert(false); + } + GCaps.max_samplers = (MTLBackend::capabilities.supports_argument_buffers_tier2) ? 1024 : 16; + + GCaps.max_textures_vert = GCaps.max_textures; + GCaps.max_textures_geom = 0; /* N/A geometry shaders not supported. */ + GCaps.max_textures_frag = GCaps.max_textures; + + /* Conservative uniform data limit is 4KB per-stage -- This is the limit of setBytes. + * MTLBuffer path is also supported but not as efficient. */ + GCaps.max_uniforms_vert = 1024; + GCaps.max_uniforms_frag = 1024; + + GCaps.max_batch_indices = 1 << 31; + GCaps.max_batch_vertices = 1 << 31; + GCaps.max_vertex_attribs = 31; + GCaps.max_varying_floats = 60; + + /* Feature support */ + GCaps.mem_stats_support = false; + GCaps.shader_image_load_store_support = ([device supportsFamily:MTLGPUFamilyApple3] || + MTLBackend::capabilities.supports_family_mac1 || + MTLBackend::capabilities.supports_family_mac2); + GCaps.compute_shader_support = false; /* TODO(Metal): Add compute support. */ + GCaps.shader_storage_buffer_objects_support = + false; /* TODO(Metal): implement Storage Buffer support.*/ + + /* Maximum buffer bindings: 31. Consider required slot for uniforms/UBOs/Vertex attributes. + * Can use argument buffers if a higher limit is required. */ + GCaps.max_shader_storage_buffer_bindings = 24; + + if (GCaps.compute_shader_support) { + GCaps.max_work_group_count[0] = 65535; + GCaps.max_work_group_count[1] = 65535; + GCaps.max_work_group_count[2] = 65535; + + /* In Metal, total_thread_count is 512 or 1024, such that + * threadgroup `width*height*depth <= total_thread_count` */ + unsigned int max_threads_per_threadgroup_per_dim = + ([device supportsFamily:MTLGPUFamilyApple4] || + MTLBackend::capabilities.supports_family_mac1) ? 1024 : 512; + GCaps.max_work_group_size[0] = max_threads_per_threadgroup_per_dim; + GCaps.max_work_group_size[1] = max_threads_per_threadgroup_per_dim; + GCaps.max_work_group_size[2] = max_threads_per_threadgroup_per_dim; + } + + GCaps.transform_feedback_support = true; + + /* OPENGL Related workarounds -- none needed for Metal. */ + GCaps.extensions_len = 0; + GCaps.extension_get = mtl_extensions_get_null; + GCaps.mip_render_workaround = false; + GCaps.depth_blitting_workaround = false; + GCaps.use_main_context_workaround = false; + GCaps.broken_amd_driver = false; + + /* Metal related workarounds. */ + /* Minimum per-vertex stride is 4 bytes in Metal. A bound vertex buffer must contribute atleast 4 bytes per vertex. */ + GCaps.minimum_per_vertex_stride = 4; +} + +/** \} */ + +} // gpu +} // blender |