Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
jp9000
45a7aca993 Add passive aggressive error message if no device 2019-04-27 15:18:04 -07:00
jp9000
30c4fe4b83 You know what, just remove const 2019-04-27 14:40:28 -07:00
jp9000
1581260320 Add a number of missing funcs 2019-04-27 14:34:10 -07:00
jp9000
e6cdda5a6c Remove weak reference stuff 2019-04-27 14:11:58 -07:00
mntone
e8ea0baede libobs-metal: Add new Metal renderer. 2019-04-27 13:48:30 -07:00
26 changed files with 5029 additions and 23 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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);

View file

@ -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",

View 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()

View file

@ -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)

View 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)

View file

@ -0,0 +1,322 @@
#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;
}
}
if (device == nil)
throw "Well I guess there's no device, try using OpenGL "
"rather than this awful apple API that they made "
"with the sole purpose of making developer's lives "
"difficult.";
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);
}

View 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(void *new_indices)
{
assert(isDynamic);
indexBuffer = device->GetBuffer(new_indices, 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();
}

View 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);
}

View 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();
}

View 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 &param = 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 &param : params)
gs_shader_set_default(&param);
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 &param : params) {
param.nextSampler = nullptr;
param.curValue.clear();
param.changed = true;
gs_shader_set_default(&param);
}
}
inline void gs_shader::UpdateParam(uint8_t *data, gs_shader_param &param)
{
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 &param = shader->params[i];
if (strcmp(param.name.c_str(), name) == 0)
return &param;
}
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;
}

View 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;
}

View 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(MTLVertexDescriptor *vertexDesc);
void BuildParamInfo(ShaderBufferInfo &info);
void BuildParams(std::vector<gs_shader_param> &params);
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);

View 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,
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(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> &params,
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> &params)
{
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";
}

View 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();
}

View 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,
NSString *name);
void PrepareBuffers(const gs_vb_data *data);
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 *new_indices);
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 &param);
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

File diff suppressed because it is too large Load diff

View 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;
}

View 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)
{
}

View 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, NSString *name)
{
id<MTLBuffer> b = device->GetBuffer(array, elementSize * vbData->num);
#if _DEBUG
b.label = name;
#endif
return b;
}
void gs_vertex_buffer::PrepareBuffers(const gs_vb_data *data)
{
assert(isDynamic);
vertexBuffer = PrepareBuffer(data->points, sizeof(vec3), @"point");
if (data->normals)
normalBuffer = PrepareBuffer(data->normals, sizeof(vec3),
@"normal");
if (data->tangents)
tangentBuffer = PrepareBuffer(data->tangents, sizeof(vec3),
@"color");
if (data->colors)
colorBuffer = PrepareBuffer(data->colors, sizeof(uint32_t),
@"tangent");
for (size_t i = 0; i < data->num_tex; i++) {
gs_tvertarray &tv = data->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();
}

View 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();
}

View file

@ -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

View file

@ -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);

View 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

View file

@ -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;
}