diff --git a/CMakeLists.txt b/CMakeLists.txt index 146d4afdc..601f01370 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,11 @@ else() set(WITH_RENDER_DX11_DEFAULT OFF) set(WITH_RENDER_DX9_DEFAULT OFF) endif() +if (APPLE) + set(WITH_RENDER_METAL_DEFAULT ON) +else() + set(WITH_RENDER_METAL_DEFAULT OFF) +endif() if (ANDROID) set(USE_GLES_DEFAULT ON) @@ -100,6 +105,7 @@ option(WITH_RENDER_OPENGL "Whether to build with the OpenGL render backend." ${W option(WITH_RENDER_OPENGL1 "Whether to build with the OpenGL 1.1 render backend." ${WITH_RENDER_OPENGL1_DEFAULT}) option(WITH_RENDER_DX11 "Whether to build with the DirectX 11 render backend." ${WITH_RENDER_DX11_DEFAULT}) option(WITH_RENDER_DX9 "Whether to build with the DirectX 9 render backend." ${WITH_RENDER_DX9_DEFAULT}) +option(WITH_RENDER_METAL "Whether to build with the Metal render backend." ${WITH_RENDER_METAL_DEFAULT}) option(USE_GLES "Use OpenGL ES for the OpenGL render backend." ${USE_GLES_DEFAULT}) option(USE_FREETYPE "Use FreeType for font rendering." ON) option(SYSTEM_FFTW "Use a system-installed version of FFTW instead of the vendored one" OFF) @@ -403,12 +409,6 @@ else() endif() endif() -if (BUILD_GUI) - if (NOT WITH_RENDER_SDL AND NOT WITH_RENDER_OPENGL AND NOT WITH_RENDER_OPENGL1 AND NOT WITH_RENDER_DX11 AND NOT WITH_RENDER_DX9) - message(FATAL_ERROR "No render backends selected!") - endif() -endif() - set(AUDIO_SOURCES src/audio/abstract.cpp src/audio/midi.cpp @@ -962,6 +962,19 @@ endif() # endif() #endif() +if (WITH_RENDER_METAL) + if (APPLE) + list(APPEND GUI_SOURCES src/gui/render/renderMetal.mm) + list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_metal.mm) + list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_METAL) + list(APPEND DEPENDENCIES_LIBRARIES "-framework Metal") + list(APPEND DEPENDENCIES_LIBRARIES "-framework MetalKit") + message(STATUS "UI render backend: Metal") + else() + message(FATAL_ERROR "Metal render backend only for Apple operating systems!") + endif() +endif() + if (NOT WIN32 AND NOT APPLE) CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) diff --git a/extern/imgui_patched/backends/imgui_impl_metal.h b/extern/imgui_patched/backends/imgui_impl_metal.h index 83b3ee8d0..d494540df 100644 --- a/extern/imgui_patched/backends/imgui_impl_metal.h +++ b/extern/imgui_patched/backends/imgui_impl_metal.h @@ -25,7 +25,7 @@ IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device); IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); +IMGUI_IMPL_API bool ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id commandBuffer, id commandEncoder); @@ -51,7 +51,7 @@ IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device); IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); +IMGUI_IMPL_API bool ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, MTL::CommandBuffer* commandBuffer, MTL::RenderCommandEncoder* commandEncoder); diff --git a/extern/imgui_patched/backends/imgui_impl_metal.mm b/extern/imgui_patched/backends/imgui_impl_metal.mm index b29b4f26a..1480c5026 100644 --- a/extern/imgui_patched/backends/imgui_impl_metal.mm +++ b/extern/imgui_patched/backends/imgui_impl_metal.mm @@ -99,9 +99,9 @@ bool ImGui_ImplMetal_Init(MTL::Device* device) return ImGui_ImplMetal_Init((__bridge id)(device)); } -void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) +bool ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) { - ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); + return ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); } void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, @@ -160,14 +160,19 @@ void ImGui_ImplMetal_Shutdown() io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); } -void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) +bool ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); IM_ASSERT(bd->SharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?"); + if (bd->SharedMetalContext.framebufferDescriptor != nil) { + [bd->SharedMetalContext.framebufferDescriptor release]; + } bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; if (bd->SharedMetalContext.depthStencilState == nil) - ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); + return ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); + + return true; } static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id commandBuffer, @@ -231,6 +236,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id c id renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; if (renderPipelineState == nil) { + printf("RPS NULL....\n"); // No luck; make a new render pipeline state renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device]; @@ -376,9 +382,7 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); - ImGui_ImplMetal_CreateFontsTexture(device); - - return true; + return ImGui_ImplMetal_CreateFontsTexture(device); } void ImGui_ImplMetal_DestroyDeviceObjects() diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 024cb9fe6..ad68bce99 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -199,6 +199,12 @@ const char* aboutLine[]={ "plane", "TheEssem", "", + "-- Metal backend test team --", + "Diggo", + "konard", + "NaxeCode", + "scratchminer", + "", "powered by:", "Dear ImGui by Omar Cornut", "SDL2 by Sam Lantinga", diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index ed5057915..fdaf3b066 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -610,6 +610,54 @@ void FurnaceGUI::drawDebug() { ImGui::Unindent(); ImGui::TreePop(); } + if (ImGui::TreeNode("Texture Test")) { + ImGui::Text("Create and Destroy 128 Textures"); + if (ImGui::Button("No Write")) { + for (int i=0; i<128; i++) { + FurnaceGUITexture* t=rend->createTexture(false,2048,2048); + if (t==NULL) { + showError(fmt::sprintf("Failure! %d",i)); + break; + } + rend->destroyTexture(t); + } + } + if (ImGui::Button("Write (update)")) { + unsigned char* data=new unsigned char[2048*2048*4]; + for (int i=0; i<2048*2048*4; i++) { + data[i]=rand(); + } + for (int i=0; i<128; i++) { + FurnaceGUITexture* t=rend->createTexture(false,2048,2048); + if (t==NULL) { + showError(fmt::sprintf("Failure! %d",i)); + break; + } + rend->updateTexture(t,data,2048*4); + rend->destroyTexture(t); + } + delete[] data; + } + if (ImGui::Button("Write (lock)")) { + unsigned char* data=NULL; + int pitch=0; + for (int i=0; i<128; i++) { + FurnaceGUITexture* t=rend->createTexture(false,2048,2048); + if (t==NULL) { + showError(fmt::sprintf("Failure! %d",i)); + break; + } + if (rend->lockTexture(t,(void**)&data,&pitch)) { + for (int i=0; i<2048*2048*4; i++) { + data[i]=rand(); + } + rend->unlockTexture(t); + } + rend->destroyTexture(t); + } + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Osc Render Test")) { ImGui::InputInt("Length",&oscDebugLen); ImGui::InputInt("Height",&oscDebugHeight); diff --git a/src/gui/gui.h b/src/gui/gui.h index 2faa77d20..5571f660b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -75,6 +75,7 @@ enum FurnaceGUIRenderBackend { GUI_BACKEND_GL1, GUI_BACKEND_DX11, GUI_BACKEND_DX9, + GUI_BACKEND_METAL, GUI_BACKEND_SOFTWARE }; @@ -82,6 +83,10 @@ enum FurnaceGUIRenderBackend { #define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11 #define GUI_BACKEND_DEFAULT_NAME "DirectX 11" #else +#ifdef HAVE_RENDER_METAL +#define GUI_BACKEND_DEFAULT GUI_BACKEND_METAL +#define GUI_BACKEND_DEFAULT_NAME "Metal" +#else #ifdef HAVE_RENDER_GL #ifdef SUPPORT_XP #define GUI_BACKEND_DEFAULT GUI_BACKEND_GL1 @@ -105,6 +110,7 @@ enum FurnaceGUIRenderBackend { #endif #endif #endif +#endif // TODO: // - add colors for FM envelope and waveform diff --git a/src/gui/render.cpp b/src/gui/render.cpp index d803ebbe3..81dcc2dc2 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -31,6 +31,9 @@ #ifdef HAVE_RENDER_DX11 #include "render/renderDX11.h" #endif +#ifdef HAVE_RENDER_METAL +#include "render/renderMetal.h" +#endif #include "render/renderSoftware.h" bool FurnaceGUI::initRender() { @@ -50,6 +53,8 @@ bool FurnaceGUI::initRender() { renderBackend=GUI_BACKEND_DX11; } else if (settings.renderBackend=="DirectX 9") { renderBackend=GUI_BACKEND_DX9; + } else if (settings.renderBackend=="Metal") { + renderBackend=GUI_BACKEND_METAL; } else if (settings.renderBackend=="SDL") { renderBackend=GUI_BACKEND_SDL; } else if (settings.renderBackend=="Software") { @@ -98,6 +103,12 @@ bool FurnaceGUI::initRender() { rend=new FurnaceGUIRenderDX9; break; #endif +#ifdef HAVE_RENDER_METAL + case GUI_BACKEND_METAL: + logI("render backend: Metal"); + rend=new FurnaceGUIRenderMetal; + break; +#endif #ifdef HAVE_RENDER_SDL case GUI_BACKEND_SDL: logI("render backend: SDL_Renderer"); diff --git a/src/gui/render/renderMetal.h b/src/gui/render/renderMetal.h new file mode 100644 index 000000000..ed6219b3a --- /dev/null +++ b/src/gui/render/renderMetal.h @@ -0,0 +1,57 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../gui.h" + +struct FurnaceGUIRenderMetalPrivate; + +class FurnaceGUIRenderMetal: public FurnaceGUIRender { + SDL_Renderer* sdlRend; + FurnaceGUIRenderMetalPrivate* priv; + bool swapIntervalSet; + public: + ImTextureID getTextureID(FurnaceGUITexture* which); + bool lockTexture(FurnaceGUITexture* which, void** data, int* pitch); + bool unlockTexture(FurnaceGUITexture* which); + bool updateTexture(FurnaceGUITexture* which, void* data, int pitch); + FurnaceGUITexture* createTexture(bool dynamic, int width, int height, bool interpolate=true); + bool destroyTexture(FurnaceGUITexture* which); + void setTextureBlendMode(FurnaceGUITexture* which, FurnaceGUIBlendMode mode); + void setBlendMode(FurnaceGUIBlendMode mode); + void clear(ImVec4 color); + bool newFrame(); + bool canVSync(); + void createFontsTexture(); + void destroyFontsTexture(); + void renderGUI(); + void wipe(float alpha); + void present(); + bool getOutputSize(int& w, int& h); + int getWindowFlags(); + void setSwapInterval(int swapInterval); + void preInit(); + bool init(SDL_Window* win, int swapInterval); + void initGUI(SDL_Window* win); + void quitGUI(); + bool quit(); + FurnaceGUIRenderMetal(): + sdlRend(NULL), + priv(NULL), + swapIntervalSet(false) {} +}; diff --git a/src/gui/render/renderMetal.mm b/src/gui/render/renderMetal.mm new file mode 100644 index 000000000..48da7278d --- /dev/null +++ b/src/gui/render/renderMetal.mm @@ -0,0 +1,248 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "renderMetal.h" +#include "backends/imgui_impl_metal.h" + +#include +#include + +struct FurnaceGUIRenderMetalPrivate { + CAMetalLayer* context; + id cmdQueue; + id cmdBuf; + id renderEncoder; + id drawable; + MTLRenderPassDescriptor* renderPass; + + FurnaceGUIRenderMetalPrivate(): + context(NULL), + cmdQueue(NULL), + cmdBuf(NULL), + renderEncoder(NULL), + drawable(NULL), + renderPass(NULL) {} +}; + +class FurnaceMetalTexture: public FurnaceGUITexture { + public: + id tex; + int width, height; + unsigned char* lockedData; + FurnaceMetalTexture(): + tex(NULL), + width(0), + height(0), + lockedData(NULL) {} +}; + +ImTextureID FurnaceGUIRenderMetal::getTextureID(FurnaceGUITexture* which) { + FurnaceMetalTexture* t=(FurnaceMetalTexture*)which; + return t->tex; +} + +bool FurnaceGUIRenderMetal::lockTexture(FurnaceGUITexture* which, void** data, int* pitch) { + FurnaceMetalTexture* t=(FurnaceMetalTexture*)which; + if (t->lockedData!=NULL) return false; + t->lockedData=new unsigned char[t->width*t->height*4]; + + *data=t->lockedData; + *pitch=t->width*4; + return true; +} + +bool FurnaceGUIRenderMetal::unlockTexture(FurnaceGUITexture* which) { + FurnaceMetalTexture* t=(FurnaceMetalTexture*)which; + if (t->lockedData==NULL) return false; + + [t->tex replaceRegion:MTLRegionMake2D(0,0,(NSUInteger)t->width,(NSUInteger)t->height) mipmapLevel:0 withBytes:t->lockedData bytesPerRow:(NSUInteger)t->width*4]; + delete[] t->lockedData; + t->lockedData=NULL; + + return true; +} + +bool FurnaceGUIRenderMetal::updateTexture(FurnaceGUITexture* which, void* data, int pitch) { + FurnaceMetalTexture* t=(FurnaceMetalTexture*)which; + [t->tex replaceRegion:MTLRegionMake2D(0,0,(NSUInteger)t->width,(NSUInteger)t->height) mipmapLevel:0 withBytes:data bytesPerRow:(NSUInteger)pitch]; + return true; +} + +FurnaceGUITexture* FurnaceGUIRenderMetal::createTexture(bool dynamic, int width, int height, bool interpolate) { + MTLTextureDescriptor* texDesc=[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:(NSUInteger)width height:(NSUInteger)height mipmapped:NO]; + texDesc.usage=MTLTextureUsageShaderRead; + texDesc.storageMode=MTLStorageModeManaged; + + id texture=[priv->context.device newTextureWithDescriptor:texDesc]; + + if (texture==NULL) return NULL; + FurnaceMetalTexture* ret=new FurnaceMetalTexture; + ret->tex=texture; + ret->width=width; + ret->height=height; + + [texDesc release]; + + return ret; +} + +bool FurnaceGUIRenderMetal::destroyTexture(FurnaceGUITexture* which) { + FurnaceMetalTexture* t=(FurnaceMetalTexture*)which; + [t->tex release]; + t->tex=NULL; + delete t; + return true; +} + +void FurnaceGUIRenderMetal::setTextureBlendMode(FurnaceGUITexture* which, FurnaceGUIBlendMode mode) { +} + +void FurnaceGUIRenderMetal::setBlendMode(FurnaceGUIBlendMode mode) { +} + +// you should only call this once!!! +void FurnaceGUIRenderMetal::clear(ImVec4 color) { + int outW, outH; + getOutputSize(outW,outH); + priv->context.drawableSize=CGSizeMake(outW,outH); + + if (priv->drawable) { + [priv->drawable release]; + } + if (priv->cmdBuf) { + [priv->cmdBuf release]; + } + + priv->drawable=[priv->context nextDrawable]; + + priv->cmdBuf=[priv->cmdQueue commandBuffer]; + priv->renderPass.colorAttachments[0].clearColor=MTLClearColorMake(color.x,color.y,color.z,color.w); + priv->renderPass.colorAttachments[0].texture=priv->drawable.texture; + priv->renderPass.colorAttachments[0].loadAction=MTLLoadActionClear; + priv->renderPass.colorAttachments[0].storeAction=MTLStoreActionStore; + priv->renderEncoder=[priv->cmdBuf renderCommandEncoderWithDescriptor:priv->renderPass]; +} + +bool FurnaceGUIRenderMetal::newFrame() { + return ImGui_ImplMetal_NewFrame(priv->renderPass); +} + +bool FurnaceGUIRenderMetal::canVSync() { + return swapIntervalSet; +} + +void FurnaceGUIRenderMetal::createFontsTexture() { + ImGui_ImplMetal_CreateFontsTexture(priv->context.device); +} + +void FurnaceGUIRenderMetal::destroyFontsTexture() { + ImGui_ImplMetal_DestroyFontsTexture(); +} + +void FurnaceGUIRenderMetal::renderGUI() { + ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(),priv->cmdBuf,priv->renderEncoder); +} + +void FurnaceGUIRenderMetal::wipe(float alpha) { + // you know what? cheat. + // I don't feel like learning yet another API just to please Apple. + // screw it. + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(sdlRend,0,0,0,255*alpha); + SDL_RenderFillRect(sdlRend,NULL); +} + +void FurnaceGUIRenderMetal::present() { + [priv->renderEncoder endEncoding]; + + [priv->cmdBuf presentDrawable:priv->drawable]; + [priv->cmdBuf commit]; + + [priv->renderEncoder release]; +} + +bool FurnaceGUIRenderMetal::getOutputSize(int& w, int& h) { + return SDL_GetRendererOutputSize(sdlRend,&w,&h)==0; +} + +int FurnaceGUIRenderMetal::getWindowFlags() { + return 0; +} + +void FurnaceGUIRenderMetal::setSwapInterval(int swapInterval) { + if (SDL_RenderSetVSync(sdlRend,(swapInterval>=0)?1:0)!=0) { + swapIntervalSet=false; + logW("tried to enable VSync but couldn't!"); + } else { + swapIntervalSet=true; + } +} + +void FurnaceGUIRenderMetal::preInit() { + SDL_SetHint(SDL_HINT_RENDER_DRIVER,"metal"); + priv=new FurnaceGUIRenderMetalPrivate; +} + +bool FurnaceGUIRenderMetal::init(SDL_Window* win, int swapInterval) { + SDL_SetHint(SDL_HINT_RENDER_DRIVER,"metal"); + + sdlRend=SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); + + if (sdlRend==NULL) return false; + + if (SDL_RenderSetVSync(sdlRend,(swapInterval>=0)?1:0)!=0) { + swapIntervalSet=false; + logW("tried to enable VSync but couldn't!"); + } else { + swapIntervalSet=true; + } + + logI("retrieving context..."); + + priv->context=(__bridge CAMetalLayer*)SDL_RenderGetMetalLayer(sdlRend); + + if (priv->context==NULL) { + logE("Metal layer is NULL!"); + return false; + } + + priv->context.pixelFormat=MTLPixelFormatBGRA8Unorm; + + priv->cmdQueue=[priv->context.device newCommandQueue]; + priv->renderPass=[MTLRenderPassDescriptor new]; + return true; +} + +void FurnaceGUIRenderMetal::initGUI(SDL_Window* win) { + ImGui_ImplMetal_Init(priv->context.device); + ImGui_ImplSDL2_InitForMetal(win); +} + +void FurnaceGUIRenderMetal::quitGUI() { + ImGui_ImplMetal_Shutdown(); +} + +bool FurnaceGUIRenderMetal::quit() { + if (sdlRend==NULL) return false; + [priv->renderPass release]; + [priv->cmdQueue release]; + SDL_DestroyRenderer(sdlRend); + sdlRend=NULL; + return true; +} diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 7d12db269..a75e6927f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -405,6 +405,12 @@ void FurnaceGUI::drawSettings() { settingsChanged=true; } #endif +#ifdef HAVE_RENDER_METAL + if (ImGui::Selectable("Metal",curRenderBackend=="Metal")) { + settings.renderBackend="Metal"; + settingsChanged=true; + } +#endif #ifdef HAVE_RENDER_GL #ifdef USE_GLES if (ImGui::Selectable("OpenGL ES 2.0",curRenderBackend=="OpenGL ES 2.0")) { diff --git a/src/main.cpp b/src/main.cpp index 91a5e8302..e91e302ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -393,7 +393,7 @@ void reportError(String what) { logE("%s",what); MessageBox(NULL,what.c_str(),"Furnace",MB_OK|MB_ICONERROR); } -#elif defined(ANDROID) +#elif defined(ANDROID) || defined(__APPLE__) void reportError(String what) { logE("%s",what); #ifdef HAVE_SDL2