diff --git a/CMakeLists.txt b/CMakeLists.txt index 47dadab82..7212212ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ set(SCRIPTING_ENABLED OFF CACHE BOOL "Interal global cmake variable" FORCE) include(ObsHelpers) include(ObsCpack) include(GNUInstallDirs) +include(MacOSVersion) # Must be a string in the format of "x.x.x-rcx" if(DEFINED RELEASE_CANDIDATE) @@ -174,6 +175,10 @@ if(NOT INSTALLER_RUN) add_subdirectory(libobs-d3d11) endif() + if(APPLE AND ${MACOS_VERSION} VERSION_GREATER 10.11) + add_subdirectory(libobs-metal) + endif() + add_subdirectory(libobs-opengl) add_subdirectory(libobs) add_subdirectory(plugins) diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 40b897405..5468d29b7 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -52,6 +52,10 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + #include #include "ui-config.h" @@ -382,6 +386,8 @@ bool OBSApp::InitGlobalConfigDefaults() #if _WIN32 config_set_default_string(globalConfig, "Video", "Renderer", "Direct3D 11"); +#elif defined(__APPLE__) && defined(__MAC_10_11) + config_set_default_string(globalConfig, "Video", "Renderer", "Metal"); #else config_set_default_string(globalConfig, "Video", "Renderer", "OpenGL"); #endif @@ -1222,8 +1228,17 @@ const char *OBSApp::GetRenderModule() const const char *renderer = config_get_string(globalConfig, "Video", "Renderer"); - return (astrcmpi(renderer, "Direct3D 11") == 0) ? - DL_D3D11 : DL_OPENGL; +#if defined(_WIN32) + return (astrcmpi(renderer, "Direct3D 11") == 0) ? DL_D3D11 : DL_OPENGL; +#elif defined(__APPLE__) && defined(__MAC_10_12) + struct mac_version_info ver; + get_mac_ver(&ver); + + return (ver.identifier >= MACOS_SIERRA && + astrcmpi(renderer, "Metal") == 0) ? DL_METAL : DL_OPENGL; +#else + return DL_OPENGL; +#endif } static bool StartupOBS(const char *locale, profiler_name_store_t *store) diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index c9a90be43..6f2091138 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -3460,12 +3460,6 @@ bool OBSBasic::Active() const return outputHandler->Active(); } -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - static inline int AttemptToResetVideo(struct obs_video_info *ovi) { return obs_reset_video(ovi); @@ -3579,14 +3573,15 @@ int OBSBasic::ResetVideo() } ret = AttemptToResetVideo(&ovi); - if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) { +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MAC_10_11)) + if (ret != OBS_VIDEO_SUCCESS) { if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { blog(LOG_WARNING, "Tried to reset when " "already active"); return ret; } - /* Try OpenGL if DirectX fails on windows */ + /* Try OpenGL if DirectX/Metal fails on windows/macOS */ if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) { blog(LOG_WARNING, "Failed to initialize obs video (%d) " "with graphics_module='%s', retrying " @@ -3597,6 +3592,9 @@ int OBSBasic::ResetVideo() ret = AttemptToResetVideo(&ovi); } } else if (ret == OBS_VIDEO_SUCCESS) { +#else + if (ret == OBS_VIDEO_SUCCESS) { +#endif ResizePreview(ovi.base_width, ovi.base_height); if (program) ResizeProgram(ovi.base_width, ovi.base_height); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 6b1c1506f..f99757425 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -50,6 +50,10 @@ #include #include "ui-config.h" +#ifdef __APPLE__ +#include +#endif + using namespace std; // Used for QVariant in codec comboboxes @@ -466,6 +470,26 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) delete ui->enableAutoUpdates; ui->enableAutoUpdates = nullptr; #endif + +#if defined(__APPLE__) && defined(__MAC_10_12) + struct mac_version_info ver; + get_mac_ver(&ver); + + if (ver.identifier < MACOS_SIERRA) { +#endif +#ifndef _WIN32 + delete ui->rendererLabel; + delete ui->renderer; + delete ui->adapterLabel; + delete ui->adapter; + ui->rendererLabel = nullptr; + ui->renderer = nullptr; + ui->adapterLabel = nullptr; + ui->adapter = nullptr; +#endif +#if defined(__APPLE__) && defined(__MAC_10_12) + } +#endif #if !defined(_WIN32) && !defined(__APPLE__) && !HAVE_PULSEAUDIO delete ui->audioAdvGroupBox; @@ -506,10 +530,6 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) ui->processPriority->addItem(QTStr(pri.name), pri.val); #else - delete ui->rendererLabel; - delete ui->renderer; - delete ui->adapterLabel; - delete ui->adapter; delete ui->processPriorityLabel; delete ui->processPriority; delete ui->advancedGeneralGroupBox; @@ -520,10 +540,6 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) #if defined(__APPLE__) || HAVE_PULSEAUDIO delete ui->disableAudioDucking; #endif - ui->rendererLabel = nullptr; - ui->renderer = nullptr; - ui->adapterLabel = nullptr; - ui->adapter = nullptr; ui->processPriorityLabel = nullptr; ui->processPriority = nullptr; ui->advancedGeneralGroupBox = nullptr; @@ -1174,12 +1190,28 @@ void OBSBasicSettings::LoadGeneralSettings() void OBSBasicSettings::LoadRendererList() { -#ifdef _WIN32 +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MAC_10_12)) +#if defined(__APPLE__) + struct mac_version_info ver; + get_mac_ver(&ver); + + if (ver.identifier < MACOS_SIERRA) + return; +#endif + const char *renderer = config_get_string(GetGlobalConfig(), "Video", "Renderer"); +#ifdef _WIN32 ui->renderer->addItem(QT_UTF8("Direct3D 11")); +#endif +#if defined(__APPLE__) && defined(__MAC_10_12) + if (ver.identifier >= MACOS_SIERRA) + ui->renderer->addItem(QT_UTF8("Metal")); +#endif +#ifdef _WIN32 if (opt_allow_opengl || strcmp(renderer, "OpenGL") == 0) +#endif ui->renderer->addItem(QT_UTF8("OpenGL")); int idx = ui->renderer->findText(QT_UTF8(renderer)); @@ -2880,11 +2912,13 @@ void OBSBasicSettings::SaveAdvancedSettings() QString lastMonitoringDevice = config_get_string(main->Config(), "Audio", "MonitoringDeviceId"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) if (WidgetChanged(ui->renderer)) config_set_string(App()->GlobalConfig(), "Video", "Renderer", QT_TO_UTF8(ui->renderer->currentText())); - +#endif + +#ifdef _WIN32 std::string priority = QT_TO_UTF8(ui->processPriority->currentData().toString()); config_set_string(App()->GlobalConfig(), "General", "ProcessPriority", diff --git a/cmake/Modules/MacOSVersion.cmake b/cmake/Modules/MacOSVersion.cmake new file mode 100644 index 000000000..0d1f2baa5 --- /dev/null +++ b/cmake/Modules/MacOSVersion.cmake @@ -0,0 +1,33 @@ +# Once done these will be defined: +# +# MACOS_VERSION + +if(APPLE) + # NOTE: CMAKE_SYSTEM_VERSION is Darwin version + # - 12.x.x = OS X Mountain Lion (10.8) + # - 13.x.x = OS X Mavericks (10.9) + # - 14.x.x = OS X Yosemite (10.10) + # - 15.x.x = OS X El Capitan (10.11) + # - 16.x.x = macOS Sierra (10.12) + # - 17.x.x = macOS High Sierra (10.13) + + if(${CMAKE_SYSTEM_VERSION} GREATER 12 AND ${CMAKE_SYSTEM_VERSION} LESS 13) + set(MACOS_VERSION 10.8) + elseif(${CMAKE_SYSTEM_VERSION} GREATER 13 AND ${CMAKE_SYSTEM_VERSION} LESS 14) + set(MACOS_VERSION 10.9) + elseif(${CMAKE_SYSTEM_VERSION} GREATER 14 AND ${CMAKE_SYSTEM_VERSION} LESS 15) + set(MACOS_VERSION 10.10) + elseif(${CMAKE_SYSTEM_VERSION} GREATER 15 AND ${CMAKE_SYSTEM_VERSION} LESS 16) + set(MACOS_VERSION 10.11) + elseif(${CMAKE_SYSTEM_VERSION} GREATER 16 AND ${CMAKE_SYSTEM_VERSION} LESS 17) + set(MACOS_VERSION 10.12) + elseif(${CMAKE_SYSTEM_VERSION} GREATER 17 AND ${CMAKE_SYSTEM_VERSION} LESS 18) + set(MACOS_VERSION 10.13) + else() + set(MACOS_VERSION 10.0) + endif() + + message(STATUS "macOS Version ${MACOS_VERSION}") +else() + set(MACOS_VERSION 0.0) +endif() \ No newline at end of file diff --git a/cmake/Modules/ObsHelpers.cmake b/cmake/Modules/ObsHelpers.cmake index 627aadf5b..c148ffd71 100644 --- a/cmake/Modules/ObsHelpers.cmake +++ b/cmake/Modules/ObsHelpers.cmake @@ -553,7 +553,7 @@ function(install_obs_plugin_with_data target datadir) endfunction() function(define_graphic_modules target) - foreach(dl_lib opengl d3d9 d3d11) + foreach(dl_lib opengl d3d9 d3d11 metal) string(TOUPPER ${dl_lib} dl_lib_upper) if(TARGET libobs-${dl_lib}) if(UNIX AND UNIX_STRUCTURE) diff --git a/libobs-metal/CMakeLists.txt b/libobs-metal/CMakeLists.txt new file mode 100644 index 000000000..3ba617e41 --- /dev/null +++ b/libobs-metal/CMakeLists.txt @@ -0,0 +1,71 @@ +project(libobs-metal) + +find_library(APPKIT AppKit) +mark_as_advanced(APPKIT) +include_directories(${APPKIT}) + +find_library(QUARTZCORE QuartzCore) +mark_as_advanced(QUARTZCORE) +include_directories(${QUARTZCORE}) + +find_library(METAL Metal) +mark_as_advanced(METAL) +include_directories(${METAL}) + +set(libobs-metal_PLATFORM_DEPS + ${APPKIT} + ${QUARTZCORE}) + +set(libobs-metal_PLATFORM_DEPS_WEAK + Metal) + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") + +add_definitions(-DLIBOBS_EXPORTS) + +set(libobs-metal_SOURCES + metal-device.mm + metal-indexbuffer.mm + metal-rebuild.mm + metal-samplerstate.mm + metal-shader.mm + metal-shaderbuilder.cpp + metal-shaderprocessor.mm + metal-stagesurf.mm + metal-subsystem.mm + metal-swapchain.mm + metal-texture2d.mm + metal-vertexbuffer.mm + metal-zstencilbuffer.mm) + +set(libobs-metal_HEADERS + metal-shaderprocessor.hpp + metal-subsystem.hpp) + +add_library(libobs-metal MODULE + ${libobs-metal_SOURCES} + ${libobs-metal_HEADERS}) +set_target_properties(libobs-metal + PROPERTIES + OUTPUT_NAME libobs-metal + PREFIX "" + XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES) +target_compile_options(libobs-metal + PUBLIC + -msse + -msse2 + -msse3 + -msse4.1 + -msse4.2) +target_link_libraries(libobs-metal + PRIVATE + ${libobs-metal_PLATFORM_DEPS} + PUBLIC + libobs) +foreach(FRAMEWORK ${libobs-metal_PLATFORM_DEPS_WEAK}) + target_link_libraries(libobs-metal + PRIVATE + "-weak_framework ${FRAMEWORK}") +endforeach() + +install_obs_core(libobs-metal) diff --git a/libobs-metal/metal-device.mm b/libobs-metal/metal-device.mm new file mode 100644 index 000000000..03e953fe5 --- /dev/null +++ b/libobs-metal/metal-device.mm @@ -0,0 +1,316 @@ +#include +#include + +#include "metal-subsystem.hpp" + +using namespace std; + +void gs_device::InitDevice(uint32_t deviceIdx) +{ + NSArray *devices; + + devIdx = deviceIdx; + devices = MTLCopyAllDevices(); + if (devices == nil) + throw "Failed to create MTLDevice"; + + for (size_t i = 0; i < devices.count; i++) { + if (i == devIdx) { + device = devices[i]; + break; + } + } + + blog(LOG_INFO, "Loading up Metal on adapter %s (%" PRIu32 ")", + device.name.UTF8String, deviceIdx); + + if ([device supportsFeatureSet:MTLFeatureSet_OSX_GPUFamily1_v2]) { + featureSetFamily = 1; + featureSetVersion = 2; + } else + throw "Failed to initialize Metal"; + + blog(LOG_INFO, "Metal loaded successfully, feature set used: %u_v%u", + featureSetFamily, featureSetVersion); +} + +void gs_device::SetClear() +{ + ClearState state = clearStates.top().second; + + if (state.flags & GS_CLEAR_COLOR) { + MTLRenderPassColorAttachmentDescriptor *colorAttachment = + passDesc.colorAttachments[0]; + colorAttachment.loadAction = MTLLoadActionClear; + colorAttachment.clearColor = MTLClearColorMake( + state.color.x, state.color.y, state.color.z, + state.color.w); + } else + passDesc.colorAttachments[0].loadAction = MTLLoadActionLoad; + + if (state.flags & GS_CLEAR_DEPTH) { + MTLRenderPassDepthAttachmentDescriptor *depthAttachment = + passDesc.depthAttachment; + depthAttachment.loadAction = MTLLoadActionClear; + depthAttachment.clearDepth = state.depth; + } else + passDesc.depthAttachment.loadAction = MTLLoadActionLoad; + + if (state.flags & GS_CLEAR_STENCIL) { + MTLRenderPassStencilAttachmentDescriptor *stencilAttachment = + passDesc.stencilAttachment; + stencilAttachment.loadAction = MTLLoadActionClear; + stencilAttachment.clearStencil = state.stencil; + } else + passDesc.stencilAttachment.loadAction = MTLLoadActionLoad; + + clearStates.pop(); + if (clearStates.size()) + preserveClearTarget = clearStates.top().first; + else + preserveClearTarget = nullptr; +} + +void gs_device::UploadVertexBuffer(id commandEncoder) +{ + vector> buffers; + vector offsets; + + if (curVertexBuffer && curVertexShader) { + curVertexBuffer->MakeBufferList(curVertexShader, buffers); + if (curVertexBuffer->isDynamic) + curVertexBuffer->Release(); + } else { + size_t buffersToClear = curVertexShader ? + curVertexShader->NumBuffersExpected() : 0; + buffers.resize(buffersToClear); + } + + offsets.resize(buffers.size()); + + [commandEncoder setVertexBuffers:buffers.data() + offsets:offsets.data() + withRange:NSMakeRange(0, buffers.size())]; + + lastVertexBuffer = curVertexBuffer; + lastVertexShader = curVertexShader; +} + +void gs_device::UploadTextures(id commandEncoder) +{ + for (size_t i = 0; i < GS_MAX_TEXTURES; i++) { + gs_texture_2d *tex2d = static_cast( + curTextures[i]); + if (tex2d == nullptr) + break; + + [commandEncoder setFragmentTexture:tex2d->texture atIndex:i]; + } +} + +void gs_device::UploadSamplers(id commandEncoder) +{ + for (size_t i = 0; i < GS_MAX_TEXTURES; i++) { + gs_sampler_state *sampler = curSamplers[i]; + if (sampler == nullptr) + break; + + [commandEncoder setFragmentSamplerState:sampler->samplerState + atIndex:i]; + } +} + +void gs_device::LoadRasterState(id commandEncoder) +{ + [commandEncoder setViewport:rasterState.mtlViewport]; + /* use CCW to convert to a right-handed coordinate system */ + [commandEncoder setFrontFacingWinding:MTLWindingCounterClockwise]; + [commandEncoder setCullMode:rasterState.mtlCullMode]; + if (rasterState.scissorEnabled) + [commandEncoder setScissorRect:rasterState.mtlScissorRect]; +} + +void gs_device::LoadZStencilState(id commandEncoder) +{ + if (zstencilState.depthEnabled) { + if (depthStencilState == nil) { + depthStencilState = [device + newDepthStencilStateWithDescriptor: + zstencilState.dsd]; + } + [commandEncoder setDepthStencilState:depthStencilState]; + } +} + +void gs_device::UpdateViewProjMatrix() +{ + gs_matrix_get(&curViewMatrix); + + /* negate Z col of the view matrix for right-handed coordinate system */ + curViewMatrix.x.z = -curViewMatrix.x.z; + curViewMatrix.y.z = -curViewMatrix.y.z; + curViewMatrix.z.z = -curViewMatrix.z.z; + curViewMatrix.t.z = -curViewMatrix.t.z; + + matrix4_mul(&curViewProjMatrix, &curViewMatrix, &curProjMatrix); + matrix4_transpose(&curViewProjMatrix, &curViewProjMatrix); + + if (curVertexShader->viewProj) + gs_shader_set_matrix4(curVertexShader->viewProj, + &curViewProjMatrix); +} + +void gs_device::DrawPrimitives(id commandEncoder, + gs_draw_mode drawMode, uint32_t startVert, uint32_t numVerts) +{ + MTLPrimitiveType primitive = ConvertGSTopology(drawMode); + if (curIndexBuffer) { + if (numVerts == 0) + numVerts = static_cast(curIndexBuffer->num); + [commandEncoder drawIndexedPrimitives:primitive + indexCount:numVerts + indexType:curIndexBuffer->indexType + indexBuffer:curIndexBuffer->indexBuffer + indexBufferOffset:0]; + if (curIndexBuffer->isDynamic) + curIndexBuffer->indexBuffer = nil; + } else { + if (numVerts == 0) + numVerts = static_cast( + curVertexBuffer->vbData->num); + [commandEncoder drawPrimitives:primitive + vertexStart:startVert vertexCount:numVerts]; + } +} + +void gs_device::Draw(gs_draw_mode drawMode, uint32_t startVert, + uint32_t numVerts) +{ + try { + if (!curVertexShader) + throw "No vertex shader specified"; + + if (!curPixelShader) + throw "No pixel shader specified"; + + if (!curVertexBuffer) + throw "No vertex buffer specified"; + + if (!curRenderTarget) + throw "No render target to render to"; + + } catch (const char *error) { + blog(LOG_ERROR, "device_draw (Metal): %s", error); + return; + } + + if (pipelineState == nil || piplineStateChanged) { + NSError *error = nil; + pipelineState = [device newRenderPipelineStateWithDescriptor: + pipelineDesc error:&error]; + + if (pipelineState == nil) { + blog(LOG_ERROR, "device_draw (Metal): %s", + error.localizedDescription.UTF8String); + return; + } + + piplineStateChanged = false; + } + + if (preserveClearTarget != curRenderTarget) { + passDesc.colorAttachments[0].loadAction = MTLLoadActionLoad; + passDesc.depthAttachment.loadAction = MTLLoadActionLoad; + passDesc.stencilAttachment.loadAction = MTLLoadActionLoad; + } else + SetClear(); + + id commandEncoder = [commandBuffer + renderCommandEncoderWithDescriptor:passDesc]; + [commandEncoder setRenderPipelineState:pipelineState]; + + try { + gs_effect_t *effect = gs_get_effect(); + if (effect) + gs_effect_update_params(effect); + + LoadRasterState(commandEncoder); + LoadZStencilState(commandEncoder); + UpdateViewProjMatrix(); + curVertexShader->UploadParams(commandEncoder); + curPixelShader->UploadParams(commandEncoder); + UploadVertexBuffer(commandEncoder); + UploadTextures(commandEncoder); + UploadSamplers(commandEncoder); + DrawPrimitives(commandEncoder, drawMode, startVert, numVerts); + + } catch (const char *error) { + blog(LOG_ERROR, "device_draw (Metal): %s", error); + } + + [commandEncoder endEncoding]; +} + +static inline id CreateBuffer(id device, + void *data, size_t length) +{ + length = (length + 15) & ~15; + + MTLResourceOptions options = MTLResourceCPUCacheModeWriteCombined | + MTLResourceStorageModeShared; + id buffer = [device newBufferWithBytes:data + length:length options:options]; + if (buffer == nil) + throw "Failed to create buffer"; + return buffer; +} + +id gs_device::GetBuffer(void *data, size_t length) +{ + lock_guard lock(mutexObj); + auto target = find_if(unusedBufferPool.begin(), unusedBufferPool.end(), + [length](id b) { return b.length >= length; }); + if (target == unusedBufferPool.end()) { + id newBuffer = CreateBuffer(device, data, length); + curBufferPool.push_back(newBuffer); + return newBuffer; + } + + id targetBuffer = *target; + unusedBufferPool.erase(target); + curBufferPool.push_back(targetBuffer); + memcpy(targetBuffer.contents, data, length); + return targetBuffer; +} + +void gs_device::PushResources() +{ + lock_guard lock(mutexObj); + bufferPools.push(curBufferPool); + curBufferPool.clear(); +} + +void gs_device::ReleaseResources() +{ + lock_guard lock(mutexObj); + auto& pool = bufferPools.front(); + unusedBufferPool.insert(unusedBufferPool.end(), + pool.begin(), pool.end()); + bufferPools.pop(); +} + +gs_device::gs_device(uint32_t adapterIdx) +{ + matrix4_identity(&curProjMatrix); + matrix4_identity(&curViewMatrix); + matrix4_identity(&curViewProjMatrix); + + passDesc = [[MTLRenderPassDescriptor alloc] init]; + pipelineDesc = [[MTLRenderPipelineDescriptor alloc] init]; + + InitDevice(adapterIdx); + + commandQueue = [device newCommandQueue]; + + device_set_render_target(this, nullptr, nullptr); +} diff --git a/libobs-metal/metal-indexbuffer.mm b/libobs-metal/metal-indexbuffer.mm new file mode 100644 index 000000000..dce5c9772 --- /dev/null +++ b/libobs-metal/metal-indexbuffer.mm @@ -0,0 +1,67 @@ +#include "metal-subsystem.hpp" + +static inline MTLIndexType ConvertGSIndexType(gs_index_type type) +{ + switch (type) { + case GS_UNSIGNED_SHORT: return MTLIndexTypeUInt16; + case GS_UNSIGNED_LONG: return MTLIndexTypeUInt32; + } + + throw "Failed to initialize index buffer"; +} + +static inline size_t ConvertGSIndexTypeToSize(gs_index_type type) +{ + switch (type) { + case GS_UNSIGNED_SHORT: return 2; + case GS_UNSIGNED_LONG: return 4; + } + + throw "Failed to initialize index buffer"; +} + +void gs_index_buffer::PrepareBuffer() +{ + assert(isDynamic); + + indexBuffer = device->GetBuffer(indices.get(), len); +#if _DEBUG + indexBuffer.label = @"index"; +#endif +} + +void gs_index_buffer::InitBuffer() +{ + NSUInteger length = len; + MTLResourceOptions options = MTLResourceCPUCacheModeWriteCombined | + MTLResourceStorageModeShared; + + indexBuffer = [device->device newBufferWithBytes:&indices + length:length options:options]; + if (indexBuffer == nil) + throw "Failed to create index buffer"; + +#ifdef _DEBUG + indexBuffer.label = @"index"; +#endif +} + +void gs_index_buffer::Rebuild() +{ + if (!isDynamic) + InitBuffer(); +} + +gs_index_buffer::gs_index_buffer(gs_device_t *device, enum gs_index_type type, + void *indices, size_t num, uint32_t flags) + : gs_obj (device, gs_type::gs_index_buffer), + type (type), + isDynamic ((flags & GS_DYNAMIC) != 0), + indices (indices, bfree), + num (num), + len (ConvertGSIndexTypeToSize(type) * num), + indexType (ConvertGSIndexType(type)) +{ + if (!isDynamic) + InitBuffer(); +} diff --git a/libobs-metal/metal-rebuild.mm b/libobs-metal/metal-rebuild.mm new file mode 100644 index 000000000..e3ed15a7a --- /dev/null +++ b/libobs-metal/metal-rebuild.mm @@ -0,0 +1,118 @@ +#include "metal-subsystem.hpp" + +void gs_device::RebuildDevice() +try { + id dev; + + blog(LOG_WARNING, "Device Remove/Reset! Rebuilding all assets..."); + + /* ----------------------------------------------------------------- */ + + gs_obj *obj = first_obj; + + while (obj) { + switch (obj->obj_type) { + case gs_type::gs_vertex_buffer: + ((gs_vertex_buffer*)obj)->Release(); + break; + case gs_type::gs_index_buffer: + ((gs_index_buffer*)obj)->Release(); + break; + case gs_type::gs_texture_2d: + ((gs_texture_2d*)obj)->Release(); + break; + case gs_type::gs_zstencil_buffer: + ((gs_zstencil_buffer*)obj)->Release(); + break; + case gs_type::gs_stage_surface: + ((gs_stage_surface*)obj)->Release(); + break; + case gs_type::gs_sampler_state: + ((gs_sampler_state*)obj)->Release(); + break; + case gs_type::gs_vertex_shader: + ((gs_vertex_shader*)obj)->Release(); + break; + case gs_type::gs_pixel_shader: + ((gs_pixel_shader*)obj)->Release(); + break; + case gs_type::gs_swap_chain: + ((gs_swap_chain*)obj)->Release(); + break; + } + + obj = obj->next; + } + + depthStencilState = nil; + pipelineState = nil; + commandBuffer = nil; + commandQueue = nil; + + /* ----------------------------------------------------------------- */ + + InitDevice(devIdx); + + dev = device; + + obj = first_obj; + while (obj) { + switch (obj->obj_type) { + case gs_type::gs_vertex_buffer: + ((gs_vertex_buffer*)obj)->Rebuild(); + break; + case gs_type::gs_index_buffer: + ((gs_index_buffer*)obj)->Rebuild(); + break; + case gs_type::gs_texture_2d: + ((gs_texture_2d*)obj)->Rebuild(); + break; + case gs_type::gs_zstencil_buffer: + ((gs_zstencil_buffer*)obj)->Rebuild(); + break; + case gs_type::gs_stage_surface: + ((gs_stage_surface*)obj)->Rebuild(); + break; + case gs_type::gs_sampler_state: + ((gs_sampler_state*)obj)->Rebuild(); + break; + case gs_type::gs_vertex_shader: + ((gs_vertex_shader*)obj)->Rebuild(); + break; + case gs_type::gs_pixel_shader: + ((gs_pixel_shader*)obj)->Rebuild(); + break; + case gs_type::gs_swap_chain: + ((gs_swap_chain*)obj)->Rebuild(); + break; + } + + obj = obj->next; + } + + curRenderTarget = nullptr; + curRenderSide = 0; + curZStencilBuffer = nullptr; + memset(&curTextures, 0, sizeof(curTextures)); + memset(&curSamplers, 0, sizeof(curSamplers)); + curVertexBuffer = nullptr; + curIndexBuffer = nullptr; + curVertexShader = nullptr; + curPixelShader = nullptr; + curSwapChain = nullptr; + curStageSurface = nullptr; + + lastVertexBuffer = nullptr; + lastVertexShader = nullptr; + + preserveClearTarget = nullptr; + while (clearStates.size()) + clearStates.pop(); + + while (projStack.size()) + projStack.pop(); + +} catch (const char *error) { + bcrash("Failed to recreate Metal: %s", error); + +} diff --git a/libobs-metal/metal-samplerstate.mm b/libobs-metal/metal-samplerstate.mm new file mode 100644 index 000000000..6ae596fb5 --- /dev/null +++ b/libobs-metal/metal-samplerstate.mm @@ -0,0 +1,136 @@ +#include + +#include "metal-subsystem.hpp" + +using std::min; +using std::max; + +static inline MTLSamplerAddressMode ConvertGSAddressMode(gs_address_mode mode) +{ + switch (mode) { + case GS_ADDRESS_WRAP: + return MTLSamplerAddressModeRepeat; + case GS_ADDRESS_CLAMP: + return MTLSamplerAddressModeClampToEdge; + case GS_ADDRESS_MIRROR: + return MTLSamplerAddressModeMirrorRepeat; + case GS_ADDRESS_BORDER: + return MTLSamplerAddressModeClampToBorderColor; + case GS_ADDRESS_MIRRORONCE: + return MTLSamplerAddressModeMirrorClampToEdge; + } + + return MTLSamplerAddressModeRepeat; +} + +static inline MTLSamplerMinMagFilter ConvertGSMinFilter(gs_sample_filter filter) +{ + switch (filter) { + case GS_FILTER_POINT: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_LINEAR: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_MIN_MAG_POINT_MIP_LINEAR: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_MIN_POINT_MAG_MIP_LINEAR: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_MIN_LINEAR_MAG_MIP_POINT: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_MIN_MAG_LINEAR_MIP_POINT: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_ANISOTROPIC: + return MTLSamplerMinMagFilterLinear; + } + + return MTLSamplerMinMagFilterNearest; +} + +static inline MTLSamplerMinMagFilter ConvertGSMagFilter(gs_sample_filter filter) +{ + switch (filter) { + case GS_FILTER_POINT: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_LINEAR: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_MIN_MAG_POINT_MIP_LINEAR: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_MIN_POINT_MAG_MIP_LINEAR: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_MIN_LINEAR_MAG_MIP_POINT: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR: + return MTLSamplerMinMagFilterNearest; + case GS_FILTER_MIN_MAG_LINEAR_MIP_POINT: + return MTLSamplerMinMagFilterLinear; + case GS_FILTER_ANISOTROPIC: + return MTLSamplerMinMagFilterLinear; + } + + return MTLSamplerMinMagFilterNearest; +} + +static inline MTLSamplerMipFilter ConvertGSMipFilter(gs_sample_filter filter) +{ + switch (filter) { + case GS_FILTER_POINT: + return MTLSamplerMipFilterNearest; + case GS_FILTER_LINEAR: + return MTLSamplerMipFilterLinear; + case GS_FILTER_MIN_MAG_POINT_MIP_LINEAR: + return MTLSamplerMipFilterLinear; + case GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT: + return MTLSamplerMipFilterNearest; + case GS_FILTER_MIN_POINT_MAG_MIP_LINEAR: + return MTLSamplerMipFilterLinear; + case GS_FILTER_MIN_LINEAR_MAG_MIP_POINT: + return MTLSamplerMipFilterNearest; + case GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR: + return MTLSamplerMipFilterLinear; + case GS_FILTER_MIN_MAG_LINEAR_MIP_POINT: + return MTLSamplerMipFilterNearest; + case GS_FILTER_ANISOTROPIC: + return MTLSamplerMipFilterLinear; + } + + return MTLSamplerMipFilterNearest; +} + +void gs_sampler_state::InitSampler() +{ + samplerState = [device->device + newSamplerStateWithDescriptor:samplerDesc]; + if (samplerState == nil) + throw "Failed to create sampler state"; +} + +gs_sampler_state::gs_sampler_state(gs_device_t *device, + const gs_sampler_info *info) + : gs_obj (device, gs_type::gs_sampler_state), + info (*info) +{ + samplerDesc = [[MTLSamplerDescriptor alloc] init]; + samplerDesc.sAddressMode = ConvertGSAddressMode(info->address_u); + samplerDesc.tAddressMode = ConvertGSAddressMode(info->address_v); + samplerDesc.rAddressMode = ConvertGSAddressMode(info->address_w); + samplerDesc.minFilter = ConvertGSMinFilter(info->filter); + samplerDesc.magFilter = ConvertGSMagFilter(info->filter); + samplerDesc.mipFilter = ConvertGSMipFilter(info->filter); + samplerDesc.maxAnisotropy = min(max(info->max_anisotropy, 1), 16); + samplerDesc.compareFunction = MTLCompareFunctionAlways; + + if ((info->border_color & 0x000000FF) == 0) + samplerDesc.borderColor = MTLSamplerBorderColorTransparentBlack; + else if (info->border_color == 0xFFFFFFFF) + samplerDesc.borderColor = MTLSamplerBorderColorOpaqueWhite; + else + samplerDesc.borderColor = MTLSamplerBorderColorOpaqueBlack; + + + InitSampler(); +} diff --git a/libobs-metal/metal-shader.mm b/libobs-metal/metal-shader.mm new file mode 100644 index 000000000..52cd90d49 --- /dev/null +++ b/libobs-metal/metal-shader.mm @@ -0,0 +1,354 @@ +#include +#include +#include +#include + +#include "metal-subsystem.hpp" +#include "metal-shaderprocessor.hpp" + +using namespace std; + +static MTLCompileOptions *mtlCompileOptions = nil; + +gs_vertex_shader::gs_vertex_shader(gs_device_t *device, const char *file, + const char *shaderString) + : gs_shader (device, gs_type::gs_vertex_shader, GS_SHADER_VERTEX), + hasNormals (false), + hasColors (false), + hasTangents (false), + texUnits (0) +{ + ShaderProcessor processor(device); + ShaderBufferInfo info; + MTLVertexDescriptor *vertdesc; + + vertdesc = [[MTLVertexDescriptor alloc] init]; + + processor.Process(shaderString, file); + source = processor.BuildString(type); + processor.BuildParams(params); + processor.BuildParamInfo(info); + processor.BuildVertexDesc(vertdesc); + BuildConstantBuffer(); + + Compile(source); + + hasNormals = info.normals; + hasColors = info.colors; + hasTangents = info.tangents; + texUnits = info.texUnits; + + vertexDesc = vertdesc; + + viewProj = gs_shader_get_param_by_name(this, "ViewProj"); + world = gs_shader_get_param_by_name(this, "World"); +} + +gs_pixel_shader::gs_pixel_shader(gs_device_t *device, const char *file, + const char *shaderString) + : gs_shader(device, gs_type::gs_pixel_shader, GS_SHADER_PIXEL) +{ + ShaderProcessor processor(device); + + processor.Process(shaderString, file); + source = processor.BuildString(type); + processor.BuildParams(params); + processor.BuildSamplers(samplers); + BuildConstantBuffer(); + + Compile(source); +} + +void gs_shader::BuildConstantBuffer() +{ + for (size_t i = 0; i < params.size(); i++) { + gs_shader_param ¶m = params[i]; + size_t size = 0; + + switch (param.type) { + case GS_SHADER_PARAM_BOOL: + case GS_SHADER_PARAM_INT: + case GS_SHADER_PARAM_FLOAT: size = sizeof(float); break; + case GS_SHADER_PARAM_INT2: + case GS_SHADER_PARAM_VEC2: size = sizeof(vec2); break; + case GS_SHADER_PARAM_INT3: + case GS_SHADER_PARAM_VEC3: size = sizeof(float) * 3; break; + case GS_SHADER_PARAM_INT4: + case GS_SHADER_PARAM_VEC4: size = sizeof(vec4); break; + case GS_SHADER_PARAM_MATRIX4X4: + size = sizeof(float) * 4 * 4; + break; + case GS_SHADER_PARAM_TEXTURE: + case GS_SHADER_PARAM_STRING: + case GS_SHADER_PARAM_UNKNOWN: + continue; + } + + /* checks to see if this constant needs to start at a new + * register */ + if (size && (constantSize & 15) != 0) { + size_t alignMax = (constantSize + 15) & ~15; + + if ((size + constantSize) > alignMax) + constantSize = alignMax; + } + + param.pos = constantSize; + constantSize += size; + } + + for (gs_shader_param ¶m : params) + gs_shader_set_default(¶m); + + data.resize(constantSize); +} + +void gs_shader::Compile(string shaderString) +{ + if (mtlCompileOptions == nil) { + mtlCompileOptions = [[MTLCompileOptions alloc] init]; + mtlCompileOptions.languageVersion = MTLLanguageVersion1_2; + } + + NSString *nsShaderString = [[NSString alloc] + initWithBytesNoCopy:(void*)shaderString.data() + length:shaderString.length() + encoding:NSUTF8StringEncoding freeWhenDone:NO]; + NSError *errors; + id lib = [device->device newLibraryWithSource:nsShaderString + options:mtlCompileOptions error:&errors]; + if (lib == nil) { + blog(LOG_DEBUG, "Converted shader program:\n%s\n------\n", + shaderString.c_str()); + + if (errors != nil) + throw ShaderError(errors); + else + throw "Failed to compile shader"; + } + + id func = [lib newFunctionWithName:@"_main"]; + if (func == nil) + throw "Failed to create function"; + + library = lib; + function = func; +} + +void gs_shader::Rebuild() +{ + Compile(source); + + for (gs_shader_param ¶m : params) { + param.nextSampler = nullptr; + param.curValue.clear(); + param.changed = true; + gs_shader_set_default(¶m); + } +} + +inline void gs_shader::UpdateParam(uint8_t *data, gs_shader_param ¶m) +{ + if (param.type != GS_SHADER_PARAM_TEXTURE) { + if (!param.curValue.size()) + throw "Not all shader parameters were set"; + + if (param.changed) { + memcpy(data + param.pos, param.curValue.data(), + param.curValue.size()); + param.changed = false; + } + + } else if (param.curValue.size() == sizeof(gs_texture_t*)) { + gs_texture_t *tex; + memcpy(&tex, param.curValue.data(), sizeof(gs_texture_t*)); + device_load_texture(device, tex, param.textureID); + + if (param.nextSampler) { + device_load_samplerstate(device, param.nextSampler, + param.textureID); + param.nextSampler = nullptr; + } + } +} + +void gs_shader::UploadParams(id commandEncoder) +{ + uint8_t *ptr = data.data(); + + for (size_t i = 0; i < params.size(); i++) + UpdateParam(ptr, params[i]); + + if (!constantSize) + return; + + id cnt = device->GetBuffer(ptr, data.size()); +#if _DEBUG + cnt.label = @"constants"; +#endif + + if (type == GS_SHADER_VERTEX) + [commandEncoder setVertexBuffer:cnt offset:0 atIndex:30]; + else if (type == GS_SHADER_PIXEL) + [commandEncoder setFragmentBuffer:cnt offset:0 atIndex:30]; + else + throw "This is unknown shader type"; +} + +void gs_shader_destroy(gs_shader_t *shader) +{ + assert(shader != nullptr); + assert(shader->obj_type == gs_type::gs_vertex_shader || + shader->obj_type == gs_type::gs_pixel_shader); + + if (shader->device->lastVertexShader == shader) + shader->device->lastVertexShader = nullptr; + + delete shader; +} + +int gs_shader_get_num_params(const gs_shader_t *shader) +{ + assert(shader != nullptr); + assert(shader->obj_type == gs_type::gs_vertex_shader || + shader->obj_type == gs_type::gs_pixel_shader); + + return (int)shader->params.size(); +} + +gs_sparam_t *gs_shader_get_param_by_idx(gs_shader_t *shader, uint32_t param) +{ + assert(shader != nullptr); + assert(shader->obj_type == gs_type::gs_vertex_shader || + shader->obj_type == gs_type::gs_pixel_shader); + + return &shader->params[param]; +} + +gs_sparam_t *gs_shader_get_param_by_name(gs_shader_t *shader, const char *name) +{ + for (size_t i = 0; i < shader->params.size(); i++) { + gs_shader_param ¶m = shader->params[i]; + if (strcmp(param.name.c_str(), name) == 0) + return ¶m; + } + + return nullptr; +} + +gs_sparam_t *gs_shader_get_viewproj_matrix(const gs_shader_t *shader) +{ + assert(shader != nullptr); + assert(shader->obj_type == gs_type::gs_vertex_shader || + shader->obj_type == gs_type::gs_pixel_shader); + + if (shader->type != GS_SHADER_VERTEX) + return nullptr; + + return static_cast(shader)->viewProj; +} + +gs_sparam_t *gs_shader_get_world_matrix(const gs_shader_t *shader) +{ + assert(shader != nullptr); + assert(shader->obj_type == gs_type::gs_vertex_shader || + shader->obj_type == gs_type::gs_pixel_shader); + + if (shader->type != GS_SHADER_VERTEX) + return nullptr; + + return static_cast(shader)->world; +} + +void gs_shader_get_param_info(const gs_sparam_t *param, + struct gs_shader_param_info *info) +{ + if (!param) + return; + + info->name = param->name.c_str(); + info->type = param->type; +} + +static inline void shader_setval_inline(gs_shader_param *param, + const void *data, size_t size) +{ + assert(param); + + if (!param) + return; + + bool size_changed = param->curValue.size() != size; + if (size_changed) + param->curValue.resize(size); + + if (size_changed || memcmp(param->curValue.data(), data, size) != 0) { + memcpy(param->curValue.data(), data, size); + param->changed = true; + } +} + +void gs_shader_set_bool(gs_sparam_t *param, bool val) +{ + int b_val = (int)val; + shader_setval_inline(param, &b_val, sizeof(int)); +} + +void gs_shader_set_float(gs_sparam_t *param, float val) +{ + shader_setval_inline(param, &val, sizeof(float)); +} + +void gs_shader_set_int(gs_sparam_t *param, int val) +{ + shader_setval_inline(param, &val, sizeof(int)); +} + +void gs_shader_set_matrix3(gs_sparam_t *param, const struct matrix3 *val) +{ + struct matrix4 mat; + matrix4_from_matrix3(&mat, val); + shader_setval_inline(param, &mat, sizeof(matrix4)); +} + +void gs_shader_set_matrix4(gs_sparam_t *param, const struct matrix4 *val) +{ + shader_setval_inline(param, val, sizeof(matrix4)); +} + +void gs_shader_set_vec2(gs_sparam_t *param, const struct vec2 *val) +{ + shader_setval_inline(param, val, sizeof(vec2)); +} + +void gs_shader_set_vec3(gs_sparam_t *param, const struct vec3 *val) +{ + shader_setval_inline(param, val, sizeof(float) * 3); +} + +void gs_shader_set_vec4(gs_sparam_t *param, const struct vec4 *val) +{ + shader_setval_inline(param, val, sizeof(vec4)); +} + +void gs_shader_set_texture(gs_sparam_t *param, gs_texture_t *val) +{ + shader_setval_inline(param, &val, sizeof(gs_texture_t*)); +} + +void gs_shader_set_val(gs_sparam_t *param, const void *val, size_t size) +{ + shader_setval_inline(param, val, size); +} + +void gs_shader_set_default(gs_sparam_t *param) +{ + if (param->defaultValue.size()) + shader_setval_inline(param, param->defaultValue.data(), + param->defaultValue.size()); +} + +void gs_shader_set_next_sampler(gs_sparam_t *param, gs_samplerstate_t *sampler) +{ + param->nextSampler = sampler; +} diff --git a/libobs-metal/metal-shaderbuilder.cpp b/libobs-metal/metal-shaderbuilder.cpp new file mode 100644 index 000000000..321034d13 --- /dev/null +++ b/libobs-metal/metal-shaderbuilder.cpp @@ -0,0 +1,937 @@ +#include "metal-shaderprocessor.hpp" + +#include +#include +#include +#include + +using namespace std; + +#define METAL_VERSION_1_1 ((1 << 16) | 1) +#define METAL_VERSION_1_2 ((1 << 16) | 2) +#define COMPILE_METAL_VERSION METAL_VERSION_1_2 + +#define USE_PROGRAMMABLE_SAMPLER 1 + +constexpr const char *UNIFORM_DATA_NAME = "UniformData"; + +enum class ShaderTextureCallType +{ + Sample, + SampleBias, + SampleGrad, + SampleLevel, + Load +}; + +struct ShaderFunctionInfo +{ + bool useUniform; + vector useTextures; +#if USE_PROGRAMMABLE_SAMPLER + vector useSamplers; +#endif +}; + +struct ShaderBuilder +{ + const gs_shader_type type; + + ShaderParser *parser; + ostrstream output; + + set constantNames; + vector textureVars; + map functionInfo; + + void Build(string &outputString); + + inline ShaderBuilder(gs_shader_type type, ShaderParser *parser) + : type(type), + parser(parser) + { + } + + bool isVertexShader() const {return type == GS_SHADER_VERTEX;} + bool isPixelShader() const {return type == GS_SHADER_PIXEL;} + +private: + struct shader_var *GetVariable(struct cf_token *token); + + bool IsNextCompareOperator(struct cf_token *&token); + void AnalysisFunction(struct cf_token *&token, const char *end, + ShaderFunctionInfo &info); + + void WriteType(const char *type); + bool WriteTypeToken(struct cf_token *token); + bool WriteMul(struct cf_token *&token); + bool WriteConstantVariable(struct cf_token *token); + bool WriteTextureCall(struct cf_token *&token, + ShaderTextureCallType type); + bool WriteTextureCode(struct cf_token *&token, struct shader_var *var); + bool WriteIntrinsic(struct cf_token *&token); + void WriteFunctionAdditionalParam(string funcionName); + void WriteFunctionContent(struct cf_token *&token, const char *end); + void WriteSamplerParamDelimitter(bool &first); + void WriteSamplerFilter(enum gs_sample_filter filter, bool &first); + void WriteSamplerAddress(enum gs_address_mode address, + const char key, bool &first); + void WriteSamplerMaxAnisotropy(int maxAnisotropy, bool &first); + void WriteSamplerBorderColor(uint32_t borderColor, bool &first); + + void WriteVariable(const shader_var *var); + void WriteSampler(struct shader_sampler *sampler); + void WriteStruct(const shader_struct *str); + void WriteFunction(const shader_func *func); + + void WriteInclude(); + void WriteVariables(); + void WriteSamplers(); + void WriteStructs(); + void WriteFunctions(); +}; + +static inline const char *GetType(const string &type) +{ + if (type == "texture2d") + return "texture2d"; + else if (type == "texture3d") + return "texture3d"; + else if (type == "texture_cube") + return "texturecube"; + else if (type == "texture_rect") + throw "texture_rect is not supported in Metal"; + else if (type.compare(0, 4, "half") == 0) { + switch (*(type.end() - 1)) { + case '2': return "float2"; + case '3': return "float3"; + case '4': return "float4"; + case 'f': return "float"; + } + throw "Unknown type"; + } else if (type.compare(0, 10, "min16float") == 0) { + switch (*(type.end() - 1)) { + case '2': return "half2"; + case '3': return "half3"; + case '4': return "half4"; + case 'f': return "half"; + } + throw "Unknown type"; + } else if (type.compare(0, 10, "min10float") == 0) + throw "min10float* is not supported in Metal"; + else if (type.compare(0, 6, "double") == 0) + throw "double* is not supported in Metal"; + else if (type.compare(0, 8, "min16int") == 0) { + switch (*(type.end() - 1)) { + case '2': return "short2"; + case '3': return "short3"; + case '4': return "short4"; + case 't': return "short"; + } + throw "Unknown type"; + } else if (type.compare(0, 9, "min16uint") == 0) { + switch (*(type.end() - 1)) { + case '2': return "ushort2"; + case '3': return "ushort3"; + case '4': return "ushort4"; + case 't': return "ushort"; + } + throw "Unknown type"; + } else if (type.compare(0, 8, "min12int") == 0) + throw "min12int* is not supported in Metal"; + + return nullptr; +} + +inline void ShaderBuilder::WriteType(const char *rawType) +{ + string type(rawType); + const char *newType = GetType(string(rawType)); + output << (newType != nullptr ? newType : type); +} + +inline bool ShaderBuilder::WriteTypeToken(struct cf_token *token) +{ + string type(token->str.array, token->str.len); + const char *newType = GetType(type); + if (newType == nullptr) + return false; + + output << newType; + return true; +} + +inline void ShaderBuilder::WriteInclude() +{ + output << "#include " << endl + << "using namespace metal;" << endl + << endl; +} + +inline void ShaderBuilder::WriteVariable(const shader_var *var) +{ + if (var->var_type == SHADER_VAR_CONST) + output << "constant "; + + WriteType(var->type); + + output << ' ' << var->name; +} + +static inline const char *GetMapping(const char *rawMapping) +{ + if (rawMapping == nullptr) + return nullptr; + + string mapping(rawMapping); + if (mapping == "POSITION") + return "position"; + if (mapping == "COLOR") + return "color(0)"; + + return nullptr; +} + +inline void ShaderBuilder::WriteVariables() +{ + if (parser->params.num == 0) + return; + + bool isFirst = true; + for (struct shader_var *var = parser->params.array; + var != parser->params.array + parser->params.num; + var++) { + if (isPixelShader() && + astrcmp_n("texture", var->type, 7) == 0) { + textureVars.push_back(var); + + } else { + if (isFirst) { + output << "struct " << UNIFORM_DATA_NAME + << " {" << endl; + isFirst = false; + } + + output << '\t'; + WriteVariable(var); + + const char* mapping = GetMapping(var->mapping); + if (mapping != nullptr) + output << " [[" << mapping << "]]"; + + output << ';' << endl; + + constantNames.insert(var->name); + } + } + if (!isFirst) + output << "};" << endl << endl; +} + +inline void ShaderBuilder::WriteSamplerParamDelimitter(bool &first) +{ + if (!first) + output << "," << endl; + else + first = false; +} + +inline void ShaderBuilder::WriteSamplerFilter(enum gs_sample_filter filter, + bool &first) +{ + if (filter != GS_FILTER_POINT) { + WriteSamplerParamDelimitter(first); + + switch (filter) { + case GS_FILTER_LINEAR: + case GS_FILTER_ANISOTROPIC: + output << "\tfilter::linear"; + break; + case GS_FILTER_MIN_MAG_POINT_MIP_LINEAR: + output << "\tmag_filter::nearest," << endl + << "\tmin_filter::nearest," << endl + << "\tmip_filter::linear"; + break; + case GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT: + output << "\tmag_filter::nearest," << endl + << "\tmin_filter::nearest," << endl + << "\tmip_filter::linear"; + break; + case GS_FILTER_MIN_POINT_MAG_MIP_LINEAR: + output << "\tmag_filter::linear," << endl + << "\tmin_filter::nearest," << endl + << "\tmip_filter::linear"; + break; + case GS_FILTER_MIN_LINEAR_MAG_MIP_POINT: + output << "\tmag_filter::nearest," << endl + << "\tmin_filter::linear," << endl + << "\tmip_filter::nearest"; + break; + case GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR: + output << "\tmag_filter::nearest," << endl + << "\tmin_filter::linear," << endl + << "\tmip_filter::linear"; + break; + case GS_FILTER_MIN_MAG_LINEAR_MIP_POINT: + output << "\tmag_filter::linear," << endl + << "\tmin_filter::linear," << endl + << "\tmip_filter::nearest"; + break; + case GS_FILTER_POINT: + default: + throw "Unknown error"; + } + } +} + +inline void ShaderBuilder::WriteSamplerAddress(enum gs_address_mode address, + const char key, bool &first) +{ + if (address != GS_ADDRESS_CLAMP) { + WriteSamplerParamDelimitter(first); + + output << "\t" << key << "_address::"; + switch (address) + { + case GS_ADDRESS_WRAP: + output << "repeat"; + break; + case GS_ADDRESS_MIRROR: + output << "mirrored_repeat"; + break; + case GS_ADDRESS_BORDER: +#if COMPILE_METAL_VERSION >= METAL_VERSION_1_2 + output << "clamp_to_border"; + break; +#else + throw "GS_ADDRESS_BORDER is not supported in MSL 1.1"; +#endif + case GS_ADDRESS_MIRRORONCE: + throw "GS_ADDRESS_MIRRORONCE is not supported in Metal"; + default: + throw "Unknown error"; + } + } +} + +inline void ShaderBuilder::WriteSamplerMaxAnisotropy(int maxAnisotropy, + bool &first) +{ + if (maxAnisotropy >= 2 && maxAnisotropy <= 16) { + WriteSamplerParamDelimitter(first); + + output << "\tmax_anisotropy(" << maxAnisotropy << ")"; + } +} + +inline void ShaderBuilder::WriteSamplerBorderColor(uint32_t borderColor, + bool &first) +{ + const bool isNotTransBlack = (borderColor & 0x000000FF) != 0; + const bool isOpaqueWhite = borderColor == 0xFFFFFFFF; + if (isNotTransBlack || isOpaqueWhite) { + WriteSamplerParamDelimitter(first); + + output << "\tborder_color::"; + + if (isOpaqueWhite) + output << "opaque_white"; + else if (isNotTransBlack) + output << "opaque_black"; + } +} + +inline void ShaderBuilder::WriteSampler(struct shader_sampler *sampler) +{ + gs_sampler_info si; + shader_sampler_convert(sampler, &si); + + output << "constexpr sampler " << sampler->name << "(" << endl; + + bool isFirst = true; + WriteSamplerFilter(si.filter, isFirst); + WriteSamplerAddress(si.address_u, 's', isFirst); + WriteSamplerAddress(si.address_v, 't', isFirst); + WriteSamplerAddress(si.address_w, 'r', isFirst); + WriteSamplerMaxAnisotropy(si.max_anisotropy, isFirst); + WriteSamplerBorderColor(si.border_color, isFirst); + + output << ");" << endl << endl; +} + +inline void ShaderBuilder::WriteSamplers() +{ + if (isPixelShader()) { + for (struct shader_sampler *sampler = parser->samplers.array; + sampler != parser->samplers.array + parser->samplers.num; + sampler++) + WriteSampler(sampler); + } +} + +inline void ShaderBuilder::WriteStruct(const shader_struct *str) +{ + output << "struct " << str->name << " {" << endl; + + size_t attributeId = 0; + for (struct shader_var *var = str->vars.array; + var != str->vars.array + str->vars.num; + var++) { + output << '\t'; + WriteVariable(var); + + const char* mapping = GetMapping(var->mapping); + if (isVertexShader()) { + output << " [[attribute(" << attributeId++ << ")"; + if (mapping != nullptr) + output << ", " << mapping; + output << "]]"; + } + + output << ';' << endl; + } + + output << "};" << endl << endl; +} + +inline void ShaderBuilder::WriteStructs() +{ + for (struct shader_struct *str = parser->structs.array; + str != parser->structs.array + parser->structs.num; + str++) + WriteStruct(str); +} + +/* + * NOTE: HLSL -> MSL intrinsic conversions + * clip -> (unsupported) + * ddx -> dfdx + * ddy -> dfdy + * frac -> fract + * lerp -> mix + * mul -> (change to operator) + * Sample -> sample + * SampleBias -> sample(.., bias(..)) + * SampleGrad -> sample(.., gradient2d(..)) + * SampleLevel -> sample(.., level(..)) + * Load -> read + * A cmp B -> all(A cmp B) + * + * All else can be left as-is + */ + +inline bool ShaderBuilder::WriteMul(struct cf_token *&token) +{ + struct cf_parser *cfp = &parser->cfp; + cfp->cur_token = token; + + if (!cf_next_token(cfp)) return false; + if (!cf_token_is(cfp, "(")) return false; + + output << '('; + WriteFunctionContent(cfp->cur_token, ","); + output << ") * ("; + cf_next_token(cfp); + WriteFunctionContent(cfp->cur_token, ")"); + output << "))"; + + token = cfp->cur_token; + return true; +} + +inline bool ShaderBuilder::WriteConstantVariable(struct cf_token *token) +{ + string str(token->str.array, token->str.len); + if (constantNames.find(str) != constantNames.end()) { + output << "uniforms." << str; + return true; + } + return false; +} + +inline bool ShaderBuilder::WriteTextureCall(struct cf_token *&token, + ShaderTextureCallType type) +{ + struct cf_parser *cfp = &parser->cfp; + cfp->cur_token = token; + + /* ( */ + if (!cf_next_token(cfp)) return false; + if (!cf_token_is(cfp, "(")) return false; + + /* sampler */ + if (type != ShaderTextureCallType::Load) { + output << "sample("; + + if (!cf_next_token(cfp)) return false; + if (cfp->cur_token->type != CFTOKEN_NAME) return false; + output.write(cfp->cur_token->str.array, + cfp->cur_token->str.len); + + if (!cf_next_token(cfp)) return false; + if (!cf_token_is(cfp, ",")) return false; + output << ", "; + } else + output << "read((u"; + + /* location */ + if (!cf_next_token(cfp)) return false; + if (type != ShaderTextureCallType::Sample && + type != ShaderTextureCallType::Load) { + WriteFunctionContent(cfp->cur_token, ","); + + /* bias, gradient2d, level */ + switch (type) + { + case ShaderTextureCallType::SampleBias: + output << "bias("; + if (!cf_next_token(cfp)) return false; + WriteFunctionContent(cfp->cur_token, ")"); + output << ')'; + break; + + case ShaderTextureCallType::SampleGrad: + output << "gradient2d("; + if (!cf_next_token(cfp)) return false; + WriteFunctionContent(cfp->cur_token, ","); + if (!cf_next_token(cfp)) return false; + WriteFunctionContent(cfp->cur_token, ")"); + output << ')'; + break; + + case ShaderTextureCallType::SampleLevel: + output << "level("; + if (!cf_next_token(cfp)) return false; + WriteFunctionContent(cfp->cur_token, ")"); + output << ')'; + break; + + default: + break; + } + } else + WriteFunctionContent(cfp->cur_token, ")"); + + /* ) */ + if (type == ShaderTextureCallType::Load) + output << ").xy)"; + else + output << ')'; + + return true; +} + +inline bool ShaderBuilder::WriteTextureCode(struct cf_token *&token, + struct shader_var *var) +{ + struct cf_parser *cfp = &parser->cfp; + bool succeeded = false; + cfp->cur_token = token; + + if (!cf_next_token(cfp)) return false; + if (!cf_token_is(cfp, ".")) return false; + output << var->name << "."; + + if (!cf_next_token(cfp)) return false; + if (cf_token_is(cfp, "Sample")) + succeeded = WriteTextureCall(cfp->cur_token, + ShaderTextureCallType::Sample); + else if (cf_token_is(cfp, "SampleBias")) + succeeded = WriteTextureCall(cfp->cur_token, + ShaderTextureCallType::SampleBias); + else if (cf_token_is(cfp, "SampleGrad")) + succeeded = WriteTextureCall(cfp->cur_token, + ShaderTextureCallType::SampleGrad); + else if (cf_token_is(cfp, "SampleLevel")) + succeeded = WriteTextureCall(cfp->cur_token, + ShaderTextureCallType::SampleLevel); + else if (cf_token_is(cfp, "Load")) + succeeded = WriteTextureCall(cfp->cur_token, + ShaderTextureCallType::Load); + + if (!succeeded) + throw "Failed to write texture code"; + + token = cfp->cur_token; + return true; +} + +inline struct shader_var *ShaderBuilder::GetVariable(struct cf_token *token) +{ + for (struct shader_var *var = parser->params.array; + var != parser->params.array + parser->params.num; + var++) { + if (strref_cmp(&token->str, var->name) == 0) + return var; + } + + return nullptr; +} + +inline bool ShaderBuilder::WriteIntrinsic(struct cf_token *&token) +{ + bool written = true; + + if (strref_cmp(&token->str, "clip") == 0) + throw "clip is not supported in Metal"; + else if (strref_cmp(&token->str, "ddx") == 0) + output << "dfdx"; + else if (strref_cmp(&token->str, "ddy") == 0) + output << "dfdy"; + else if (strref_cmp(&token->str, "frac") == 0) + output << "fract"; + else if (strref_cmp(&token->str, "lerp") == 0) + output << "mix"; + else if (strref_cmp(&token->str, "mul") == 0) + written = WriteMul(token); + else { + struct shader_var *var = GetVariable(token); + if (var != nullptr && astrcmp_n(var->type, "texture", 7) == 0) + written = WriteTextureCode(token, var); + else + written = false; + } + + return written; +} + +inline void ShaderBuilder::AnalysisFunction(struct cf_token *&token, + const char *end, ShaderFunctionInfo &info) +{ + while (token->type != CFTOKEN_NONE) { + token++; + + if (strref_cmp(&token->str, end) == 0) + break; + + if (token->type == CFTOKEN_NAME) { + string name(token->str.array, token->str.len); + + /* Check function */ + const auto fi = functionInfo.find(name); + if (fi != functionInfo.end()) { + if (fi->second.useUniform) + info.useUniform = true; + info.useTextures.insert(info.useTextures.end(), + fi->second.useTextures.begin(), + fi->second.useTextures.end()); +#if USE_PROGRAMMABLE_SAMPLER + info.useSamplers.insert(info.useSamplers.end(), + fi->second.useSamplers.begin(), + fi->second.useSamplers.end()); +#endif + continue; + } + + /* Check UniformData */ + if (!info.useUniform && + constantNames.find(name) != constantNames.end()) { + info.useUniform = true; + continue; + } + + /* Check texture */ + if (isPixelShader()) { + for (auto tex = textureVars.cbegin(); + tex != textureVars.cend(); + tex++) { + if (name == (*tex)->name) { + info.useTextures.emplace_back( + name); + break; + } + } +#if USE_PROGRAMMABLE_SAMPLER + for (struct shader_sampler *sampler = + parser->samplers.array; + sampler != parser->samplers.array + + parser->samplers.num; + sampler++) { + if (name == sampler->name) { + info.useSamplers.emplace_back( + name); + break; + } + } +#endif + } + + } else if (token->type == CFTOKEN_OTHER) { + if (*token->str.array == '{') + AnalysisFunction(token, "}", info); + else if (*token->str.array == '(') + AnalysisFunction(token, ")", info); + } + } +} + +inline void ShaderBuilder::WriteFunctionAdditionalParam(string funcionName) +{ + auto fi = functionInfo.find(funcionName); + if (fi != functionInfo.end()) { + if (fi->second.useUniform) + output << ", uniforms"; + + for (auto var = textureVars.cbegin(); + var != textureVars.cend(); + var++) { + for (auto tex = fi->second.useTextures.cbegin(); + tex != fi->second.useTextures.cend(); + tex++) { + if (*tex == (*var)->name) { + output << ", " << *tex; + break; + } + } + } + +#if USE_PROGRAMMABLE_SAMPLER + for (struct shader_sampler *sampler = parser->samplers.array; + sampler != parser->samplers.array + parser->samplers.num; + sampler++) { + for (auto s = fi->second.useSamplers.cbegin(); + s != fi->second.useSamplers.cend(); + s++) { + if (*s == sampler->name) { + output << ", " << *s; + break; + } + } + } +#endif + } +} + +inline bool ShaderBuilder::IsNextCompareOperator(struct cf_token *&token) +{ + struct cf_token *token2 = token + 1; + if (token2->type != CFTOKEN_OTHER) + return false; + + if (astrcmp_n(token2->str.array, "==", token2->str.len) == 0 || + astrcmp_n(token2->str.array, "!=", token2->str.len) == 0 || + astrcmp_n(token2->str.array, "<", token2->str.len) == 0 || + astrcmp_n(token2->str.array, "<=", token2->str.len) == 0 || + astrcmp_n(token2->str.array, ">", token2->str.len) == 0 || + astrcmp_n(token2->str.array, ">=", token2->str.len) == 0) + return true; + return false; +} + +inline void ShaderBuilder::WriteFunctionContent(struct cf_token *&token, + const char *end) +{ + string temp; + if (token->type != CFTOKEN_NAME) + output.write(token->str.array, token->str.len); + + else if ((!WriteTypeToken(token) && !WriteIntrinsic(token) && + !WriteConstantVariable(token))) { + temp = string(token->str.array, token->str.len); + output << temp; + } + + bool dot = false; + bool cmp = false; + while (token->type != CFTOKEN_NONE) { + token++; + + if (strref_cmp(&token->str, end) == 0) + break; + + if (token->type == CFTOKEN_NAME) { + if (!WriteTypeToken(token) && !WriteIntrinsic(token) && + (dot || !WriteConstantVariable(token))) { + if (dot) + dot = false; + + bool cmp2 = IsNextCompareOperator(token); + if (cmp2) + output << "all("; + + temp = string(token->str.array, token->str.len); + output << temp; + + if (cmp) { + output << ")"; + cmp = false; + } + + cmp = cmp2; + } + + } else if (token->type == CFTOKEN_OTHER) { + if (*token->str.array == '{') + WriteFunctionContent(token, "}"); + else if (*token->str.array == '(') { + WriteFunctionContent(token, ")"); + WriteFunctionAdditionalParam(temp); + } else if (*token->str.array == '.') + dot = true; + + output.write(token->str.array, token->str.len); + + } else + output.write(token->str.array, token->str.len); + } +} + +inline void ShaderBuilder::WriteFunction(const shader_func *func) +{ + string funcName(func->name); + + const bool isMain = funcName == "main"; + if (isMain) { + if (isVertexShader()) + output << "vertex "; + else if (isPixelShader()) + output << "fragment "; + else + throw "Failed to add shader prefix"; + + funcName = "_main"; + } + + ShaderFunctionInfo info = {}; + struct cf_token *token = func->start; + AnalysisFunction(token, "}", info); + unique(info.useTextures.begin(), info.useTextures.end()); + unique(info.useSamplers.begin(), info.useSamplers.end()); + functionInfo.emplace(funcName, info); + + output << func->return_type << ' ' << funcName << '('; + + bool isFirst = true; + for (struct shader_var *param = func->params.array; + param != func->params.array + func->params.num; + param++) { + if (!isFirst) + output << ", "; + + WriteVariable(param); + + if (isMain) { + if (!isFirst) + throw "Failed to add type"; + output << " [[stage_in]]"; + + } + + if (isFirst) + isFirst = false; + } + + if (constantNames.size() != 0 && (isMain || info.useUniform)) + { + if (!isFirst) + output << ", "; + + output << "constant " << UNIFORM_DATA_NAME << " &uniforms"; + + if (isMain) + output << " [[buffer(30)]]"; + + if (isFirst) + isFirst = false; + } + + if (isPixelShader()) + { + size_t textureId = 0; + for (auto var = textureVars.cbegin(); + var != textureVars.cend(); + var++) { + if (!isMain) { + bool additional = false; + for (auto tex = info.useTextures.cbegin(); + tex != info.useTextures.cend(); + tex++) { + if (*tex == (*var)->name) { + additional = true; + break; + } + } + if (!additional) + continue; + } + + if (!isFirst) + output << ", "; + + WriteVariable(*var); + + if (isMain) + output << " [[texture(" << textureId++ << ")]]"; + + if (isFirst) + isFirst = false; + } + +#if USE_PROGRAMMABLE_SAMPLER + size_t samplerId = 0; + for (struct shader_sampler *sampler = parser->samplers.array; + sampler != parser->samplers.array + parser->samplers.num; + sampler++) { + if (!isMain) { + bool additional = false; + for (auto s = info.useSamplers.cbegin(); + s != info.useSamplers.cend(); + s++) { + if (*s == sampler->name) { + additional = true; + break; + } + } + if (!additional) + continue; + } + + if (!isFirst) + output << ", "; + + output << "sampler " << sampler->name; + + if (isMain) + output << " [[sampler(" << samplerId++ << ")]]"; + + if (isFirst) + isFirst = false; + } +#endif + } + + output << ")" << endl; + + token = func->start; + WriteFunctionContent(token, "}"); + + output << '}' << endl << endl; +} + +inline void ShaderBuilder::WriteFunctions() +{ + for (struct shader_func *func = parser->funcs.array; + func != parser->funcs.array + parser->funcs.num; + func++) + WriteFunction(func); +} + +void ShaderBuilder::Build(string &outputString) +{ + WriteInclude(); +#if !USE_PROGRAMMABLE_SAMPLER + WriteSamplers(); +#endif + WriteVariables(); + WriteStructs(); + WriteFunctions(); + outputString = string(output.str(), output.pcount()); + output.freeze(false); +} + +string build_shader(gs_shader_type type, ShaderParser *parser) +{ + string output; + ShaderBuilder(type, parser).Build(output); + return output; +} diff --git a/libobs-metal/metal-shaderprocessor.hpp b/libobs-metal/metal-shaderprocessor.hpp new file mode 100644 index 000000000..18218db20 --- /dev/null +++ b/libobs-metal/metal-shaderprocessor.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include + +struct ShaderBufferInfo { + bool normals = false; + bool colors = false; + bool tangents = false; + uint32_t texUnits = 0; +}; + +struct ShaderParser : shader_parser { + inline ShaderParser() {shader_parser_init(this);} + inline ~ShaderParser() {shader_parser_free(this);} +}; + +#ifdef __OBJC__ +struct ShaderProcessor { + gs_device_t *device; + ShaderParser parser; + + void BuildVertexDesc(__weak MTLVertexDescriptor *vertexDesc); + void BuildParamInfo(ShaderBufferInfo &info); + void BuildParams(std::vector ¶ms); + void BuildSamplers(std::vector> + &samplers); + std::string BuildString(gs_shader_type type); + void Process(const char *shader_string, const char *file); + + inline ShaderProcessor(gs_device_t *device) : device(device) + { + } +}; +#endif + +extern std::string build_shader(gs_shader_type type, ShaderParser *parser); diff --git a/libobs-metal/metal-shaderprocessor.mm b/libobs-metal/metal-shaderprocessor.mm new file mode 100644 index 000000000..df58b46e4 --- /dev/null +++ b/libobs-metal/metal-shaderprocessor.mm @@ -0,0 +1,186 @@ +#include "metal-subsystem.hpp" +#include "metal-shaderprocessor.hpp" + +#include + +using namespace std; + +static inline void AddInputLayoutVar(shader_var *var, + MTLVertexAttributeDescriptor *vad, + MTLVertexBufferLayoutDescriptor *vbld) +{ + if (strcmp(var->mapping, "COLOR") == 0) { + vad.format = MTLVertexFormatUChar4Normalized; + vbld.stride = sizeof(vec4); + + } else if (strcmp(var->mapping, "POSITION") == 0 || + strcmp(var->mapping, "NORMAL") == 0 || + strcmp(var->mapping, "TANGENT") == 0) { + vad.format = MTLVertexFormatFloat4; + vbld.stride = sizeof(vec4); + + } else if (astrcmp_n(var->mapping, "TEXCOORD", 8) == 0) { + /* type is always a 'float' type */ + switch (var->type[5]) { + case 0: + vad.format = MTLVertexFormatFloat; + vbld.stride = sizeof(float); + break; + + case '2': + vad.format = MTLVertexFormatFloat2; + vbld.stride = sizeof(float) * 2; + break; + + case '3': + vad.format = MTLVertexFormatFloat3; + vbld.stride = sizeof(vec3); + break; + + case '4': + vad.format = MTLVertexFormatFloat4; + vbld.stride = sizeof(vec4); + break; + } + } +} + +static inline void BuildVertexDescFromVars(shader_parser *parser, darray *vars, + __weak MTLVertexDescriptor *vd, size_t &index) +{ + shader_var *array = (shader_var*)vars->array; + + for (size_t i = 0; i < vars->num; i++) { + shader_var *var = array + i; + + if (var->mapping) { + vd.attributes[index].bufferIndex = index; + AddInputLayoutVar(var, vd.attributes[index], + vd.layouts[index++]); + } else { + shader_struct *st = shader_parser_getstruct(parser, + var->type); + if (st) + BuildVertexDescFromVars(parser, &st->vars.da, + vd, index); + } + } +} + +void ShaderProcessor::BuildVertexDesc(__weak MTLVertexDescriptor *vertexDesc) +{ + shader_func *func = shader_parser_getfunc(&parser, "main"); + if (!func) + throw "Failed to find 'main' shader function"; + + size_t index = 0; + BuildVertexDescFromVars(&parser, &func->params.da, vertexDesc, index); +} + +static inline void BuildParamInfoFromVars(shader_parser *parser, darray *vars, + ShaderBufferInfo &info) +{ + shader_var *array = (shader_var*)vars->array; + + for (size_t i = 0; i < vars->num; i++) { + shader_var *var = array + i; + + if (var->mapping) { + if (strcmp(var->mapping, "NORMAL") == 0) + info.normals = true; + else if (strcmp(var->mapping, "TANGENT") == 0) + info.tangents = true; + else if (strcmp(var->mapping, "COLOR") == 0) + info.colors = true; + else if (astrcmp_n(var->mapping, "TEXCOORD", 8) == 0) + info.texUnits++; + + } else { + shader_struct *st = shader_parser_getstruct(parser, + var->type); + if (st) + BuildParamInfoFromVars(parser, &st->vars.da, + info); + } + } +} + +void ShaderProcessor::BuildParamInfo(ShaderBufferInfo &info) +{ + shader_func *func = shader_parser_getfunc(&parser, "main"); + if (!func) + throw "Failed to find 'main' shader function"; + + BuildParamInfoFromVars(&parser, &func->params.da, info); +} + +gs_shader_param::gs_shader_param(shader_var &var, uint32_t &texCounter) + : name (var.name), + type (get_shader_param_type(var.type)), + arrayCount (var.array_count), + textureID (texCounter), + changed (false) +{ + defaultValue.resize(var.default_val.num); + memcpy(defaultValue.data(), var.default_val.array, var.default_val.num); + + if (type == GS_SHADER_PARAM_TEXTURE) + texCounter++; + else + textureID = 0; +} + +static inline void AddParam(shader_var &var, vector ¶ms, + uint32_t &texCounter) +{ + if (var.var_type != SHADER_VAR_UNIFORM || + strcmp(var.type, "sampler") == 0) + return; + + params.push_back(gs_shader_param(var, texCounter)); +} + +void ShaderProcessor::BuildParams(vector ¶ms) +{ + uint32_t texCounter = 0; + + for (struct shader_var *var = parser.params.array; + var != parser.params.array + parser.params.num; + var++) + AddParam(*var, params, texCounter); +} + +static inline void AddSampler(gs_device_t *device, shader_sampler &sampler, + vector> &samplers) +{ + gs_sampler_info si; + shader_sampler_convert(&sampler, &si); + samplers.emplace_back(new ShaderSampler(sampler.name, device, &si)); +} + +void ShaderProcessor::BuildSamplers(vector> &samplers) +{ + for (struct shader_sampler *sampler = parser.samplers.array; + sampler != parser.samplers.array + parser.samplers.num; + sampler++) + AddSampler(device, *sampler, samplers); +} + +string ShaderProcessor::BuildString(gs_shader_type type) +{ + return build_shader(type, &parser); +} + +void ShaderProcessor::Process(const char *shader_string, const char *file) +{ + bool success = shader_parse(&parser, shader_string, file); + char *str = shader_parser_geterrors(&parser); + if (str) { + blog(LOG_WARNING, "Shader parser errors/warnings:\n%s\n", str); + bfree(str); + } + + if (!success) + throw "Failed to parse shader"; +} + diff --git a/libobs-metal/metal-stagesurf.mm b/libobs-metal/metal-stagesurf.mm new file mode 100644 index 000000000..023dfdd27 --- /dev/null +++ b/libobs-metal/metal-stagesurf.mm @@ -0,0 +1,33 @@ +#include "metal-subsystem.hpp" + +void gs_stage_surface::DownloadTexture() +{ + MTLRegion from = MTLRegionMake2D(0, 0, width, height); + [texture getBytes:data.data() bytesPerRow:bytePerRow + fromRegion:from mipmapLevel:0]; +} + +void gs_stage_surface::InitTexture() +{ + texture = [device->device newTextureWithDescriptor:textureDesc]; + if (texture == nil) + throw "Failed to create staging surface"; +} + +gs_stage_surface::gs_stage_surface(gs_device_t *device, uint32_t width, + uint32_t height, gs_color_format colorFormat) + : gs_obj (device, gs_type::gs_stage_surface), + width (width), + height (height), + bytePerRow (width * gs_get_format_bpp(colorFormat) / 8), + format (colorFormat) +{ + textureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: + ConvertGSTextureFormat(colorFormat) + width:width height:height mipmapped:NO]; + textureDesc.storageMode = MTLStorageModeManaged; + + data.resize(height * bytePerRow); + + InitTexture(); +} diff --git a/libobs-metal/metal-subsystem.hpp b/libobs-metal/metal-subsystem.hpp new file mode 100644 index 000000000..a6d487a52 --- /dev/null +++ b/libobs-metal/metal-subsystem.hpp @@ -0,0 +1,706 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef __OBJC__ +#import +#import +#import + +struct shader_var; +struct gs_vertex_shader; + + +static inline MTLPixelFormat ConvertGSTextureFormat(gs_color_format format) +{ + switch (format) { + case GS_UNKNOWN: return MTLPixelFormatInvalid; + case GS_A8: return MTLPixelFormatA8Unorm; + case GS_R8: return MTLPixelFormatR8Unorm; + case GS_RGBA: return MTLPixelFormatRGBA8Unorm; + case GS_BGRX: return MTLPixelFormatBGRA8Unorm; + case GS_BGRA: return MTLPixelFormatBGRA8Unorm; + case GS_R10G10B10A2: return MTLPixelFormatRGB10A2Unorm; + case GS_RGBA16: return MTLPixelFormatRGBA16Unorm; + case GS_R16: return MTLPixelFormatR16Unorm; + case GS_RGBA16F: return MTLPixelFormatRGBA16Float; + case GS_RGBA32F: return MTLPixelFormatRGBA32Float; + case GS_RG16F: return MTLPixelFormatRG16Float; + case GS_RG32F: return MTLPixelFormatRG32Float; + case GS_R16F: return MTLPixelFormatR16Float; + case GS_R32F: return MTLPixelFormatR32Float; + case GS_DXT1: return MTLPixelFormatBC1_RGBA; + case GS_DXT3: return MTLPixelFormatBC2_RGBA; + case GS_DXT5: return MTLPixelFormatBC3_RGBA; + } + + return MTLPixelFormatInvalid; +} + +static inline gs_color_format ConvertMTLTextureFormat(MTLPixelFormat format) +{ + switch ((NSUInteger)format) { + case MTLPixelFormatA8Unorm: return GS_A8; + case MTLPixelFormatR8Unorm: return GS_R8; + case MTLPixelFormatRGBA8Unorm: return GS_RGBA; + case MTLPixelFormatBGRA8Unorm: return GS_BGRA; + case MTLPixelFormatRGB10A2Unorm: return GS_R10G10B10A2; + case MTLPixelFormatRGBA16Unorm: return GS_RGBA16; + case MTLPixelFormatR16Unorm: return GS_R16; + case MTLPixelFormatRGBA16Float: return GS_RGBA16F; + case MTLPixelFormatRGBA32Float: return GS_RGBA32F; + case MTLPixelFormatRG16Float: return GS_RG16F; + case MTLPixelFormatRG32Float: return GS_RG32F; + case MTLPixelFormatR16Float: return GS_R16F; + case MTLPixelFormatR32Float: return GS_R32F; + case MTLPixelFormatBC1_RGBA: return GS_DXT1; + case MTLPixelFormatBC2_RGBA: return GS_DXT3; + case MTLPixelFormatBC3_RGBA: return GS_DXT5; + } + + return GS_UNKNOWN; +} + +static inline gs_color_format ConvertOSTypePixelFormat(OSType format) +{ + if (format == 'BGRA') return GS_BGRA; + if (format == 'w30r') return GS_R10G10B10A2; + return GS_UNKNOWN; +} + +static inline MTLCompareFunction ConvertGSDepthTest(gs_depth_test test) +{ + switch (test) { + case GS_NEVER: return MTLCompareFunctionNever; + case GS_LESS: return MTLCompareFunctionLess; + case GS_LEQUAL: return MTLCompareFunctionLessEqual; + case GS_EQUAL: return MTLCompareFunctionEqual; + case GS_GEQUAL: return MTLCompareFunctionGreaterEqual; + case GS_GREATER: return MTLCompareFunctionGreater; + case GS_NOTEQUAL: return MTLCompareFunctionNotEqual; + case GS_ALWAYS: return MTLCompareFunctionAlways; + } + + return MTLCompareFunctionNever; +} + +static inline MTLStencilOperation ConvertGSStencilOp(gs_stencil_op_type op) +{ + switch (op) { + case GS_KEEP: return MTLStencilOperationKeep; + case GS_ZERO: return MTLStencilOperationZero; + case GS_REPLACE: return MTLStencilOperationReplace; + case GS_INCR: return MTLStencilOperationIncrementWrap; + case GS_DECR: return MTLStencilOperationDecrementWrap; + case GS_INVERT: return MTLStencilOperationInvert; + } + + return MTLStencilOperationKeep; +} + +static inline MTLBlendFactor ConvertGSBlendType(gs_blend_type type) +{ + switch (type) { + case GS_BLEND_ZERO: + return MTLBlendFactorZero; + case GS_BLEND_ONE: + return MTLBlendFactorOne; + case GS_BLEND_SRCCOLOR: + return MTLBlendFactorSourceColor; + case GS_BLEND_INVSRCCOLOR: + return MTLBlendFactorOneMinusSourceColor; + case GS_BLEND_SRCALPHA: + return MTLBlendFactorSourceAlpha; + case GS_BLEND_INVSRCALPHA: + return MTLBlendFactorOneMinusSourceAlpha; + case GS_BLEND_DSTCOLOR: + return MTLBlendFactorDestinationColor; + case GS_BLEND_INVDSTCOLOR: + return MTLBlendFactorOneMinusDestinationColor; + case GS_BLEND_DSTALPHA: + return MTLBlendFactorDestinationAlpha; + case GS_BLEND_INVDSTALPHA: + return MTLBlendFactorOneMinusDestinationAlpha; + case GS_BLEND_SRCALPHASAT: + return MTLBlendFactorSourceAlphaSaturated; + } + + return MTLBlendFactorOne; +} + +static inline MTLCullMode ConvertGSCullMode(gs_cull_mode mode) +{ + switch (mode) { + case GS_BACK: return MTLCullModeBack; + case GS_FRONT: return MTLCullModeFront; + case GS_NEITHER: return MTLCullModeNone; + } + + return MTLCullModeBack; +} + +static inline MTLPrimitiveType ConvertGSTopology(gs_draw_mode mode) +{ + switch (mode) { + case GS_POINTS: return MTLPrimitiveTypePoint; + case GS_LINES: return MTLPrimitiveTypeLine; + case GS_LINESTRIP: return MTLPrimitiveTypeLineStrip; + case GS_TRIS: return MTLPrimitiveTypeTriangle; + case GS_TRISTRIP: return MTLPrimitiveTypeTriangleStrip; + } + + return MTLPrimitiveTypePoint; +} + +static inline MTLViewport ConvertGSRectToMTLViewport(gs_rect rect) +{ + MTLViewport ret; + ret.originX = rect.x; + ret.originY = rect.y; + ret.width = rect.cx; + ret.height = rect.cy; + ret.znear = 0.0; + ret.zfar = 1.0; + return ret; +} + +static inline MTLScissorRect ConvertGSRectToMTLScissorRect(gs_rect rect) +{ + MTLScissorRect ret; + ret.x = rect.x; + ret.y = rect.y; + ret.width = rect.cx; + ret.height = rect.cy; + return ret; +} + +enum class gs_type { + gs_vertex_buffer, + gs_index_buffer, + gs_texture_2d, + gs_zstencil_buffer, + gs_stage_surface, + gs_sampler_state, + gs_vertex_shader, + gs_pixel_shader, + gs_swap_chain, +}; + +struct gs_obj { + gs_device_t *device; + gs_type obj_type; + gs_obj *next; + gs_obj **prev_next; + + inline gs_obj() : + device (nullptr), + next (nullptr), + prev_next (nullptr) + { + } + + gs_obj(gs_device_t *device, gs_type type); + virtual ~gs_obj(); +}; + +struct gs_vertex_buffer : gs_obj { + const bool isDynamic; + const std::unique_ptr vbData; + + id vertexBuffer; + id normalBuffer; + id colorBuffer; + id tangentBuffer; + std::vector> uvBuffers; + + inline id PrepareBuffer(void *array, size_t elementSize, + __weak NSString *name); + void PrepareBuffers(); + + void MakeBufferList(gs_vertex_shader *shader, + std::vector> &buffers); + + inline id InitBuffer(size_t elementSize, void *array, + const char *name); + void InitBuffers(); + + inline void Release() + { + vertexBuffer = nil; + normalBuffer = nil; + colorBuffer = nil; + tangentBuffer = nil; + uvBuffers.clear(); + } + void Rebuild(); + + gs_vertex_buffer(gs_device_t *device, struct gs_vb_data *data, + uint32_t flags); +}; + +struct gs_index_buffer : gs_obj { + const gs_index_type type; + const bool isDynamic; + const std::unique_ptr indices; + const size_t num = 0, len = 0; + const MTLIndexType indexType; + + id indexBuffer; + + void PrepareBuffer(); + void InitBuffer(); + + inline void Release() {indexBuffer = nil;} + void Rebuild(); + + gs_index_buffer(gs_device_t *device, enum gs_index_type type, + void *indices, size_t num, uint32_t flags); +}; + +struct gs_texture : gs_obj { + const gs_texture_type type = GS_TEXTURE_2D; + const uint32_t levels; + const gs_color_format format = GS_UNKNOWN; + + inline gs_texture(gs_device *device, gs_type obj_type, + gs_texture_type type, + uint32_t levels, gs_color_format format) + : gs_obj (device, obj_type), + type (type), + levels (levels), + format (format) + { + } +}; + +struct gs_texture_2d : gs_texture { + const uint32_t width = 0, height = 0, bytePerRow = 0; + const bool isRenderTarget = false; + const bool isDynamic = false; + const bool genMipmaps = false; + const bool isShared = false; + const MTLPixelFormat mtlPixelFormat = MTLPixelFormatInvalid; + + MTLTextureDescriptor *textureDesc = nil; + id texture = nil; + + std::vector> data; + + void GenerateMipmap(); + void BackupTexture(const uint8_t **data); + void UploadTexture(); + void InitTexture(); + + inline void Release() {texture = nil;} + void Rebuild(); + + gs_texture_2d(gs_device_t *device, uint32_t width, uint32_t height, + gs_color_format colorFormat, uint32_t levels, + const uint8_t **data, uint32_t flags, + gs_texture_type type); + + gs_texture_2d(gs_device_t *device, id texture); +}; + +struct gs_zstencil_buffer : gs_obj { + const uint32_t width = 0, height = 0; + const gs_zstencil_format format = GS_ZS_NONE; + + MTLTextureDescriptor *textureDesc; + id texture; + + void InitBuffer(); + + inline void Release() {texture = nil;} + inline void Rebuild() {InitBuffer();} + + gs_zstencil_buffer(gs_device_t *device, uint32_t width, + uint32_t height, gs_zstencil_format format); +}; + +struct gs_stage_surface : gs_obj { + const uint32_t width = 0, height = 0, bytePerRow = 0; + const gs_color_format format = GS_UNKNOWN; + + MTLTextureDescriptor *textureDesc; + id texture; + + std::vector data; + + void DownloadTexture(); + void InitTexture(); + + inline void Release() {texture = nil;} + inline void Rebuild() {InitTexture();}; + + gs_stage_surface(gs_device_t *device, uint32_t width, uint32_t height, + gs_color_format colorFormat); +}; + +struct gs_sampler_state : gs_obj { + const gs_sampler_info info; + + MTLSamplerDescriptor *samplerDesc; + id samplerState; + + void InitSampler(); + + inline void Release() {samplerState = nil;} + inline void Rebuild() {InitSampler();} + + gs_sampler_state(gs_device_t *device, const gs_sampler_info *info); +}; + +struct gs_shader_param { + const std::string name; + const gs_shader_param_type type; + const int arrayCount; + + struct gs_sampler_state *nextSampler = nullptr; + + uint32_t textureID; + size_t pos; + + std::vector curValue; + std::vector defaultValue; + bool changed; + + gs_shader_param(shader_var &var, uint32_t &texCounter); +}; + +struct ShaderError { + const std::string error; + + inline ShaderError(NSError *error) + : error (error.localizedDescription.UTF8String) + { + } +}; + +struct gs_shader : gs_obj { + const gs_shader_type type; + std::string source; + id library; + id function; + std::vector params; + + size_t constantSize = 0; + + std::vector data; + + inline void UpdateParam(uint8_t *data, gs_shader_param ¶m); + void UploadParams(id commandEncoder); + + void BuildConstantBuffer(); + void Compile(std::string shaderStr); + + inline void Release() + { + function = nil; + library = nil; + } + void Rebuild(); + + inline gs_shader(gs_device_t *device, gs_type obj_type, + gs_shader_type type) + : gs_obj (device, obj_type), + type (type) + { + } +}; + +struct gs_vertex_shader : gs_shader { + MTLVertexDescriptor *vertexDesc; + + bool hasNormals; + bool hasColors; + bool hasTangents; + uint32_t texUnits; + + gs_shader_param *world, *viewProj; + + inline uint32_t NumBuffersExpected() const + { + uint32_t count = texUnits + 1; + if (hasNormals) count++; + if (hasColors) count++; + if (hasTangents) count++; + + return count; + } + + gs_vertex_shader(gs_device_t *device, const char *file, + const char *shaderString); +}; + +struct ShaderSampler { + std::string name; + gs_sampler_state sampler; + + inline ShaderSampler(const char *name, gs_device_t *device, + gs_sampler_info *info) + : name (name), + sampler (device, info) + { + } +}; + +struct gs_pixel_shader : gs_shader { + std::vector> samplers; + + inline void GetSamplerStates(gs_sampler_state **states) + { + size_t i; + for (i = 0; i < samplers.size(); i++) + states[i] = &samplers[i]->sampler; + for (; i < GS_MAX_TEXTURES; i++) + states[i] = nullptr; + } + + gs_pixel_shader(gs_device_t *device, const char *file, + const char *shaderString); +}; + +struct gs_swap_chain : gs_obj { + uint32_t numBuffers; + NSView *view; + CAMetalLayer *metalLayer; + + gs_init_data initData; + id nextDrawable; + std::unique_ptr nextTarget; + + gs_texture_2d *GetTarget(); + gs_texture_2d *NextTarget(); + void Resize(uint32_t cx, uint32_t cy); + + + inline void Release() + { + nextTarget.reset(); + nextDrawable = nil; + } + void Rebuild(); + + gs_swap_chain(gs_device *device, const gs_init_data *data); +}; + +struct ClearState { + uint32_t flags; + struct vec4 color; + float depth; + uint8_t stencil; + + inline ClearState() + : flags (0), + color ({}), + depth (0.0f), + stencil (0) + { + } +}; + +struct BlendState { + bool blendEnabled; + gs_blend_type srcFactorC; + gs_blend_type destFactorC; + gs_blend_type srcFactorA; + gs_blend_type destFactorA; + + bool redEnabled; + bool greenEnabled; + bool blueEnabled; + bool alphaEnabled; + + inline BlendState() + : blendEnabled (true), + srcFactorC (GS_BLEND_SRCALPHA), + destFactorC (GS_BLEND_INVSRCALPHA), + srcFactorA (GS_BLEND_ONE), + destFactorA (GS_BLEND_ONE), + redEnabled (true), + greenEnabled (true), + blueEnabled (true), + alphaEnabled (true) + { + } + + inline BlendState(const BlendState &state) + { + memcpy(this, &state, sizeof(BlendState)); + } +}; + +struct RasterState { + gs_rect viewport; + gs_cull_mode cullMode; + bool scissorEnabled; + gs_rect scissorRect; + + MTLViewport mtlViewport; + MTLCullMode mtlCullMode; + MTLScissorRect mtlScissorRect; + + inline RasterState() + : viewport (), + cullMode (GS_BACK), + scissorEnabled (false), + scissorRect (), + mtlCullMode (MTLCullModeBack) + { + } + + inline RasterState(const RasterState &state) + { + memcpy(this, &state, sizeof(RasterState)); + } +}; + +struct StencilSide { + gs_depth_test test; + gs_stencil_op_type fail; + gs_stencil_op_type zfail; + gs_stencil_op_type zpass; + + inline StencilSide() + : test (GS_ALWAYS), + fail (GS_KEEP), + zfail (GS_KEEP), + zpass (GS_KEEP) + { + } +}; + +struct ZStencilState { + bool depthEnabled; + bool depthWriteEnabled; + gs_depth_test depthFunc; + + bool stencilEnabled; + bool stencilWriteEnabled; + StencilSide stencilFront; + StencilSide stencilBack; + + MTLDepthStencilDescriptor *dsd; + + inline ZStencilState() + : depthEnabled (true), + depthWriteEnabled (true), + depthFunc (GS_LESS), + stencilEnabled (false), + stencilWriteEnabled (true) + { + dsd = [[MTLDepthStencilDescriptor alloc] init]; + } + + inline ZStencilState(const ZStencilState &state) + { + memcpy(this, &state, sizeof(ZStencilState)); + } +}; + +struct gs_device { + uint32_t devIdx = 0; + uint16_t featureSetFamily = 0; + uint16_t featureSetVersion = 0; + + MTLRenderPassDescriptor *passDesc; + MTLRenderPipelineDescriptor *pipelineDesc; + + id device; + id commandQueue; + id commandBuffer; + id pipelineState; + id depthStencilState; + + gs_texture_2d *curRenderTarget = nullptr; + int curRenderSide = 0; + gs_zstencil_buffer *curZStencilBuffer = nullptr; + gs_texture *curTextures[GS_MAX_TEXTURES] = {}; + gs_sampler_state *curSamplers[GS_MAX_TEXTURES] = {}; + gs_vertex_buffer *curVertexBuffer = nullptr; + gs_index_buffer *curIndexBuffer = nullptr; + gs_vertex_shader *curVertexShader = nullptr; + gs_pixel_shader *curPixelShader = nullptr; + gs_swap_chain *curSwapChain = nullptr; + gs_stage_surface *curStageSurface = nullptr; + + gs_vertex_buffer *lastVertexBuffer = nullptr; + gs_vertex_shader *lastVertexShader = nullptr; + + gs_texture_2d *preserveClearTarget = nullptr; + std::stack> clearStates; + + bool piplineStateChanged = false; + BlendState blendState; + RasterState rasterState; + ZStencilState zstencilState; + + std::stack projStack; + + matrix4 curProjMatrix; + matrix4 curViewMatrix; + matrix4 curViewProjMatrix; + + gs_obj *first_obj = nullptr; + + std::mutex mutexObj; + std::vector> curBufferPool; + std::queue>> bufferPools; + std::vector> unusedBufferPool; + + void InitDevice(uint32_t adapterIdx); + + /* Create Draw Command */ + void SetClear(); + void LoadSamplers(id commandEncoder); + void LoadRasterState(id commandEncoder); + void LoadZStencilState(id commandEncoder); + void UpdateViewProjMatrix(); + void UploadVertexBuffer(id commandEncoder); + void UploadTextures(id commandEncoder); + void UploadSamplers(id commandEncoder); + void DrawPrimitives(id commandEncoder, + gs_draw_mode drawMode, + uint32_t startVert, uint32_t numVerts); + void Draw(gs_draw_mode drawMode, uint32_t startVert, uint32_t numVerts); + + /* Buffer Management */ + id GetBuffer(void *data, size_t length); + void PushResources(); + void ReleaseResources(); + + /* Other */ + void RebuildDevice(); + inline void CopyTex(id dst, + uint32_t dst_x, uint32_t dst_y, + gs_texture_t *src, uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h); + + gs_device(uint32_t adapterIdx); +}; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef libobs_metal_EXPORTS +void device_clear_textures(gs_device_t *device); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/libobs-metal/metal-subsystem.mm b/libobs-metal/metal-subsystem.mm new file mode 100644 index 000000000..6838d1bbd --- /dev/null +++ b/libobs-metal/metal-subsystem.mm @@ -0,0 +1,1448 @@ +#include +#include +#include + +#include "metal-subsystem.hpp" + +using namespace std; + +gs_obj::gs_obj(gs_device_t *device_, gs_type type) : + device (device_), + obj_type (type) +{ + prev_next = &device->first_obj; + next = device->first_obj; + device->first_obj = this; + if (next) + next->prev_next = &next; +} + +gs_obj::~gs_obj() +{ + if (prev_next) + *prev_next = next; + if (next) + next->prev_next = prev_next; +} + +const char *device_get_name(void) +{ + return "Metal"; +} + +int device_get_type(void) +{ + return GS_DEVICE_METAL; +} + +const char *device_preprocessor_name(void) +{ + return "_Metal"; +} + +static inline void EnumMetalAdapters( + bool (*callback)(void*, const char*, uint32_t), + void *param) +{ + uint32_t i = 0; + NSArray *devices = MTLCopyAllDevices(); + if (devices == nil) + return; + + for (id device in devices) { + if (!callback(param, [device name].UTF8String, i++)) + break; + } +} + +bool device_enum_adapters( + bool (*callback)(void *param, const char *name, uint32_t id), + void *param) +{ + EnumMetalAdapters(callback, param); + return true; +} + +static inline void CheckMetalSupport() +{ + if (NSProtocolFromString(@"MTLDevice") == nil) + throw "This device doesn't support Metal."; +} + +static inline void LogMetalAdapters() +{ + blog(LOG_INFO, "Available Video Adapters: "); + + NSArray *devices = MTLCopyAllDevices(); + if (devices == nil) + return; + + for (size_t i = 0; i < devices.count; i++) { + id device = devices[i]; + blog(LOG_INFO, "\tAdapter %zu: %s", i, + [device name].UTF8String); + } +} + +int device_create(gs_device_t **p_device, uint32_t adapter) +{ + int errorcode = GS_SUCCESS; + + gs_device *device = nullptr; + try { + CheckMetalSupport(); + + blog(LOG_INFO, "---------------------------------"); + blog(LOG_INFO, "Initializing Metal..."); + LogMetalAdapters(); + + device = new gs_device(adapter); + + } catch (const char *error) { + blog(LOG_ERROR, "device_create (Metal): %s", error); + errorcode = GS_ERROR_NOT_SUPPORTED; + } + + *p_device = device; + return errorcode; +} + +void device_destroy(gs_device_t *device) +{ + delete device; +} + +void device_enter_context(gs_device_t *device) +{ + /* does nothing */ + UNUSED_PARAMETER(device); +} + +void device_leave_context(gs_device_t *device) +{ + /* does nothing */ + UNUSED_PARAMETER(device); +} + +gs_swapchain_t *device_swapchain_create(gs_device_t *device, + const struct gs_init_data *data) +{ + gs_swap_chain *swap = nullptr; + try { + swap = new gs_swap_chain(device, data); + + } catch (const char *error) { + blog(LOG_ERROR, "device_swapchain_create (Metal): %s", error); + } + + return swap; +} + +void device_resize(gs_device_t *device, uint32_t cx, uint32_t cy) +{ + if (device->curSwapChain == nullptr) { + blog(LOG_WARNING, "device_resize (Metal): No active swap"); + return; + } + + try { + id renderTarget = nil; + id zstencilTarget = nil; + + device->passDesc.colorAttachments[0].texture = nil; + device->passDesc.depthAttachment.texture = nil; + device->passDesc.stencilAttachment.texture = nil; + device->curSwapChain->Resize(cx, cy); + + if (device->curRenderTarget) + renderTarget = device->curRenderTarget->texture; + if (device->curZStencilBuffer) + zstencilTarget = device->curZStencilBuffer->texture; + + device->passDesc.colorAttachments[0].texture = renderTarget; + device->passDesc.depthAttachment.texture = zstencilTarget; + device->passDesc.stencilAttachment.texture = zstencilTarget; + + } catch (const char *error) { + blog(LOG_ERROR, "device_resize (Metal): %s", error); + } +} + +void device_get_size(const gs_device_t *device, uint32_t *cx, uint32_t *cy) +{ + if (device->curSwapChain) { + CGSize curSize = device->curSwapChain->metalLayer.drawableSize; + *cx = curSize.width; + *cy = curSize.height; + } else { + blog(LOG_ERROR, "device_get_size (Metal): No active swap"); + *cx = 0; + *cy = 0; + } +} + +uint32_t device_get_width(const gs_device_t *device) +{ + if (device->curSwapChain) { + CGSize curSize = device->curSwapChain->metalLayer.drawableSize; + return curSize.width; + } else { + blog(LOG_ERROR, "device_get_size (Metal): No active swap"); + return 0; + } +} + +uint32_t device_get_height(const gs_device_t *device) +{ + if (device->curSwapChain) { + CGSize curSize = device->curSwapChain->metalLayer.drawableSize; + return curSize.height; + } else { + blog(LOG_ERROR, "device_get_size (Metal): No active swap"); + return 0; + } +} + +gs_texture_t *device_texture_create(gs_device_t *device, uint32_t width, + uint32_t height, enum gs_color_format color_format, + uint32_t levels, const uint8_t **data, uint32_t flags) +{ + gs_texture *texture = nullptr; + try { + texture = new gs_texture_2d(device, width, height, color_format, + levels, data, flags, GS_TEXTURE_2D); + + } catch (const char *error) { + blog(LOG_ERROR, "device_texture_create (Metal): %s", error); + } + + return texture; +} + +gs_texture_t *device_cubetexture_create(gs_device_t *device, uint32_t size, + enum gs_color_format color_format, uint32_t levels, + const uint8_t **data, uint32_t flags) +{ + gs_texture *texture = nullptr; + try { + texture = new gs_texture_2d(device, size, size, color_format, + levels, data, flags, GS_TEXTURE_CUBE); + + } catch (const char *error) { + blog(LOG_ERROR, "device_cubetexture_create (Metal): %s", error); + } + + return texture; +} + +gs_texture_t *device_voltexture_create(gs_device_t *device, uint32_t width, + uint32_t height, uint32_t depth, + enum gs_color_format color_format, uint32_t levels, + const uint8_t **data, uint32_t flags) +{ + /* TODO */ + UNUSED_PARAMETER(device); + UNUSED_PARAMETER(width); + UNUSED_PARAMETER(height); + UNUSED_PARAMETER(depth); + UNUSED_PARAMETER(color_format); + UNUSED_PARAMETER(levels); + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(flags); + return NULL; +} + +gs_zstencil_t *device_zstencil_create(gs_device_t *device, uint32_t width, + uint32_t height, enum gs_zstencil_format format) +{ + gs_zstencil_buffer *zstencil = nullptr; + try { + zstencil = new gs_zstencil_buffer(device, width, height, + format); + + } catch (const char *error) { + blog(LOG_ERROR, "device_zstencil_create (Metal): %s", error); + } + + return zstencil; +} + +gs_stagesurf_t *device_stagesurface_create(gs_device_t *device, uint32_t width, + uint32_t height, enum gs_color_format color_format) +{ + gs_stage_surface *surf = nullptr; + try { + surf = new gs_stage_surface(device, width, height, + color_format); + + } catch (const char *error) { + blog(LOG_ERROR, "device_stagesurface_create (Metal): %s", + error); + } + + return surf; +} + +gs_samplerstate_t *device_samplerstate_create(gs_device_t *device, + const struct gs_sampler_info *info) +{ + gs_sampler_state *ss = nullptr; + try { + ss = new gs_sampler_state(device, info); + + } catch (const char *error) { + blog(LOG_ERROR, "device_samplerstate_create (Metal): %s", + error); + } + + return ss; +} + +gs_shader_t *device_vertexshader_create(gs_device_t *device, + const char *shader_string, const char *file, + char **error_string) +{ + gs_vertex_shader *shader = nullptr; + try { + shader = new gs_vertex_shader(device, file, shader_string); + + } catch (ShaderError error) { + blog(LOG_ERROR, "device_vertexshader_create (Metal): " + "Compile warnings/errors for %s:\n%s", + file, + error.error.c_str()); + + } catch (const char *error) { + blog(LOG_ERROR, "device_vertexshader_create (Metal): %s", + error); + } + + return shader; + + UNUSED_PARAMETER(error_string); +} + +gs_shader_t *device_pixelshader_create(gs_device_t *device, + const char *shader_string, const char *file, + char **error_string) +{ + gs_pixel_shader *shader = nullptr; + try { + shader = new gs_pixel_shader(device, file, shader_string); + + } catch (ShaderError error) { + blog(LOG_ERROR, "device_pixelshader_create (Metal): " + "Compile warnings/errors for %s:\n%s", + file, + error.error.c_str()); + + } catch (const char *error) { + blog(LOG_ERROR, "device_pixelshader_create (Metal): %s", error); + } + + return shader; + + UNUSED_PARAMETER(error_string); +} + +gs_vertbuffer_t *device_vertexbuffer_create(gs_device_t *device, + struct gs_vb_data *data, uint32_t flags) +{ + gs_vertex_buffer *buffer = nullptr; + try { + buffer = new gs_vertex_buffer(device, data, flags); + + } catch (const char *error) { + blog(LOG_ERROR, "device_vertexbuffer_create (Metal): %s", + error); + } + + return buffer; +} + +gs_indexbuffer_t *device_indexbuffer_create(gs_device_t *device, + enum gs_index_type type, void *indices, size_t num, + uint32_t flags) +{ + gs_index_buffer *buffer = nullptr; + try { + buffer = new gs_index_buffer(device, type, indices, num, flags); + + } catch (const char *error) { + blog(LOG_ERROR, "device_indexbuffer_create (Metal): %s", error); + } + + return buffer; +} + +enum gs_texture_type device_get_texture_type(const gs_texture_t *texture) +{ + return texture->type; +} + +void device_load_vertexbuffer(gs_device_t *device, gs_vertbuffer_t *vertbuffer) +{ + if (device->curVertexBuffer == vertbuffer) + return; + + device->curVertexBuffer = vertbuffer; +} + +void device_load_indexbuffer(gs_device_t *device, gs_indexbuffer_t *indexbuffer) +{ + if (device->curIndexBuffer == indexbuffer) + return; + + device->curIndexBuffer = indexbuffer; +} + +void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) +{ + if (device->curTextures[unit] == tex) + return; + + device->curTextures[unit] = tex; +} + +void device_load_samplerstate(gs_device_t *device, + gs_samplerstate_t *samplerstate, int unit) +{ + if (device->curSamplers[unit] == samplerstate) + return; + + device->curSamplers[unit] = samplerstate; +} + +void device_load_vertexshader(gs_device_t *device, gs_shader_t *vertshader) +{ + id function = nil; + MTLVertexDescriptor *vertDesc = nil; + + if (device->curVertexShader == vertshader) + return; + + gs_vertex_shader *vs = static_cast(vertshader); + if (vertshader) { + if (vertshader->type != GS_SHADER_VERTEX) { + blog(LOG_ERROR, "device_load_vertexshader (Metal): " + "Specified shader is not a vertex " + "shader"); + return; + } + + function = vs->function; + vertDesc = vs->vertexDesc; + } + + device->curVertexShader = vs; + + device->pipelineDesc.vertexFunction = function; + device->pipelineDesc.vertexDescriptor = vertDesc; + + device->piplineStateChanged = true; +} + +void device_clear_textures(gs_device_t *device) +{ + memset(device->curTextures, 0, sizeof(device->curTextures)); +} + +void device_load_pixelshader(gs_device_t *device, gs_shader_t *pixelshader) +{ + id function = nil; + gs_sampler_state *states[GS_MAX_TEXTURES]; + + if (device->curPixelShader == pixelshader) + return; + + gs_pixel_shader *ps = static_cast(pixelshader); + if (pixelshader) { + if (pixelshader->type != GS_SHADER_PIXEL) { + blog(LOG_ERROR, "device_load_pixelshader (Metal): " + "Specified shader is not a pixel " + "shader"); + return; + } + + function = ps->function; + ps->GetSamplerStates(states); + } + + device_clear_textures(device); + + device->curPixelShader = ps; + for (size_t i = 0; i < GS_MAX_TEXTURES; i++) + device->curSamplers[i] = states[i]; + + device->pipelineDesc.fragmentFunction = function; + + device->piplineStateChanged = true; +} + +void device_load_default_samplerstate(gs_device_t *device, bool b_3d, int unit) +{ + /* TODO */ + UNUSED_PARAMETER(device); + UNUSED_PARAMETER(b_3d); + UNUSED_PARAMETER(unit); +} + +gs_shader_t *device_get_vertex_shader(const gs_device_t *device) +{ + return device->curVertexShader; +} + +gs_shader_t *device_get_pixel_shader(const gs_device_t *device) +{ + return device->curPixelShader; +} + +gs_texture_t *device_get_render_target(const gs_device_t *device) +{ + if (device->curSwapChain && + device->curRenderTarget == device->curSwapChain->GetTarget()) + return nullptr; + + return device->curRenderTarget; +} + +gs_zstencil_t *device_get_zstencil_target(const gs_device_t *device) +{ + return device->curZStencilBuffer; +} + +void device_set_render_target(gs_device_t *device, gs_texture_t *tex, + gs_zstencil_t *zstencil) +{ + if (device->curSwapChain) { + if (!tex) + tex = device->curSwapChain->GetTarget(); + } + + if (device->curRenderTarget == tex && + device->curZStencilBuffer == zstencil) + return; + + if (tex && tex->type != GS_TEXTURE_2D) { + blog(LOG_ERROR, "device_set_render_target (Metal): " + "texture is not a 2D texture"); + return; + } + + gs_texture_2d *tex2d = static_cast(tex); + if (tex2d && tex2d->texture == nil) { + blog(LOG_ERROR, "device_set_render_target (Metal): " + "texture is null"); + return; + } + + device->curRenderTarget = tex2d; + device->curRenderSide = 0; + device->curZStencilBuffer = zstencil; + + if (tex2d) { + device->passDesc.colorAttachments[0].texture = tex2d->texture; + device->pipelineDesc.colorAttachments[0].pixelFormat = + tex2d->mtlPixelFormat; + } else + device->passDesc.colorAttachments[0].texture = nil; + + if (zstencil) { + device->passDesc.depthAttachment.texture = zstencil->texture; + device->passDesc.stencilAttachment.texture = zstencil->texture; + device->pipelineDesc.depthAttachmentPixelFormat = + zstencil->textureDesc.pixelFormat; + device->pipelineDesc.stencilAttachmentPixelFormat = + zstencil->textureDesc.pixelFormat; + } else { + device->passDesc.depthAttachment.texture = nil; + device->passDesc.stencilAttachment.texture = nil; + } + + device->piplineStateChanged = true; +} + +void device_set_cube_render_target(gs_device_t *device, gs_texture_t *tex, + int side, gs_zstencil_t *zstencil) +{ + if (device->curSwapChain) { + if (!tex) + tex = device->curSwapChain->GetTarget(); + } + + if (device->curRenderTarget == tex && + device->curRenderSide == side && + device->curZStencilBuffer == zstencil) + return; + + if (tex->type != GS_TEXTURE_CUBE) { + blog(LOG_ERROR, "device_set_cube_render_target (Metal): " + "texture is not a cube texture"); + return; + } + + gs_texture_2d *tex2d = static_cast(tex); + if (tex2d && tex2d->texture == nil) { + blog(LOG_ERROR, "device_set_cube_render_target (Metal): " + "texture is null"); + return; + } + + device->curRenderTarget = tex2d; + device->curRenderSide = side; + device->curZStencilBuffer = zstencil; + + if (tex2d) { + device->passDesc.colorAttachments[0].texture = tex2d->texture; + device->pipelineDesc.colorAttachments[0].pixelFormat = + tex2d->mtlPixelFormat; + } else + device->passDesc.colorAttachments[0].texture = nil; + + if (zstencil) { + device->passDesc.depthAttachment.texture = zstencil->texture; + device->passDesc.stencilAttachment.texture = zstencil->texture; + device->pipelineDesc.depthAttachmentPixelFormat = + zstencil->textureDesc.pixelFormat; + device->pipelineDesc.stencilAttachmentPixelFormat = + zstencil->textureDesc.pixelFormat; + } else { + device->passDesc.depthAttachment.texture = nil; + device->passDesc.stencilAttachment.texture = nil; + } + + device->piplineStateChanged = true; +} + +inline void gs_device::CopyTex(id dst, + uint32_t dst_x, uint32_t dst_y, + gs_texture_t *src, uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + assert(commandBuffer != nil); + + gs_texture_2d *tex2d = static_cast(src); + if (src_w == 0) + src_w = tex2d->width; + if (src_h == 0) + src_h = tex2d->height; + + @autoreleasepool { + id commandEncoder = + [commandBuffer blitCommandEncoder]; + MTLOrigin sourceOrigin = MTLOriginMake(src_x, src_y, 0); + MTLSize sourceSize = MTLSizeMake(src_w, src_h, 1); + MTLOrigin destinationOrigin = MTLOriginMake(dst_x, dst_y, 0); + [commandEncoder copyFromTexture:tex2d->texture + sourceSlice:0 + sourceLevel:0 + sourceOrigin:sourceOrigin + sourceSize:sourceSize + toTexture:dst + destinationSlice:0 + destinationLevel:0 + destinationOrigin:destinationOrigin]; + [commandEncoder endEncoding]; + } +} + +void device_copy_texture_region(gs_device_t *device, + gs_texture_t *dst, uint32_t dst_x, uint32_t dst_y, + gs_texture_t *src, uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + try { + gs_texture_2d *src2d = static_cast(src); + gs_texture_2d *dst2d = static_cast(dst); + + if (!src) + throw "Source texture is null"; + if (!dst) + throw "Destination texture is null"; + if (src->type != GS_TEXTURE_2D || dst->type != GS_TEXTURE_2D) + throw "Source and destination textures must be a 2D " + "textures"; + if (dst->format != src->format) + throw "Source and destination formats do not match"; + + uint32_t copyWidth = src_w ? src_w : (src2d->width - src_x); + uint32_t copyHeight = src_h ? src_h : (src2d->height - src_y); + + uint32_t dstWidth = dst2d->width - dst_x; + uint32_t dstHeight = dst2d->height - dst_y; + + if (dstWidth < copyWidth || dstHeight < copyHeight) + throw "Destination texture region is not big " + "enough to hold the source region"; + + if (dst_x == 0 && dst_y == 0 && + src_x == 0 && src_y == 0 && + src_w == 0 && src_h == 0) { + copyWidth = 0; + copyHeight = 0; + } + + device->CopyTex(dst2d->texture, dst_x, dst_y, + src, src_x, src_y, copyWidth, copyHeight); + + } catch(const char *error) { + blog(LOG_ERROR, "device_copy_texture (Metal): %s", error); + } +} + +void device_copy_texture(gs_device_t *device, gs_texture_t *dst, + gs_texture_t *src) +{ + device_copy_texture_region(device, dst, 0, 0, src, 0, 0, 0, 0); +} + +void device_stage_texture(gs_device_t *device, gs_stagesurf_t *dst, + gs_texture_t *src) +{ + try { + gs_texture_2d *src2d = static_cast(src); + + if (!src) + throw "Source texture is null"; + if (src->type != GS_TEXTURE_2D) + throw "Source texture must be a 2D texture"; + if (!dst) + throw "Destination surface is null"; + if (dst->format != src->format) + throw "Source and destination formats do not match"; + if (dst->width != src2d->width || + dst->height != src2d->height) + throw "Source and destination must have the same " + "dimensions"; + + device->CopyTex(dst->texture, 0, 0, src, 0, 0, 0, 0); + + } catch (const char *error) { + blog(LOG_ERROR, "device_stage_texture (Metal): %s", error); + } +} + +void device_begin_scene(gs_device_t *device) +{ + device_clear_textures(device); + + device->commandBuffer = [device->commandQueue commandBuffer]; +} + +void device_draw(gs_device_t *device, enum gs_draw_mode draw_mode, + uint32_t start_vert, uint32_t num_verts) +{ + /* + * Do not remove autorelease pool. + * Add MTLRenderCommandEncoder to autorelease pool. + */ + @autoreleasepool { + device->Draw(draw_mode, start_vert, num_verts); + } +} + +void device_end_scene(gs_device_t *device) +{ + /* does nothing in Metal */ + UNUSED_PARAMETER(device); +} + +void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swapchain) +{ + if (device->curSwapChain == swapchain) + return; + + if (swapchain) { + device->curSwapChain = swapchain; + device->curRenderTarget = swapchain->GetTarget(); + device->curRenderSide = 0; + device->curZStencilBuffer = nullptr; + + device->passDesc.colorAttachments[0].texture = + device->curSwapChain->nextDrawable.texture; + device->passDesc.depthAttachment.texture = nil; + device->passDesc.stencilAttachment.texture = nil; + + device->pipelineDesc.colorAttachments[0].pixelFormat = + device->curSwapChain->metalLayer.pixelFormat; + } else { + device->curSwapChain = nullptr; + device->curRenderTarget = nullptr; + device->curRenderSide = 0; + device->curZStencilBuffer = nullptr; + + device->passDesc.colorAttachments[0].texture = nil; + device->passDesc.depthAttachment.texture = nil; + device->passDesc.stencilAttachment.texture = nil; + } + + device->piplineStateChanged = true; +} + +void device_clear(gs_device_t *device, uint32_t clear_flags, + const struct vec4 *color, float depth, uint8_t stencil) +{ + device->preserveClearTarget = device->curRenderTarget; + + ClearState state; + state.flags = clear_flags; + state.color = *color; + state.depth = depth; + state.stencil = stencil; + device->clearStates.emplace(device->curRenderTarget, state); +} + +void device_present(gs_device_t *device) +{ + if (device->curSwapChain) { + [device->commandBuffer presentDrawable: + device->curSwapChain->nextDrawable]; + } else { + blog(LOG_WARNING, "device_present (Metal): No active swap"); + } + + device->PushResources(); + + [device->commandBuffer addCompletedHandler:^(id buf) { + device->ReleaseResources(); + + UNUSED_PARAMETER(buf); + }]; + [device->commandBuffer commit]; + device->commandBuffer = nil; + + if (device->curSwapChain) + device->curSwapChain->NextTarget(); + +} + +void device_flush(gs_device_t *device) +{ + if (device->commandBuffer != nil) { + device->PushResources(); + + [device->commandBuffer commit]; + [device->commandBuffer waitUntilCompleted]; + device->commandBuffer = nil; + + device->ReleaseResources(); + + if (device->curStageSurface) { + device->curStageSurface->DownloadTexture(); + device->curStageSurface = nullptr; + } + } +} + +void device_set_cull_mode(gs_device_t *device, enum gs_cull_mode mode) +{ + if (device->rasterState.cullMode == mode) + return; + + device->rasterState.cullMode = mode; + + device->rasterState.mtlCullMode = ConvertGSCullMode(mode); +} + +enum gs_cull_mode device_get_cull_mode(const gs_device_t *device) +{ + return device->rasterState.cullMode; +} + +void device_enable_blending(gs_device_t *device, bool enable) +{ + if (device->blendState.blendEnabled == enable) + return; + + device->blendState.blendEnabled = enable; + + device->pipelineDesc.colorAttachments[0].blendingEnabled = + enable ? YES : NO; + + device->piplineStateChanged = true; +} + +void device_enable_depth_test(gs_device_t *device, bool enable) +{ + if (device->zstencilState.depthEnabled == enable) + return; + + device->zstencilState.depthEnabled = enable; + + device->depthStencilState = nil; +} + +void device_enable_stencil_test(gs_device_t *device, bool enable) +{ + ZStencilState &state = device->zstencilState; + + if (state.stencilEnabled == enable) + return; + + state.stencilEnabled = enable; + + state.dsd.frontFaceStencil.readMask = enable ? 1 : 0; + state.dsd.backFaceStencil.readMask = enable ? 1 : 0; + + device->depthStencilState = nil; +} + +void device_enable_stencil_write(gs_device_t *device, bool enable) +{ + ZStencilState &state = device->zstencilState; + + if (state.stencilWriteEnabled == enable) + return; + + state.stencilWriteEnabled = enable; + + state.dsd.frontFaceStencil.writeMask = enable ? 1 : 0; + state.dsd.backFaceStencil.writeMask = enable ? 1 : 0; + + device->depthStencilState = nil; +} + +void device_enable_color(gs_device_t *device, bool red, bool green, + bool blue, bool alpha) +{ + BlendState &state = device->blendState; + + if (state.redEnabled == red && + state.greenEnabled == green && + state.blueEnabled == blue && + state.alphaEnabled == alpha) + return; + + state.redEnabled = red; + state.greenEnabled = green; + state.blueEnabled = blue; + state.alphaEnabled = alpha; + + MTLRenderPipelineColorAttachmentDescriptor *cad = + device->pipelineDesc.colorAttachments[0]; + cad.writeMask = MTLColorWriteMaskNone; + if (red) cad.writeMask |= MTLColorWriteMaskRed; + if (green) cad.writeMask |= MTLColorWriteMaskGreen; + if (blue) cad.writeMask |= MTLColorWriteMaskBlue; + if (alpha) cad.writeMask |= MTLColorWriteMaskAlpha; + + device->piplineStateChanged = true; +} + +void device_blend_function(gs_device_t *device, enum gs_blend_type src, + enum gs_blend_type dest) +{ + BlendState &state = device->blendState; + + if (state.srcFactorC == src && state.destFactorC == dest && + state.srcFactorA == src && state.destFactorA == dest) + return; + + state.srcFactorC = src; + state.destFactorC = dest; + state.srcFactorA = src; + state.destFactorA = dest; + + MTLRenderPipelineColorAttachmentDescriptor *cad = + device->pipelineDesc.colorAttachments[0]; + cad.sourceRGBBlendFactor = ConvertGSBlendType(src); + cad.destinationRGBBlendFactor = ConvertGSBlendType(dest); + cad.sourceAlphaBlendFactor = ConvertGSBlendType(src); + cad.destinationAlphaBlendFactor = ConvertGSBlendType(dest); + + device->piplineStateChanged = true; +} + +void device_blend_function_separate(gs_device_t *device, + enum gs_blend_type src_c, enum gs_blend_type dest_c, + enum gs_blend_type src_a, enum gs_blend_type dest_a) +{ + BlendState &state = device->blendState; + + if (state.srcFactorC == src_c && state.destFactorC == dest_c && + state.srcFactorA == src_a && state.destFactorA == dest_a) + return; + + state.srcFactorC = src_c; + state.destFactorC = dest_c; + state.srcFactorA = src_a; + state.destFactorA = dest_a; + + MTLRenderPipelineColorAttachmentDescriptor *cad = + device->pipelineDesc.colorAttachments[0]; + cad.sourceRGBBlendFactor = ConvertGSBlendType(src_c); + cad.destinationRGBBlendFactor = ConvertGSBlendType(dest_c); + cad.sourceAlphaBlendFactor = ConvertGSBlendType(src_a); + cad.destinationAlphaBlendFactor = ConvertGSBlendType(dest_a); + + device->piplineStateChanged = true; +} + +void device_depth_function(gs_device_t *device, enum gs_depth_test test) +{ + if (device->zstencilState.depthFunc == test) + return; + + device->zstencilState.depthFunc = test; + + device->zstencilState.dsd.depthCompareFunction = + ConvertGSDepthTest(test); + + device->depthStencilState = nil; +} + +static inline void update_stencilside_test(gs_device_t *device, + StencilSide &side, MTLStencilDescriptor *desc, + gs_depth_test test) +{ + if (side.test == test) + return; + + side.test = test; + + desc.stencilCompareFunction = ConvertGSDepthTest(test); + + device->depthStencilState = nil; +} + +void device_stencil_function(gs_device_t *device, enum gs_stencil_side side, + enum gs_depth_test test) +{ + int sideVal = static_cast(side); + if (sideVal & GS_STENCIL_FRONT) + update_stencilside_test(device, + device->zstencilState.stencilFront, + device->zstencilState.dsd.frontFaceStencil, + test); + if (sideVal & GS_STENCIL_BACK) + update_stencilside_test(device, + device->zstencilState.stencilBack, + device->zstencilState.dsd.backFaceStencil, + test); +} + +static inline void update_stencilside_op(gs_device_t *device, + StencilSide &side, MTLStencilDescriptor *desc, + enum gs_stencil_op_type fail, enum gs_stencil_op_type zfail, + enum gs_stencil_op_type zpass) +{ + if (side.fail == fail && side.zfail == zfail && side.zpass == zpass) + return; + + side.fail = fail; + side.zfail = zfail; + side.zpass = zpass; + + desc.stencilFailureOperation = ConvertGSStencilOp(fail); + desc.depthFailureOperation = ConvertGSStencilOp(zfail); + desc.depthStencilPassOperation = ConvertGSStencilOp(zpass); + + device->depthStencilState = nil; +} + +void device_stencil_op(gs_device_t *device, enum gs_stencil_side side, + enum gs_stencil_op_type fail, enum gs_stencil_op_type zfail, + enum gs_stencil_op_type zpass) +{ + int sideVal = static_cast(side); + + if (sideVal & GS_STENCIL_FRONT) + update_stencilside_op(device, + device->zstencilState.stencilFront, + device->zstencilState.dsd.frontFaceStencil, + fail, zfail, zpass); + if (sideVal & GS_STENCIL_BACK) + update_stencilside_op(device, + device->zstencilState.stencilBack, + device->zstencilState.dsd.backFaceStencil, + fail, zfail, zpass); +} + +void device_set_viewport(gs_device_t *device, int x, int y, int width, + int height) +{ + RasterState &state = device->rasterState; + + if (state.viewport.x == x && + state.viewport.y == y && + state.viewport.cx == width && + state.viewport.cy == height) + return; + + state.viewport.x = x; + state.viewport.y = y; + state.viewport.cx = width; + state.viewport.cy = height; + + state.mtlViewport = ConvertGSRectToMTLViewport(state.viewport); +} + +void device_get_viewport(const gs_device_t *device, struct gs_rect *rect) +{ + memcpy(rect, &device->rasterState.viewport, sizeof(gs_rect)); +} + +void device_set_scissor_rect(gs_device_t *device, const struct gs_rect *rect) +{ + if (rect != nullptr) { + device->rasterState.scissorEnabled = true; + device->rasterState.scissorRect = *rect; + device->rasterState.mtlScissorRect = + ConvertGSRectToMTLScissorRect(*rect); + } else { + device->rasterState.scissorEnabled = false; + } +} + +void device_ortho(gs_device_t *device, float left, float right, float top, + float bottom, float zNear, float zFar) +{ + matrix4 &dst = device->curProjMatrix; + + float rml = right - left; + float bmt = bottom - top; + float fmn = zFar - zNear; + + vec4_zero(&dst.x); + vec4_zero(&dst.y); + vec4_zero(&dst.z); + vec4_zero(&dst.t); + + dst.x.x = 2.0f / rml; + dst.t.x = (left + right) / -rml; + + dst.y.y = 2.0f / -bmt; + dst.t.y = (bottom + top) / bmt; + + dst.z.z = 1.0f / fmn; + dst.t.z = zNear / -fmn; + + dst.t.w = 1.0f; +} + +void device_frustum(gs_device_t *device, float left, float right, float top, + float bottom, float zNear, float zFar) +{ + matrix4 &dst = device->curProjMatrix; + + float rml = right - left; + float bmt = bottom - top; + float fmn = zFar - zNear; + float nearx2 = 2.0f * zNear; + + vec4_zero(&dst.x); + vec4_zero(&dst.y); + vec4_zero(&dst.z); + vec4_zero(&dst.t); + + dst.x.x = nearx2 / rml; + dst.z.x = (left + right) / -rml; + + dst.y.y = nearx2 / -bmt; + dst.z.y = (bottom + top) / bmt; + + dst.z.z = zFar / fmn; + dst.t.z = (zNear * zFar) / -fmn; + + dst.z.w = 1.0f; +} + +void device_projection_push(gs_device_t *device) +{ + device->projStack.push(device->curProjMatrix); +} + +void device_projection_pop(gs_device_t *device) +{ + if (!device->projStack.size()) + return; + + device->curProjMatrix = device->projStack.top(); + device->projStack.pop(); +} + +void gs_swapchain_destroy(gs_swapchain_t *swapchain) +{ + assert(swapchain->obj_type == gs_type::gs_swap_chain); + + if (swapchain->device->curSwapChain == swapchain) + device_load_swapchain(swapchain->device, nullptr); + + delete swapchain; +} + +void gs_texture_destroy(gs_texture_t *tex) +{ + delete tex; +} + +uint32_t gs_texture_get_width(const gs_texture_t *tex) +{ + if (tex->type != GS_TEXTURE_2D) + return 0; + + return static_cast(tex)->width; +} + +uint32_t gs_texture_get_height(const gs_texture_t *tex) +{ + if (tex->type != GS_TEXTURE_2D) + return 0; + + return static_cast(tex)->height; +} + +enum gs_color_format gs_texture_get_color_format(const gs_texture_t *tex) +{ + if (tex->type != GS_TEXTURE_2D) + return GS_UNKNOWN; + + return static_cast(tex)->format; +} + +bool gs_texture_map(gs_texture_t *tex, uint8_t **ptr, uint32_t *linesize) +{ + if (tex->type != GS_TEXTURE_2D) + return false; + + gs_texture_2d *tex2d = static_cast(tex); + uint32_t texSizeBytes = tex2d->height * tex2d->bytePerRow; + + tex2d->data.resize(1); + tex2d->data[0].resize(texSizeBytes); + + *ptr = (uint8_t *)tex2d->data[0].data(); + *linesize = tex2d->bytePerRow; + return true; +} + +void gs_texture_unmap(gs_texture_t *tex) +{ + if (tex->type != GS_TEXTURE_2D) + return; + + gs_texture_2d *tex2d = static_cast(tex); + tex2d->UploadTexture(); +} + +void *gs_texture_get_obj(gs_texture_t *tex) +{ + if (tex->type != GS_TEXTURE_2D) + return nullptr; + + gs_texture_2d *tex2d = static_cast(tex); + return (__bridge void*)tex2d->texture; +} + + +void gs_cubetexture_destroy(gs_texture_t *cubetex) +{ + delete cubetex; +} + +uint32_t gs_cubetexture_get_size(const gs_texture_t *cubetex) +{ + if (cubetex->type != GS_TEXTURE_CUBE) + return 0; + + const gs_texture_2d *tex = static_cast(cubetex); + return tex->width; +} + +enum gs_color_format gs_cubetexture_get_color_format( + const gs_texture_t *cubetex) +{ + if (cubetex->type != GS_TEXTURE_CUBE) + return GS_UNKNOWN; + + const gs_texture_2d *tex = static_cast(cubetex); + return tex->format; +} + + +void gs_voltexture_destroy(gs_texture_t *voltex) +{ + delete voltex; +} + +uint32_t gs_voltexture_get_width(const gs_texture_t *voltex) +{ + /* TODO */ + UNUSED_PARAMETER(voltex); + return 0; +} + +uint32_t gs_voltexture_get_height(const gs_texture_t *voltex) +{ + /* TODO */ + UNUSED_PARAMETER(voltex); + return 0; +} + +uint32_t gs_voltexture_get_depth(const gs_texture_t *voltex) +{ + /* TODO */ + UNUSED_PARAMETER(voltex); + return 0; +} + +enum gs_color_format gs_voltexture_get_color_format(const gs_texture_t *voltex) +{ + /* TODO */ + UNUSED_PARAMETER(voltex); + return GS_UNKNOWN; +} + + +void gs_stagesurface_destroy(gs_stagesurf_t *stagesurf) +{ + assert(stagesurf->obj_type == gs_type::gs_stage_surface); + + delete stagesurf; +} + +uint32_t gs_stagesurface_get_width(const gs_stagesurf_t *stagesurf) +{ + assert(stagesurf->obj_type == gs_type::gs_stage_surface); + + return stagesurf->width; +} + +uint32_t gs_stagesurface_get_height(const gs_stagesurf_t *stagesurf) +{ + assert(stagesurf->obj_type == gs_type::gs_stage_surface); + + return stagesurf->height; +} + +enum gs_color_format gs_stagesurface_get_color_format( + const gs_stagesurf_t *stagesurf) +{ + assert(stagesurf->obj_type == gs_type::gs_stage_surface); + + return stagesurf->format; +} + +bool gs_stagesurface_map(gs_stagesurf_t *stagesurf, uint8_t **data, + uint32_t *linesize) +{ + assert(stagesurf->obj_type == gs_type::gs_stage_surface); + assert(stagesurf->device->commandBuffer != nil); + + @autoreleasepool { + id commandEncoder = + [stagesurf->device->commandBuffer + blitCommandEncoder]; + [commandEncoder synchronizeTexture:stagesurf->texture + slice:0 level:0]; + [commandEncoder endEncoding]; + } + + *data = (uint8_t *)stagesurf->data.data(); + *linesize = stagesurf->bytePerRow; + + stagesurf->device->curStageSurface = stagesurf; + return true; +} + +void gs_stagesurface_unmap(gs_stagesurf_t *stagesurf) +{ + /* does nothing in Metal */ + UNUSED_PARAMETER(stagesurf); +} + + +void gs_zstencil_destroy(gs_zstencil_t *zstencil) +{ + assert(zstencil->obj_type == gs_type::gs_zstencil_buffer); + + delete zstencil; +} + + +void gs_samplerstate_destroy(gs_samplerstate_t *samplerstate) +{ + assert(samplerstate->obj_type == gs_type::gs_sampler_state); + + if (samplerstate->device) { + for (size_t i = 0; i < GS_MAX_TEXTURES; i++) + if (samplerstate->device->curSamplers[i] == + samplerstate) + samplerstate->device->curSamplers[i] = nullptr; + } + + delete samplerstate; +} + + +void gs_vertexbuffer_destroy(gs_vertbuffer_t *vertbuffer) +{ + assert(vertbuffer->obj_type == gs_type::gs_vertex_buffer); + + if (vertbuffer->device->lastVertexBuffer == vertbuffer) + vertbuffer->device->lastVertexBuffer = nullptr; + + delete vertbuffer; +} + +void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer) +{ + assert(vertbuffer->obj_type == gs_type::gs_vertex_buffer); + + if (!vertbuffer->isDynamic) { + blog(LOG_ERROR, "gs_vertexbuffer_flush: vertex buffer is not " + "dynamic"); + return; + } + + vertbuffer->PrepareBuffers(); +} + +struct gs_vb_data *gs_vertexbuffer_get_data(const gs_vertbuffer_t *vertbuffer) +{ + assert(vertbuffer->obj_type == gs_type::gs_vertex_buffer); + + return vertbuffer->vbData.get(); +} + + +void gs_indexbuffer_destroy(gs_indexbuffer_t *indexbuffer) +{ + assert(indexbuffer->obj_type == gs_type::gs_index_buffer); + + delete indexbuffer; +} + +void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer) +{ + assert(indexbuffer->obj_type == gs_type::gs_index_buffer); + + if (!indexbuffer->isDynamic) { + blog(LOG_ERROR, "gs_indexbuffer_flush: index buffer is not " + "dynamic"); + return; + } + + indexbuffer->PrepareBuffer(); +} + +void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer) +{ + assert(indexbuffer->obj_type == gs_type::gs_index_buffer); + + return indexbuffer->indices.get(); +} + +size_t gs_indexbuffer_get_num_indices(const gs_indexbuffer_t *indexbuffer) +{ + assert(indexbuffer->obj_type == gs_type::gs_index_buffer); + + return indexbuffer->num; +} + +enum gs_index_type gs_indexbuffer_get_type(const gs_indexbuffer_t *indexbuffer) +{ + assert(indexbuffer->obj_type == gs_type::gs_index_buffer); + + return indexbuffer->type; +} diff --git a/libobs-metal/metal-swapchain.mm b/libobs-metal/metal-swapchain.mm new file mode 100644 index 000000000..78c8a2485 --- /dev/null +++ b/libobs-metal/metal-swapchain.mm @@ -0,0 +1,61 @@ +#include "metal-subsystem.hpp" + +#import + +using namespace std; + +gs_texture_2d *gs_swap_chain::GetTarget() +{ + if (!nextTarget) + return NextTarget(); + + return nextTarget.get(); +} + +gs_texture_2d *gs_swap_chain::NextTarget() +{ + nextDrawable = metalLayer.nextDrawable; + if (nextDrawable != nil) + nextTarget.reset(new gs_texture_2d(device, + nextDrawable.texture)); + else + nextTarget.reset(); + + return nextTarget.get(); +} + +void gs_swap_chain::Resize(uint32_t cx, uint32_t cy) +{ + initData.cx = cx; + initData.cy = cy; + + if (cx == 0 || cy == 0) { + NSRect clientRect = view.layer.frame; + if (cx == 0) cx = clientRect.size.width - clientRect.origin.x; + if (cy == 0) cy = clientRect.size.height - clientRect.origin.y; + } + + metalLayer.drawableSize = CGSizeMake(cx, cy); +} + +void gs_swap_chain::Rebuild() +{ + metalLayer.device = device->device; +} + +gs_swap_chain::gs_swap_chain(gs_device *device, const gs_init_data *data) + : gs_obj (device, gs_type::gs_swap_chain), + numBuffers (data->num_backbuffers), + view (data->window.view), + initData (*data) +{ + if (metalLayer.pixelFormat != ConvertGSTextureFormat(data->format)) + blog(LOG_WARNING, "device_stage_texture (Metal): " + "pixel format is not matched (RGBA only)"); + + metalLayer = [CAMetalLayer layer]; + metalLayer.device = device->device; + metalLayer.drawableSize = CGSizeMake(initData.cx, initData.cy); + view.wantsLayer = YES; + view.layer = metalLayer; +} diff --git a/libobs-metal/metal-texture2d.mm b/libobs-metal/metal-texture2d.mm new file mode 100644 index 000000000..fa995227b --- /dev/null +++ b/libobs-metal/metal-texture2d.mm @@ -0,0 +1,159 @@ +#include + +#include "metal-subsystem.hpp" + +void gs_texture_2d::GenerateMipmap() +{ + assert(device->commandBuffer == nil); + + if (levels == 1) + return; + + @autoreleasepool { + id buf = [device->commandQueue commandBuffer]; + id blit = [buf blitCommandEncoder]; + [blit generateMipmapsForTexture:texture]; + [blit endEncoding]; + [buf commit]; + [buf waitUntilCompleted]; + } +} + +void gs_texture_2d::BackupTexture(const uint8_t **data) +{ + this->data.resize(levels); + + uint32_t w = width; + uint32_t h = height; + uint32_t bpp = gs_get_format_bpp(format); + + for (uint32_t i = 0; i < levels; i++) { + if (!data[i]) + break; + + uint32_t texSize = bpp * w * h / 8; + this->data[i].resize(texSize); + + auto &subData = this->data[i]; + memcpy(&subData[0], data[i], texSize); + + w /= 2; + h /= 2; + } +} + +void gs_texture_2d::UploadTexture() +{ + const uint32_t bpp = gs_get_format_bpp(format) / 8; + uint32_t w = width; + uint32_t h = height; + + for (uint32_t i = 0; i < levels; i++) { + if (i >= data.size()) + break; + + const uint32_t rowSizeBytes = w * bpp; + const uint32_t texSizeBytes = h * rowSizeBytes; + MTLRegion region = MTLRegionMake2D(0, 0, w, h); + [texture replaceRegion:region mipmapLevel:i slice:0 + withBytes:data[i].data() + bytesPerRow:rowSizeBytes + bytesPerImage:texSizeBytes]; + + w /= 2; + h /= 2; + } +} + +void gs_texture_2d::InitTexture() +{ + assert(!isShared); + + texture = [device->device newTextureWithDescriptor:textureDesc]; + if (texture == nil) + throw "Failed to create 2D texture"; +} + +void gs_texture_2d::Rebuild() +{ + if (isShared) { + texture = nil; + return; + } + + InitTexture(); +} + +gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t width, + uint32_t height, gs_color_format colorFormat, uint32_t levels, + const uint8_t **data, uint32_t flags, gs_texture_type type) + : gs_texture (device, gs_type::gs_texture_2d, type, levels, + colorFormat), + width (width), + height (height), + bytePerRow (width * gs_get_format_bpp(colorFormat) / 8), + isRenderTarget ((flags & GS_RENDER_TARGET) != 0), + isDynamic ((flags & GS_DYNAMIC) != 0), + genMipmaps ((flags & GS_BUILD_MIPMAPS) != 0), + isShared (false), + mtlPixelFormat (ConvertGSTextureFormat(format)) +{ + if (type == GS_TEXTURE_CUBE) { + NSUInteger size = 6 * width * height; + textureDesc = [MTLTextureDescriptor + textureCubeDescriptorWithPixelFormat: + mtlPixelFormat size:size + mipmapped:genMipmaps ? YES : NO]; + } else { + textureDesc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat: + mtlPixelFormat width:width height:height + mipmapped:genMipmaps ? YES : NO]; + } + + switch (type) { + case GS_TEXTURE_3D: + textureDesc.textureType = MTLTextureType3D; + break; + case GS_TEXTURE_CUBE: + textureDesc.textureType = MTLTextureTypeCube; + break; + case GS_TEXTURE_2D: + default: + break; + } + if (genMipmaps) + textureDesc.mipmapLevelCount = levels; + textureDesc.arrayLength = type == GS_TEXTURE_CUBE ? 6 : 1; + textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined; + textureDesc.storageMode = MTLStorageModeManaged; + textureDesc.usage = MTLTextureUsageShaderRead; + if (isRenderTarget) + textureDesc.usage |= MTLTextureUsageRenderTarget; + + InitTexture(); + + if (data) { + BackupTexture(data); + UploadTexture(); + if (genMipmaps) + GenerateMipmap(); + } +} + +gs_texture_2d::gs_texture_2d(gs_device_t *device, id texture) + : gs_texture (device, gs_type::gs_texture_2d, + GS_TEXTURE_2D, + texture.mipmapLevelCount, + ConvertMTLTextureFormat(texture.pixelFormat)), + width (texture.width), + height (texture.height), + bytePerRow (width * gs_get_format_bpp(format) / 8), + isRenderTarget (false), + isDynamic (false), + genMipmaps (false), + isShared (true), + mtlPixelFormat (texture.pixelFormat), + texture (texture) +{ +} diff --git a/libobs-metal/metal-vertexbuffer.mm b/libobs-metal/metal-vertexbuffer.mm new file mode 100644 index 000000000..08c429bc9 --- /dev/null +++ b/libobs-metal/metal-vertexbuffer.mm @@ -0,0 +1,136 @@ +#include +#include + +#include "metal-subsystem.hpp" + +using namespace std; + +inline id gs_vertex_buffer::PrepareBuffer( + void *array, size_t elementSize, __weak NSString *name) +{ + id b = device->GetBuffer(array, elementSize * vbData->num); +#if _DEBUG + b.label = name; +#endif + return b; +} + +void gs_vertex_buffer::PrepareBuffers() +{ + assert(isDynamic); + + vertexBuffer = PrepareBuffer(vbData->points, sizeof(vec3), @"point"); + if (vbData->normals) + normalBuffer = PrepareBuffer(vbData->normals, sizeof(vec3), + @"normal"); + if (vbData->tangents) + tangentBuffer = PrepareBuffer(vbData->tangents, sizeof(vec3), + @"color"); + if (vbData->colors) + colorBuffer = PrepareBuffer(vbData->colors, sizeof(uint32_t), + @"tangent"); + + for (size_t i = 0; i < vbData->num_tex; i++) { + gs_tvertarray &tv = vbData->tvarray[i]; + uvBuffers.push_back(PrepareBuffer(tv.array, + tv.width * sizeof(float), @"texcoord")); + } +} + +static inline void PushBuffer(vector> &buffers, + id buffer, const char *name) +{ + if (buffer != nil) { + buffers.push_back(buffer); + } else { + blog(LOG_ERROR, "This vertex shader requires a %s buffer", + name); + } +} + +void gs_vertex_buffer::MakeBufferList(gs_vertex_shader *shader, + vector> &buffers) +{ + PushBuffer(buffers, vertexBuffer, "point"); + if (shader->hasNormals) + PushBuffer(buffers, normalBuffer, "normal"); + if (shader->hasColors) + PushBuffer(buffers, colorBuffer, "color"); + if (shader->hasTangents) + PushBuffer(buffers, tangentBuffer, "tangent"); + if (shader->texUnits <= uvBuffers.size()) { + for (size_t i = 0; i < shader->texUnits; i++) + buffers.push_back(uvBuffers[i]); + } else { + blog(LOG_ERROR, "This vertex shader requires at least %u " + "texture buffers.", + (uint32_t)shader->texUnits); + } +} + +inline id gs_vertex_buffer::InitBuffer(size_t elementSize, + void *array, const char *name) +{ + NSUInteger length = elementSize * vbData->num; + MTLResourceOptions options = MTLResourceCPUCacheModeWriteCombined | + (isDynamic ? MTLResourceStorageModeShared : + MTLResourceStorageModeManaged); + + id buffer = [device->device newBufferWithBytes:array + length:length options:options]; + if (buffer == nil) + throw "Failed to create buffer"; + +#ifdef _DEBUG + buffer.label = [[NSString alloc] initWithUTF8String:name]; +#endif + + return buffer; +} + +void gs_vertex_buffer::InitBuffers() +{ + vertexBuffer = InitBuffer(sizeof(vec3), vbData->points, "point"); + if (vbData->normals) + normalBuffer = InitBuffer(sizeof(vec3), vbData->normals, + "normal"); + if (vbData->tangents) + tangentBuffer = InitBuffer(sizeof(vec3), vbData->tangents, + "color"); + if (vbData->colors) + colorBuffer = InitBuffer(sizeof(uint32_t), vbData->colors, + "tangent"); + for (struct gs_tvertarray *tverts = vbData->tvarray; + tverts != vbData->tvarray + vbData->num_tex; + tverts++) { + if (tverts->width != 2 && tverts->width != 4) + throw "Invalid texture vertex size specified"; + if (!tverts->array) + throw "No texture vertices specified"; + + id buffer = InitBuffer(tverts->width * sizeof(float), + tverts->array, "texcoord"); + uvBuffers.emplace_back(buffer); + } +} + +void gs_vertex_buffer::Rebuild() +{ + if (!isDynamic) + InitBuffers(); +} + +gs_vertex_buffer::gs_vertex_buffer(gs_device_t *device, struct gs_vb_data *data, + uint32_t flags) + : gs_obj (device, gs_type::gs_vertex_buffer), + isDynamic ((flags & GS_DYNAMIC) != 0), + vbData (data, gs_vbdata_destroy) +{ + if (!data->num) + throw "Cannot initialize vertex buffer with 0 vertices"; + if (!data->points) + throw "No points specified for vertex buffer"; + + if (!isDynamic) + InitBuffers(); +} diff --git a/libobs-metal/metal-zstencilbuffer.mm b/libobs-metal/metal-zstencilbuffer.mm new file mode 100644 index 000000000..f87ec14e2 --- /dev/null +++ b/libobs-metal/metal-zstencilbuffer.mm @@ -0,0 +1,43 @@ +#include "metal-subsystem.hpp" + +static inline MTLPixelFormat ConvertGSZStencilFormat(gs_zstencil_format format) +{ + switch (format) { + case GS_ZS_NONE: return MTLPixelFormatInvalid; + case GS_Z16: return MTLPixelFormatDepth16Unorm; + case GS_Z24_S8: return MTLPixelFormatDepth24Unorm_Stencil8; + case GS_Z32F: return MTLPixelFormatDepth32Float; + case GS_Z32F_S8X24: return MTLPixelFormatDepth32Float_Stencil8; + default: throw "Failed to initialize zstencil buffer"; + } + + return MTLPixelFormatInvalid; +} + +void gs_zstencil_buffer::InitBuffer() +{ + texture = [device->device newTextureWithDescriptor:textureDesc]; + if (texture == nil) + throw "Failed to create depth stencil texture"; + +#if _DEBUG + texture.label = @"zstencil"; +#endif +} + +gs_zstencil_buffer::gs_zstencil_buffer(gs_device_t *device, + uint32_t width, uint32_t height, + gs_zstencil_format format) + : gs_obj (device, gs_type::gs_zstencil_buffer), + width (width), + height (height), + format (format) +{ + textureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: + ConvertGSZStencilFormat(format) + width:width height:height mipmapped:NO]; + textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined; + textureDesc.storageMode = MTLStorageModeManaged; + + InitBuffer(); +} diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index fed3e6949..59f03db68 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -117,7 +117,8 @@ elseif(APPLE) util/platform-cocoa.m) set(libobs_PLATFORM_HEADERS util/threading-posix.h - util/apple/cfstring-utils.h) + util/apple/cfstring-utils.h + util/mac/mac-version.h) set(libobs_audio_monitoring_SOURCES audio-monitoring/osx/coreaudio-enum-devices.c audio-monitoring/osx/coreaudio-output.c diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index a3f6de872..98d2d86b4 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -479,6 +479,7 @@ struct gs_init_data { #define GS_DEVICE_OPENGL 1 #define GS_DEVICE_DIRECT3D_11 2 +#define GS_DEVICE_METAL 3 EXPORT const char *gs_get_device_name(void); EXPORT int gs_get_device_type(void); diff --git a/libobs/util/mac/mac-version.h b/libobs/util/mac/mac-version.h new file mode 100644 index 000000000..d94e5817f --- /dev/null +++ b/libobs/util/mac/mac-version.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../c99defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mac_version_info { + union { + struct { + uint8_t _; + uint8_t bug_fix; + uint8_t minor; + uint8_t major; + }; + uint32_t identifier; + }; +}; + +#define MACOSX_LEOPARD ((10 << 24) | ( 5 << 16)) +#define MACOSX_SNOW_LEOPARD ((10 << 24) | ( 6 << 16)) +#define MACOSX_LION ((10 << 24) | ( 7 << 16)) +#define OSX_MOUNTAIN_LION ((10 << 24) | ( 8 << 16)) +#define OSX_MAVERICKS ((10 << 24) | ( 9 << 16)) +#define OSX_YOSEMITE ((10 << 24) | (10 << 16)) +#define OSX_EL_CAPITAN ((10 << 24) | (11 << 16)) +#define MACOS_SIERRA ((10 << 24) | (12 << 16)) +#define MACOS_HIGH_SIERRA ((10 << 24) | (13 << 16)) + +EXPORT void get_mac_ver(struct mac_version_info *info); + +#ifdef __cplusplus +} +#endif diff --git a/libobs/util/platform-cocoa.m b/libobs/util/platform-cocoa.m index ff434d670..d5e93ef5f 100644 --- a/libobs/util/platform-cocoa.m +++ b/libobs/util/platform-cocoa.m @@ -19,6 +19,7 @@ #include "base.h" #include "platform.h" #include "dstr.h" +#include "mac/mac-version.h" #include #include @@ -538,3 +539,38 @@ bool cfstr_copy_dstr(CFStringRef cfstring, return (bool)success; } + +void get_mac_ver(struct mac_version_info *info) +{ + static struct mac_version_info ver = {0}; + static bool got_version = false; + + if (!info) + return; + + if (!got_version) { + @autoreleasepool { + NSDictionary *dict = [NSDictionary + dictionaryWithContentsOfFile: + @"/System/Library/CoreServices/" + "SystemVersion.plist"]; + NSString *version = + [dict objectForKey:@"ProductVersion"]; + const char *str = version.UTF8String; + + const char *p = str; + ver.major = atoi(p); + if ((p = strchr(p, '.')) != NULL) { + p++; + ver.minor = atoi(p); + } + if ((p = strchr(p, '.')) != NULL) { + p++; + ver.bug_fix = atoi(p); + } + } + got_version = true; + } + + *info = ver; +}