libobs-metal: Add new Metal renderer.
This commit is contained in:
parent
dd68493590
commit
e8ea0baede
26 changed files with 4990 additions and 23 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@
|
|||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <util/mac/mac-version.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@
|
|||
#include <util/platform.h>
|
||||
#include "ui-config.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <util/mac/mac-version.h>
|
||||
#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",
|
||||
|
|
|
|||
33
cmake/Modules/MacOSVersion.cmake
Normal file
33
cmake/Modules/MacOSVersion.cmake
Normal file
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
71
libobs-metal/CMakeLists.txt
Normal file
71
libobs-metal/CMakeLists.txt
Normal file
|
|
@ -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)
|
||||
316
libobs-metal/metal-device.mm
Normal file
316
libobs-metal/metal-device.mm
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#include <cinttypes>
|
||||
#include <util/base.h>
|
||||
|
||||
#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<MTLRenderCommandEncoder> commandEncoder)
|
||||
{
|
||||
vector<id<MTLBuffer>> buffers;
|
||||
vector<NSUInteger> 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<MTLRenderCommandEncoder> commandEncoder)
|
||||
{
|
||||
for (size_t i = 0; i < GS_MAX_TEXTURES; i++) {
|
||||
gs_texture_2d *tex2d = static_cast<gs_texture_2d*>(
|
||||
curTextures[i]);
|
||||
if (tex2d == nullptr)
|
||||
break;
|
||||
|
||||
[commandEncoder setFragmentTexture:tex2d->texture atIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
void gs_device::UploadSamplers(id<MTLRenderCommandEncoder> 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<MTLRenderCommandEncoder> 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<MTLRenderCommandEncoder> 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<MTLRenderCommandEncoder> commandEncoder,
|
||||
gs_draw_mode drawMode, uint32_t startVert, uint32_t numVerts)
|
||||
{
|
||||
MTLPrimitiveType primitive = ConvertGSTopology(drawMode);
|
||||
if (curIndexBuffer) {
|
||||
if (numVerts == 0)
|
||||
numVerts = static_cast<uint32_t>(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<uint32_t>(
|
||||
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<MTLRenderCommandEncoder> 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<MTLBuffer> CreateBuffer(id<MTLDevice> device,
|
||||
void *data, size_t length)
|
||||
{
|
||||
length = (length + 15) & ~15;
|
||||
|
||||
MTLResourceOptions options = MTLResourceCPUCacheModeWriteCombined |
|
||||
MTLResourceStorageModeShared;
|
||||
id<MTLBuffer> buffer = [device newBufferWithBytes:data
|
||||
length:length options:options];
|
||||
if (buffer == nil)
|
||||
throw "Failed to create buffer";
|
||||
return buffer;
|
||||
}
|
||||
|
||||
id<MTLBuffer> gs_device::GetBuffer(void *data, size_t length)
|
||||
{
|
||||
lock_guard<mutex> lock(mutexObj);
|
||||
auto target = find_if(unusedBufferPool.begin(), unusedBufferPool.end(),
|
||||
[length](id<MTLBuffer> b) { return b.length >= length; });
|
||||
if (target == unusedBufferPool.end()) {
|
||||
id<MTLBuffer> newBuffer = CreateBuffer(device, data, length);
|
||||
curBufferPool.push_back(newBuffer);
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
id<MTLBuffer> targetBuffer = *target;
|
||||
unusedBufferPool.erase(target);
|
||||
curBufferPool.push_back(targetBuffer);
|
||||
memcpy(targetBuffer.contents, data, length);
|
||||
return targetBuffer;
|
||||
}
|
||||
|
||||
void gs_device::PushResources()
|
||||
{
|
||||
lock_guard<mutex> lock(mutexObj);
|
||||
bufferPools.push(curBufferPool);
|
||||
curBufferPool.clear();
|
||||
}
|
||||
|
||||
void gs_device::ReleaseResources()
|
||||
{
|
||||
lock_guard<mutex> 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);
|
||||
}
|
||||
67
libobs-metal/metal-indexbuffer.mm
Normal file
67
libobs-metal/metal-indexbuffer.mm
Normal file
|
|
@ -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();
|
||||
}
|
||||
118
libobs-metal/metal-rebuild.mm
Normal file
118
libobs-metal/metal-rebuild.mm
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#include "metal-subsystem.hpp"
|
||||
|
||||
void gs_device::RebuildDevice()
|
||||
try {
|
||||
id<MTLDevice> 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);
|
||||
|
||||
}
|
||||
136
libobs-metal/metal-samplerstate.mm
Normal file
136
libobs-metal/metal-samplerstate.mm
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include <graphics/vec4.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
354
libobs-metal/metal-shader.mm
Normal file
354
libobs-metal/metal-shader.mm
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
#include <graphics/vec2.h>
|
||||
#include <graphics/vec3.h>
|
||||
#include <graphics/matrix3.h>
|
||||
#include <graphics/matrix4.h>
|
||||
|
||||
#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<MTLLibrary> 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<MTLFunction> 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<MTLRenderCommandEncoder> commandEncoder)
|
||||
{
|
||||
uint8_t *ptr = data.data();
|
||||
|
||||
for (size_t i = 0; i < params.size(); i++)
|
||||
UpdateParam(ptr, params[i]);
|
||||
|
||||
if (!constantSize)
|
||||
return;
|
||||
|
||||
id<MTLBuffer> 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<const gs_vertex_shader*>(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<const gs_vertex_shader*>(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;
|
||||
}
|
||||
937
libobs-metal/metal-shaderbuilder.cpp
Normal file
937
libobs-metal/metal-shaderbuilder.cpp
Normal file
|
|
@ -0,0 +1,937 @@
|
|||
#include "metal-shaderprocessor.hpp"
|
||||
|
||||
#include <strstream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
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<string> useTextures;
|
||||
#if USE_PROGRAMMABLE_SAMPLER
|
||||
vector<string> useSamplers;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ShaderBuilder
|
||||
{
|
||||
const gs_shader_type type;
|
||||
|
||||
ShaderParser *parser;
|
||||
ostrstream output;
|
||||
|
||||
set<string> constantNames;
|
||||
vector<struct shader_var*> textureVars;
|
||||
map<string, ShaderFunctionInfo> 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<float>";
|
||||
else if (type == "texture3d")
|
||||
return "texture3d<float>";
|
||||
else if (type == "texture_cube")
|
||||
return "texturecube<float>";
|
||||
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 <metal_stdlib>" << 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;
|
||||
}
|
||||
38
libobs-metal/metal-shaderprocessor.hpp
Normal file
38
libobs-metal/metal-shaderprocessor.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <graphics/shader-parser.h>
|
||||
|
||||
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<gs_shader_param> ¶ms);
|
||||
void BuildSamplers(std::vector<std::unique_ptr<ShaderSampler>>
|
||||
&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);
|
||||
186
libobs-metal/metal-shaderprocessor.mm
Normal file
186
libobs-metal/metal-shaderprocessor.mm
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
#include "metal-subsystem.hpp"
|
||||
#include "metal-shaderprocessor.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<gs_shader_param> ¶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<gs_shader_param> ¶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<unique_ptr<ShaderSampler>> &samplers)
|
||||
{
|
||||
gs_sampler_info si;
|
||||
shader_sampler_convert(&sampler, &si);
|
||||
samplers.emplace_back(new ShaderSampler(sampler.name, device, &si));
|
||||
}
|
||||
|
||||
void ShaderProcessor::BuildSamplers(vector<unique_ptr<ShaderSampler>> &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";
|
||||
}
|
||||
|
||||
33
libobs-metal/metal-stagesurf.mm
Normal file
33
libobs-metal/metal-stagesurf.mm
Normal file
|
|
@ -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();
|
||||
}
|
||||
706
libobs-metal/metal-subsystem.hpp
Normal file
706
libobs-metal/metal-subsystem.hpp
Normal file
|
|
@ -0,0 +1,706 @@
|
|||
#pragma once
|
||||
|
||||
#include <util/AlignedNew.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
#include <util/base.h>
|
||||
#include <graphics/matrix4.h>
|
||||
#include <graphics/graphics.h>
|
||||
#include <graphics/device-exports.h>
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <AppKit/NSView.h>
|
||||
#import <QuartzCore/CoreAnimation.h>
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
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<gs_vb_data, decltype(&gs_vbdata_destroy)> vbData;
|
||||
|
||||
id<MTLBuffer> vertexBuffer;
|
||||
id<MTLBuffer> normalBuffer;
|
||||
id<MTLBuffer> colorBuffer;
|
||||
id<MTLBuffer> tangentBuffer;
|
||||
std::vector<id<MTLBuffer>> uvBuffers;
|
||||
|
||||
inline id<MTLBuffer> PrepareBuffer(void *array, size_t elementSize,
|
||||
__weak NSString *name);
|
||||
void PrepareBuffers();
|
||||
|
||||
void MakeBufferList(gs_vertex_shader *shader,
|
||||
std::vector<id<MTLBuffer>> &buffers);
|
||||
|
||||
inline id<MTLBuffer> 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<void, decltype(&bfree)> indices;
|
||||
const size_t num = 0, len = 0;
|
||||
const MTLIndexType indexType;
|
||||
|
||||
id<MTLBuffer> 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<MTLTexture> texture = nil;
|
||||
|
||||
std::vector<std::vector<uint8_t>> 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<MTLTexture> 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<MTLTexture> 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<MTLTexture> texture;
|
||||
|
||||
std::vector<uint8_t> 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<MTLSamplerState> 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<uint8_t> curValue;
|
||||
std::vector<uint8_t> 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<MTLLibrary> library;
|
||||
id<MTLFunction> function;
|
||||
std::vector<gs_shader_param> params;
|
||||
|
||||
size_t constantSize = 0;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
inline void UpdateParam(uint8_t *data, gs_shader_param ¶m);
|
||||
void UploadParams(id<MTLRenderCommandEncoder> 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<std::unique_ptr<ShaderSampler>> 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<CAMetalDrawable> nextDrawable;
|
||||
std::unique_ptr<gs_texture_2d> 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<MTLDevice> device;
|
||||
id<MTLCommandQueue> commandQueue;
|
||||
id<MTLCommandBuffer> commandBuffer;
|
||||
id<MTLRenderPipelineState> pipelineState;
|
||||
id<MTLDepthStencilState> 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<std::pair<gs_texture_2d *, ClearState>> clearStates;
|
||||
|
||||
bool piplineStateChanged = false;
|
||||
BlendState blendState;
|
||||
RasterState rasterState;
|
||||
ZStencilState zstencilState;
|
||||
|
||||
std::stack<matrix4> projStack;
|
||||
|
||||
matrix4 curProjMatrix;
|
||||
matrix4 curViewMatrix;
|
||||
matrix4 curViewProjMatrix;
|
||||
|
||||
gs_obj *first_obj = nullptr;
|
||||
|
||||
std::mutex mutexObj;
|
||||
std::vector<id<MTLBuffer>> curBufferPool;
|
||||
std::queue<std::vector<id<MTLBuffer>>> bufferPools;
|
||||
std::vector<id<MTLBuffer>> unusedBufferPool;
|
||||
|
||||
void InitDevice(uint32_t adapterIdx);
|
||||
|
||||
/* Create Draw Command */
|
||||
void SetClear();
|
||||
void LoadSamplers(id<MTLRenderCommandEncoder> commandEncoder);
|
||||
void LoadRasterState(id<MTLRenderCommandEncoder> commandEncoder);
|
||||
void LoadZStencilState(id<MTLRenderCommandEncoder> commandEncoder);
|
||||
void UpdateViewProjMatrix();
|
||||
void UploadVertexBuffer(id<MTLRenderCommandEncoder> commandEncoder);
|
||||
void UploadTextures(id<MTLRenderCommandEncoder> commandEncoder);
|
||||
void UploadSamplers(id<MTLRenderCommandEncoder> commandEncoder);
|
||||
void DrawPrimitives(id<MTLRenderCommandEncoder> 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<MTLBuffer> GetBuffer(void *data, size_t length);
|
||||
void PushResources();
|
||||
void ReleaseResources();
|
||||
|
||||
/* Other */
|
||||
void RebuildDevice();
|
||||
inline void CopyTex(id<MTLTexture> 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
|
||||
1448
libobs-metal/metal-subsystem.mm
Normal file
1448
libobs-metal/metal-subsystem.mm
Normal file
File diff suppressed because it is too large
Load diff
61
libobs-metal/metal-swapchain.mm
Normal file
61
libobs-metal/metal-swapchain.mm
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#include "metal-subsystem.hpp"
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
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;
|
||||
}
|
||||
159
libobs-metal/metal-texture2d.mm
Normal file
159
libobs-metal/metal-texture2d.mm
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#include <util/base.h>
|
||||
|
||||
#include "metal-subsystem.hpp"
|
||||
|
||||
void gs_texture_2d::GenerateMipmap()
|
||||
{
|
||||
assert(device->commandBuffer == nil);
|
||||
|
||||
if (levels == 1)
|
||||
return;
|
||||
|
||||
@autoreleasepool {
|
||||
id<MTLCommandBuffer> buf = [device->commandQueue commandBuffer];
|
||||
id<MTLBlitCommandEncoder> 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<MTLTexture> 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)
|
||||
{
|
||||
}
|
||||
136
libobs-metal/metal-vertexbuffer.mm
Normal file
136
libobs-metal/metal-vertexbuffer.mm
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include <util/base.h>
|
||||
#include <graphics/vec3.h>
|
||||
|
||||
#include "metal-subsystem.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
inline id<MTLBuffer> gs_vertex_buffer::PrepareBuffer(
|
||||
void *array, size_t elementSize, __weak NSString *name)
|
||||
{
|
||||
id<MTLBuffer> 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<id<MTLBuffer>> &buffers,
|
||||
id<MTLBuffer> 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<id<MTLBuffer>> &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<MTLBuffer> gs_vertex_buffer::InitBuffer(size_t elementSize,
|
||||
void *array, const char *name)
|
||||
{
|
||||
NSUInteger length = elementSize * vbData->num;
|
||||
MTLResourceOptions options = MTLResourceCPUCacheModeWriteCombined |
|
||||
(isDynamic ? MTLResourceStorageModeShared :
|
||||
MTLResourceStorageModeManaged);
|
||||
|
||||
id<MTLBuffer> 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<MTLBuffer> 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();
|
||||
}
|
||||
43
libobs-metal/metal-zstencilbuffer.mm
Normal file
43
libobs-metal/metal-zstencilbuffer.mm
Normal file
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
35
libobs/util/mac/mac-version.h
Normal file
35
libobs/util/mac/mac-version.h
Normal file
|
|
@ -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
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#include "base.h"
|
||||
#include "platform.h"
|
||||
#include "dstr.h"
|
||||
#include "mac/mac-version.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <time.h>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue