diff options
-rw-r--r-- | CMakeLists.txt | 23 | ||||
-rw-r--r-- | source/blender/gpu/CMakeLists.txt | 64 | ||||
-rw-r--r-- | source/blender/gpu/GPU_context.h | 14 | ||||
-rw-r--r-- | source/blender/gpu/GPU_platform.h | 11 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_backend.hh | 6 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_capabilities_private.hh | 8 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_context.cc | 70 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_platform.cc | 9 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_platform_private.hh | 2 | ||||
-rw-r--r-- | source/blender/gpu/metal/mtl_backend.hh | 77 | ||||
-rw-r--r-- | source/blender/gpu/metal/mtl_backend.mm | 404 | ||||
-rw-r--r-- | source/blender/gpu/metal/mtl_capabilities.hh | 45 | ||||
-rw-r--r-- | source/blender/gpu/opengl/gl_backend.cc | 8 | ||||
-rw-r--r-- | source/blender/gpu/opengl/gl_backend.hh | 5 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_draw.c | 5 |
15 files changed, 719 insertions, 32 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 04cedcc6722..d31a0c4a63d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -531,6 +531,19 @@ mark_as_advanced( WITH_GPU_SHADER_BUILDER ) +# Metal + +if (APPLE) + option(WITH_METAL_BACKEND "Use Metal for graphics instead of (or as well as) OpenGL on macOS." OFF) + mark_as_advanced(WITH_METAL_BACKEND) +else() + set(WITH_METAL_BACKEND OFF) +endif() + +if (WITH_METAL_BACKEND) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) +endif() + if(WIN32) option(WITH_GL_ANGLE "Link with the ANGLE library, an OpenGL ES 2.0 implementation based on Direct3D, instead of the system OpenGL library." OFF) mark_as_advanced(WITH_GL_ANGLE) @@ -1275,6 +1288,16 @@ else() endif() #----------------------------------------------------------------------------- +# Configure Metal. +if (WITH_METAL_BACKEND) + add_definitions(-DWITH_METAL_BACKEND) + + # No need to add frameworks here, all the ones we need for Metal and + # Metal-OpenGL Interop are already being added by + # build_files/cmake/platform/platform_apple.cmake +endif() + +#----------------------------------------------------------------------------- # Configure OpenMP. if(WITH_OPENMP) if(NOT OPENMP_CUSTOM) diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 45e614991ca..6c795aba560 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -5,7 +5,7 @@ # to more easily highlight codepadths in other libraries that need to be refactored, # bf_gpu is allowed to have opengl regardless of this option. -if(NOT WITH_OPENGL) +if(NOT WITH_OPENGL AND NOT WITH_METAL_BACKEND) add_definitions(-DWITH_OPENGL) endif() @@ -13,6 +13,7 @@ set(INC . intern opengl + metal ../blenkernel ../blenlib ../bmesh @@ -78,27 +79,6 @@ set(SRC intern/gpu_vertex_format.cc intern/gpu_viewport.c - opengl/gl_backend.cc - opengl/gl_batch.cc - opengl/gl_compute.cc - opengl/gl_context.cc - opengl/gl_debug.cc - opengl/gl_debug_layer.cc - opengl/gl_drawlist.cc - opengl/gl_framebuffer.cc - opengl/gl_immediate.cc - opengl/gl_index_buffer.cc - opengl/gl_query.cc - opengl/gl_shader.cc - opengl/gl_shader_interface.cc - opengl/gl_shader_log.cc - opengl/gl_state.cc - opengl/gl_storage_buffer.cc - opengl/gl_texture.cc - opengl/gl_uniform_buffer.cc - opengl/gl_vertex_array.cc - opengl/gl_vertex_buffer.cc - GPU_batch.h GPU_batch_presets.h GPU_batch_utils.h @@ -157,6 +137,30 @@ set(SRC intern/gpu_uniform_buffer_private.hh intern/gpu_vertex_buffer_private.hh intern/gpu_vertex_format_private.h +) + +set(OPENGL_SRC + + opengl/gl_backend.cc + opengl/gl_batch.cc + opengl/gl_compute.cc + opengl/gl_context.cc + opengl/gl_debug.cc + opengl/gl_debug_layer.cc + opengl/gl_drawlist.cc + opengl/gl_framebuffer.cc + opengl/gl_immediate.cc + opengl/gl_index_buffer.cc + opengl/gl_query.cc + opengl/gl_shader.cc + opengl/gl_shader_interface.cc + opengl/gl_shader_log.cc + opengl/gl_state.cc + opengl/gl_storage_buffer.cc + opengl/gl_texture.cc + opengl/gl_uniform_buffer.cc + opengl/gl_vertex_array.cc + opengl/gl_vertex_buffer.cc opengl/gl_backend.hh opengl/gl_batch.hh @@ -178,6 +182,22 @@ set(SRC opengl/gl_vertex_buffer.hh ) +set(METAL_SRC + metal/mtl_backend.mm + + metal/mtl_backend.hh + metal/mtl_capabilities.hh +) + +# Select Backend source based on availability +if(WITH_OPENGL) + list(APPEND SRC ${OPENGL_SRC}) +endif() + +if(WITH_METAL_BACKEND) + list(APPEND SRC ${METAL_SRC}) +endif() + set(LIB ${BLENDER_GL_LIBRARIES} ) diff --git a/source/blender/gpu/GPU_context.h b/source/blender/gpu/GPU_context.h index 85433913456..453c117501d 100644 --- a/source/blender/gpu/GPU_context.h +++ b/source/blender/gpu/GPU_context.h @@ -11,18 +11,15 @@ #include "GPU_batch.h" #include "GPU_common.h" +#include "GPU_platform.h" #ifdef __cplusplus extern "C" { #endif -typedef enum eGPUBackendType { - GPU_BACKEND_NONE = 0, - GPU_BACKEND_OPENGL, -} eGPUBackendType; - void GPU_backend_init(eGPUBackendType backend); void GPU_backend_exit(void); +bool GPU_backend_supported(eGPUBackendType type); eGPUBackendType GPU_backend_get_type(void); @@ -49,6 +46,13 @@ GPUContext *GPU_context_active_get(void); void GPU_context_main_lock(void); void GPU_context_main_unlock(void); +/* GPU Begin/end work blocks */ +void GPU_render_begin(); +void GPU_render_end(); + +/* For operations which need to run exactly once per frame -- even if there are no render updates. */ +void GPU_render_step(); + #ifdef __cplusplus } #endif diff --git a/source/blender/gpu/GPU_platform.h b/source/blender/gpu/GPU_platform.h index d5a0fcfa921..bd20d0c7db1 100644 --- a/source/blender/gpu/GPU_platform.h +++ b/source/blender/gpu/GPU_platform.h @@ -12,6 +12,13 @@ /* GPU platform support */ +typedef enum eGPUBackendType { + GPU_BACKEND_NONE = 0, + GPU_BACKEND_OPENGL = 1 << 0, + GPU_BACKEND_METAL = 1 << 1, + GPU_BACKEND_ANY = 0xFFFFFFFFu +} eGPUBackendType; + /* GPU Types */ typedef enum eGPUDeviceType { GPU_DEVICE_NVIDIA = (1 << 0), @@ -51,8 +58,10 @@ extern "C" { #endif /* GPU Types */ - +/* TODO: Verify all use-cases of GPU_type_matches to determine which graphics API it should apply to, and replace + * with `GPU_type_matches_ex` where appropriate. */ bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver); +bool GPU_type_matches_ex(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver, eGPUBackendType backend); eGPUSupportLevel GPU_platform_support_level(void); const char *GPU_platform_vendor(void); diff --git a/source/blender/gpu/intern/gpu_backend.hh b/source/blender/gpu/intern/gpu_backend.hh index ad906c74980..6e07e6c3229 100644 --- a/source/blender/gpu/intern/gpu_backend.hh +++ b/source/blender/gpu/intern/gpu_backend.hh @@ -49,6 +49,12 @@ class GPUBackend { virtual UniformBuf *uniformbuf_alloc(int size, const char *name) = 0; virtual StorageBuf *storagebuf_alloc(int size, GPUUsageType usage, const char *name) = 0; virtual VertBuf *vertbuf_alloc() = 0; + + /* Render Frame Coordination -- + * Used for performing per-frame actions globally */ + virtual void render_begin() = 0; + virtual void render_end() = 0; + virtual void render_step() = 0; }; } // namespace gpu diff --git a/source/blender/gpu/intern/gpu_capabilities_private.hh b/source/blender/gpu/intern/gpu_capabilities_private.hh index d645d82a879..4a951eb8458 100644 --- a/source/blender/gpu/intern/gpu_capabilities_private.hh +++ b/source/blender/gpu/intern/gpu_capabilities_private.hh @@ -20,11 +20,13 @@ namespace blender::gpu { */ struct GPUCapabilities { int max_texture_size = 0; + int max_texture_3d_size = 0; int max_texture_layers = 0; int max_textures = 0; int max_textures_vert = 0; int max_textures_geom = 0; int max_textures_frag = 0; + int max_samplers = 0; int max_work_group_count[3] = {0, 0, 0}; int max_work_group_size[3] = {0, 0, 0}; int max_uniforms_vert = 0; @@ -41,6 +43,8 @@ struct GPUCapabilities { bool compute_shader_support = false; bool shader_storage_buffer_objects_support = false; bool shader_image_load_store_support = false; + bool transform_feedback_support = false; + /* OpenGL related workarounds. */ bool mip_render_workaround = false; bool depth_blitting_workaround = false; @@ -48,6 +52,10 @@ struct GPUCapabilities { bool broken_amd_driver = false; bool use_hq_normals_workaround = false; /* Vulkan related workarounds. */ + + /* Metal related workarounds. */ + /* Minimum per-vertex stride in bytes (For a vertex buffer). */ + int minimum_per_vertex_stride = 1; }; extern GPUCapabilities GCaps; diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 60e95e09a99..5b40d3850a5 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -13,7 +13,9 @@ */ /* TODO: Create cmake option. */ -#define WITH_OPENGL_BACKEND 1 +#if WITH_OPENGL + #define WITH_OPENGL_BACKEND 1 +#endif #include "BLI_assert.h" #include "BLI_utildefines.h" @@ -32,6 +34,9 @@ # include "gl_backend.hh" # include "gl_context.hh" #endif +#ifdef WITH_METAL_BACKEND +# include "mtl_backend.hh" +#endif #include <mutex> #include <vector> @@ -141,21 +146,73 @@ void GPU_context_main_unlock() /** \} */ /* -------------------------------------------------------------------- */ +/** \name GPU Begin/end work blocks + * + * Used to explicitly define a per-frame block within which GPU work will happen. + * Used for global autoreleasepool flushing in Metal + * \{ */ + +void GPU_render_begin() { + GPUBackend* backend = GPUBackend::get(); + BLI_assert(backend); + backend->render_begin(); +} +void GPU_render_end() { + GPUBackend* backend = GPUBackend::get(); + BLI_assert(backend); + backend->render_end(); +} +void GPU_render_step() { + GPUBackend* backend = GPUBackend::get(); + BLI_assert(backend); + backend->render_step(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Backend selection * \{ */ static GPUBackend *g_backend; +bool GPU_backend_supported(eGPUBackendType type) +{ + switch (type) { + case GPU_BACKEND_OPENGL: +#ifdef WITH_OPENGL_BACKEND + return true; +#else + return false; +#endif + case GPU_BACKEND_METAL: +#ifdef WITH_METAL_BACKEND + return MTLBackend::metal_is_supported(); +#else + return false; +#endif + default: + BLI_assert(false && "No backend specified"); + return false; + } +} + void GPU_backend_init(eGPUBackendType backend_type) { BLI_assert(g_backend == nullptr); + BLI_assert(GPU_backend_supported(backend_type)); switch (backend_type) { -#if WITH_OPENGL_BACKEND +#ifdef WITH_OPENGL_BACKEND case GPU_BACKEND_OPENGL: g_backend = new GLBackend; break; #endif +#ifdef WITH_METAL_BACKEND + case GPU_BACKEND_METAL: + g_backend = new MTLBackend; + break; +#endif default: BLI_assert(0); break; @@ -172,9 +229,18 @@ void GPU_backend_exit() eGPUBackendType GPU_backend_get_type() { + +#ifdef WITH_OPENGL_BACKEND if (g_backend && dynamic_cast<GLBackend *>(g_backend) != nullptr) { return GPU_BACKEND_OPENGL; } +#endif + +#ifdef WITH_METAL_BACKEND + if (g_backend && dynamic_cast<MTLBackend *>(g_backend) != nullptr) { + return GPU_BACKEND_METAL; + } +#endif return GPU_BACKEND_NONE; } diff --git a/source/blender/gpu/intern/gpu_platform.cc b/source/blender/gpu/intern/gpu_platform.cc index 969a3033c6f..8d4e80abd97 100644 --- a/source/blender/gpu/intern/gpu_platform.cc +++ b/source/blender/gpu/intern/gpu_platform.cc @@ -65,6 +65,7 @@ void GPUPlatformGlobal::init(eGPUDeviceType gpu_device, eGPUOSType os_type, eGPUDriverType driver_type, eGPUSupportLevel gpu_support_level, + eGPUBackendType backend, const char *vendor_str, const char *renderer_str, const char *version_str) @@ -83,6 +84,7 @@ void GPUPlatformGlobal::init(eGPUDeviceType gpu_device, this->version = BLI_strdup(version_str); this->support_key = create_key(gpu_support_level, vendor_str, renderer_str, version_str); this->gpu_name = create_gpu_name(vendor_str, renderer_str, version_str); + this->backend = backend; } void GPUPlatformGlobal::clear() @@ -143,8 +145,13 @@ const char *GPU_platform_gpu_name() bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver) { + return GPU_type_matches_ex(device, os, driver, GPU_BACKEND_ANY); +} + +bool GPU_type_matches_ex(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver, eGPUBackendType backend) +{ BLI_assert(GPG.initialized); - return (GPG.device & device) && (GPG.os & os) && (GPG.driver & driver); + return (GPG.device & device) && (GPG.os & os) && (GPG.driver & driver) && (GPG.backend & backend); } /** \} */ diff --git a/source/blender/gpu/intern/gpu_platform_private.hh b/source/blender/gpu/intern/gpu_platform_private.hh index a6c400ad319..6e6c24a8662 100644 --- a/source/blender/gpu/intern/gpu_platform_private.hh +++ b/source/blender/gpu/intern/gpu_platform_private.hh @@ -23,12 +23,14 @@ class GPUPlatformGlobal { char *version = nullptr; char *support_key = nullptr; char *gpu_name = nullptr; + eGPUBackendType backend = GPU_BACKEND_NONE; public: void init(eGPUDeviceType gpu_device, eGPUOSType os_type, eGPUDriverType driver_type, eGPUSupportLevel gpu_support_level, + eGPUBackendType backend, const char *vendor_str, const char *renderer_str, const char *version_str); diff --git a/source/blender/gpu/metal/mtl_backend.hh b/source/blender/gpu/metal/mtl_backend.hh new file mode 100644 index 00000000000..0a84636b9e4 --- /dev/null +++ b/source/blender/gpu/metal/mtl_backend.hh @@ -0,0 +1,77 @@ +/** \file + * \ingroup gpu + */ + +#pragma once + +#include "BLI_vector.hh" + +#include "gpu_backend.hh" +#include "mtl_capabilities.hh" + +namespace blender { +namespace gpu { + +class Batch; +class DrawList; +class FrameBuffer; +class IndexBuf; +class QueryPool; +class Shader; +class Texture; +class UniformBuf; +class VertBuf; +class MTLContext; + +class MTLBackend : public GPUBackend { + friend class MTLContext; + + public: + /* Capabilities. */ + static MTLCapabilities capabilities; + + inline ~MTLBackend() + { + MTLBackend::platform_exit(); + } + + static bool metal_is_supported(); + inline static MTLBackend *get() + { + return static_cast<MTLBackend *>(GPUBackend::get()); + } + + void samplers_update() override; + inline void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) override + { + /* Placeholder */ + } + + /* MTL Allocators need to be implemented in separate .mm files, due to allocation of Objective-C + * objects. */ + Context *context_alloc(void *ghost_window) override; + Batch *batch_alloc() override; + DrawList *drawlist_alloc(int list_length) override; + FrameBuffer *framebuffer_alloc(const char *name) override; + IndexBuf *indexbuf_alloc() override; + QueryPool *querypool_alloc() override; + Shader *shader_alloc(const char *name) override; + Texture *texture_alloc(const char *name) override; + UniformBuf *uniformbuf_alloc(int size, const char *name) override; + VertBuf *vertbuf_alloc() override; + + /* Render Frame Coordination. */ + void render_begin() override; + void render_end() override; + void render_step() override; + bool is_inside_render_boundary(); + + private: + static void platform_init(MTLContext *ctx); + static void platform_exit(); + + static void capabilities_init(MTLContext *ctx); +}; + +} // namespace gpu +} // namespace blender
\ No newline at end of file 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 diff --git a/source/blender/gpu/metal/mtl_capabilities.hh b/source/blender/gpu/metal/mtl_capabilities.hh new file mode 100644 index 00000000000..8acdf5f303a --- /dev/null +++ b/source/blender/gpu/metal/mtl_capabilities.hh @@ -0,0 +1,45 @@ +/** \file + * \ingroup gpu + */ + +#pragma once + +namespace blender { +namespace gpu { + +/*** Derived from: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf ***/ +/** Upper Bound/Fixed Limits **/ + +#define METAL_MAX_TEXTURE_SLOTS 128 +#define METAL_MAX_SAMPLER_SLOTS METAL_MAX_TEXTURE_SLOTS +#define METAL_MAX_UNIFORM_BUFFER_BINDINGS 31 +#define METAL_MAX_VERTEX_INPUT_ATTRIBUTES 31 +#define METAL_MAX_UNIFORMS_PER_BLOCK 64 + +/* Context-specific limits -- populated in 'MTLBackend::platform_init' */ +typedef struct MTLCapabilities { + + /* Variable Limits & faeture sets */ + int max_color_render_targets = 4; /* Minimum = 4 */ + int buffer_alignment_for_textures = 256; /* Upper bound = 256 bytes */ + int minimum_buffer_offset_alignment = 256; /* Upper bound = 256 bytes */ + + /* Capabilities */ + bool supports_vertex_amplification = false; + bool supports_texture_swizzle = true; + bool supports_cubemaps = true; + bool supports_layered_rendering = true; + bool supports_memory_barriers = false; + bool supports_sampler_border_color = false; + bool supports_argument_buffers_tier2 = false; + + /* GPU Family */ + bool supports_family_mac1 = false; + bool supports_family_mac2 = false; + bool supports_family_mac_catalyst1 = false; + bool supports_family_mac_catalyst2 = false; + +} MTLCapabilities; + +} // namespace gpu +} // namespace blender diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index 883f9403920..2c9cbdb99d8 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -140,7 +140,7 @@ void GLBackend::platform_init() } } - GPG.init(device, os, driver, support_level, vendor, renderer, version); + GPG.init(device, os, driver, support_level, GPU_BACKEND_OPENGL, vendor, renderer, version); } void GLBackend::platform_exit() @@ -418,6 +418,12 @@ static void detect_workarounds() (strstr(renderer, "HD Graphics 4400") || strstr(renderer, "HD Graphics 4600"))) { GCaps.shader_storage_buffer_objects_support = false; } + + /* Metal-related Workarounds. */ + + /* Minimum Per-Vertex stride is 1 byte for OpenGL. */ + GCaps.minimum_per_vertex_stride = 1; + } // namespace blender::gpu /** Internal capabilities. */ diff --git a/source/blender/gpu/opengl/gl_backend.hh b/source/blender/gpu/opengl/gl_backend.hh index e72726d9c7b..29249111294 100644 --- a/source/blender/gpu/opengl/gl_backend.hh +++ b/source/blender/gpu/opengl/gl_backend.hh @@ -136,6 +136,11 @@ class GLBackend : public GPUBackend { glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); } + /* Render Frame Coordination */ + void render_begin(void) override{}; + void render_end(void) override{}; + void render_step(void) override{}; + private: static void platform_init(); static void platform_exit(); diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c index 46f7b67c2ba..3c8474b1b6c 100644 --- a/source/blender/windowmanager/intern/wm_draw.c +++ b/source/blender/windowmanager/intern/wm_draw.c @@ -1037,6 +1037,10 @@ void wm_draw_update(bContext *C) wmWindowManager *wm = CTX_wm_manager(C); GPU_context_main_lock(); + + GPU_render_begin(); + GPU_render_step(); + BKE_image_free_unused_gpu_textures(); LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { @@ -1075,6 +1079,7 @@ void wm_draw_update(bContext *C) /* Draw non-windows (surfaces) */ wm_surfaces_iter(C, wm_draw_surface); + GPU_render_end(); GPU_context_main_unlock(); } |