diff --git a/cmake/HalleyProject.cmake b/cmake/HalleyProject.cmake index 19372747c0..91634cf838 100644 --- a/cmake/HalleyProject.cmake +++ b/cmake/HalleyProject.cmake @@ -180,7 +180,8 @@ endif () if (APPLE) set(USE_AVFOUNDATION 1) - set(USE_METAL 0) + set(USE_METAL 1) + set(USE_OPENGL 0) set(USE_ASIO 1) endif () diff --git a/scripts/build_editor_osx.sh b/scripts/build_editor_osx.sh new file mode 100755 index 0000000000..3f4cb0dbaa --- /dev/null +++ b/scripts/build_editor_osx.sh @@ -0,0 +1,49 @@ +#!/bin/sh -e + +root=$(pwd) +arch=$(uname -m) +script=$(realpath "$0") +script_path=$(dirname "$script") + +mkdir -p build + +mkdir -p ${script_path}/../bin +cp ${script_path}/../deps/osx/${arch}/lib/libShaderConductor.dylib libShaderConductor.dylib + +# +# generate cmake project +# +cd ${root}/build +rm -f CMakeCache.txt + +cmake -G "Xcode" \ + -DHALLEY_PATH="../halley" \ + -DBUILD_HALLEY_TOOLS=1 \ + -DBUILD_HALLEY_TESTS=0 \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DHALLEY_ENABLE_STATIC_STDLIB=1 \ + -DCMAKE_INCLUDE_PATH="${script_path}/../deps/osx/${arch}/include" \ + -DCMAKE_LIBRARY_PATH="${script_path}/../deps/osx/${arch}/lib" \ + -DSDL2_INCLUDE_DIR="${script_path}/../deps/osx/${arch}/include/SDL2" \ + -DSDL2_LIBRARIES="${script_path}/../deps/osx/${arch}/libSDL2.a" \ + -DShaderConductor_INCLUDE_DIR="${script_path}/../deps/osx/${arch}/include/ShaderConductor" \ + -DShaderConductor_LIBRARY="${script_path}/../deps/osx/${arch}/lib/libShaderConductor.dylib" \ + -DBoost_INCLUDE_DIR="${script_path}/../deps/Boost/include/boost-1_81" \ + -DBoost_USE_STATIC_LIBS=1 \ + .. +# +# compile halley-cmd +# +cmake --build . --target halley-cmd --config RelWithDebInfo -j 4 + +# +# import assets & code gen +# +cd ${script_path}/../bin +./halley-cmd import ${root} ${root}/halley/ + +# +# compile halley-editor +# +cd ${root}/build +cmake --build . --target halley-editor --config RelWithDebInfo -j 4 diff --git a/src/plugins/metal/CMakeLists.txt b/src/plugins/metal/CMakeLists.txt index f2590229fa..e81a3ab9d6 100644 --- a/src/plugins/metal/CMakeLists.txt +++ b/src/plugins/metal/CMakeLists.txt @@ -1,6 +1,6 @@ project (halley-metal) -include_directories(${Boost_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} "../../engine/utils/include" "../../engine/core/include") +include_directories(${Boost_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} "../../engine/utils/include" "../../engine/core/include") set(SOURCES "src/metal_plugin.mm" @@ -13,6 +13,7 @@ set(SOURCES "src/metal_shader.mm" "src/metal_texture.mm" "src/metal_video.mm" + "src/metal_depth_stencil.mm" ) set(HEADERS @@ -24,6 +25,7 @@ set(HEADERS "src/metal_shader.h" "src/metal_texture.h" "src/metal_video.h" + "src/metal_depth_stencil.h" ) assign_source_group(${SOURCES}) @@ -36,3 +38,7 @@ set_target_properties(halley-metal PROPERTIES DISABLE_PRECOMPILE_HEADERS ON) find_library(METAL_LIBRARY Metal) find_library(QUARTZ_LIBRARY QuartzCore) target_link_libraries(halley-metal halley-engine "${METAL_LIBRARY}" "${QUARTZ_LIBRARY}") + +if(IOS) + target_link_libraries( halley-metal "${SDL2_LIBRARIES}" ) +endif() \ No newline at end of file diff --git a/src/plugins/metal/src/metal_buffer.mm b/src/plugins/metal/src/metal_buffer.mm index 57607f2887..a630183ee1 100644 --- a/src/plugins/metal/src/metal_buffer.mm +++ b/src/plugins/metal/src/metal_buffer.mm @@ -9,12 +9,12 @@ {} MetalBuffer::~MetalBuffer() { - [buffer setPurgeableState:MTLPurgeableStateEmpty]; - [buffer release]; + video.addBufferToRelease( buffer ); + buffer = nil; } void MetalBuffer::setData(gsl::span data) { - auto oldBuffer = std::move(buffer); + auto oldBuffer = buffer; buffer = [video.getDevice() newBufferWithBytes:data.data() length:data.size_bytes() options:MTLResourceStorageModeShared]; [oldBuffer setPurgeableState:MTLPurgeableStateEmpty]; [oldBuffer release]; diff --git a/src/plugins/metal/src/metal_depth_stencil.h b/src/plugins/metal/src/metal_depth_stencil.h new file mode 100644 index 0000000000..854379a0e0 --- /dev/null +++ b/src/plugins/metal/src/metal_depth_stencil.h @@ -0,0 +1,27 @@ +#pragma once +#include +#undef min +#undef max +#include "halley/graphics/material/material_definition.h" + +namespace Halley +{ + class MaterialDepthStencil; + class MetalVideo; + + class MetalDepthStencil + { + public: + MetalDepthStencil(MetalVideo& video, const MaterialDepthStencil& definition); + ~MetalDepthStencil(); + + const MaterialDepthStencil& getDefinition() const; + void bind(id descriptor); + + private: + MetalVideo& video; + id state = nil; + MaterialDepthStencil definition; + int reference = 1; + }; +} diff --git a/src/plugins/metal/src/metal_depth_stencil.mm b/src/plugins/metal/src/metal_depth_stencil.mm new file mode 100644 index 0000000000..6ea804c853 --- /dev/null +++ b/src/plugins/metal/src/metal_depth_stencil.mm @@ -0,0 +1,105 @@ +#include "metal_depth_stencil.h" +#include "metal_video.h" +using namespace Halley; + +static MTLCompareFunction getComparisonFunc(DepthStencilComparisonFunction f) +{ + switch (f) { + case DepthStencilComparisonFunction::Always: + return MTLCompareFunctionAlways; + case DepthStencilComparisonFunction::Never: + return MTLCompareFunctionNever; + case DepthStencilComparisonFunction::Equal: + return MTLCompareFunctionEqual; + case DepthStencilComparisonFunction::NotEqual: + return MTLCompareFunctionNotEqual; + case DepthStencilComparisonFunction::Less: + return MTLCompareFunctionLess; + case DepthStencilComparisonFunction::LessEqual: + return MTLCompareFunctionLessEqual; + case DepthStencilComparisonFunction::Greater: + return MTLCompareFunctionGreater; + case DepthStencilComparisonFunction::GreaterEqual: + return MTLCompareFunctionGreaterEqual; + } + + return MTLCompareFunctionNever; +} + +static MTLStencilOperation getOperation(StencilWriteOperation op) +{ + switch (op) { + case StencilWriteOperation::Zero: + return MTLStencilOperationZero; + case StencilWriteOperation::Invert: + return MTLStencilOperationInvert; + case StencilWriteOperation::Keep: + return MTLStencilOperationKeep; + case StencilWriteOperation::Replace: + return MTLStencilOperationReplace; + case StencilWriteOperation::IncrementClamp: + return MTLStencilOperationIncrementClamp; + case StencilWriteOperation::IncrementWrap: + return MTLStencilOperationIncrementWrap; + case StencilWriteOperation::DecrementClamp: + return MTLStencilOperationDecrementClamp; + case StencilWriteOperation::DecrementWrap: + return MTLStencilOperationDecrementWrap; + } + return MTLStencilOperationKeep; +} + +MetalDepthStencil::MetalDepthStencil(MetalVideo& video, const MaterialDepthStencil& definition) + : video(video) + , definition(definition) +{ + MTLDepthStencilDescriptor * desc = [[MTLDepthStencilDescriptor alloc] init]; + + desc.depthWriteEnabled = definition.isDepthWriteEnabled(); + desc.depthCompareFunction = getComparisonFunc(definition.isDepthTestEnabled() ? definition.getDepthComparisonFunction() : DepthStencilComparisonFunction::Always); + + if( definition.isStencilTestEnabled() ) { + + MTLStencilDescriptor *stencil_desc = [[MTLStencilDescriptor alloc] init]; + + stencil_desc.readMask = definition.getStencilReadMask(); + stencil_desc.writeMask = definition.getStencilWriteMask(); + + stencil_desc.stencilFailureOperation = getOperation(definition.getStencilOpStencilFail()); + stencil_desc.depthFailureOperation = getOperation(definition.getStencilOpDepthFail()); + stencil_desc.depthStencilPassOperation = getOperation(definition.getStencilOpPass()); + stencil_desc.stencilCompareFunction = getComparisonFunc(definition.getStencilComparisonFunction()); + + desc.frontFaceStencil = stencil_desc; + desc.backFaceStencil = stencil_desc; + + reference = definition.getStencilReference(); + } + + state = [video.getDevice() newDepthStencilStateWithDescriptor:desc]; + if (state == nil) { + throw Exception("Unable to create DepthStencil state", HalleyExceptions::VideoPlugin); + } +} + +MetalDepthStencil::~MetalDepthStencil() +{ + if (state) { + [state release]; + state = nullptr; + } +} + +const MaterialDepthStencil& MetalDepthStencil::getDefinition() const +{ + return definition; +} + +void MetalDepthStencil::bind( id encoder ) +{ + [encoder setDepthStencilState: state]; + if (definition.isStencilTestEnabled()) + { + [encoder setStencilReferenceValue: reference]; + } +} diff --git a/src/plugins/metal/src/metal_material_constant_buffer.mm b/src/plugins/metal/src/metal_material_constant_buffer.mm index 434c507729..f435a183c3 100644 --- a/src/plugins/metal/src/metal_material_constant_buffer.mm +++ b/src/plugins/metal/src/metal_material_constant_buffer.mm @@ -14,13 +14,13 @@ void MetalMaterialConstantBuffer::update(gsl::span data) { // We must pad up to a multiple of 16 (float4) // TODO we ought to move this somewhere it won't be called so often. - const size_t padding = alignUp(data.size_bytes(), 16); + const size_t padded_size = alignUp(data.size_bytes(), 16); - auto padded = malloc(data.size_bytes() + padding); + auto padded = malloc(padded_size); memcpy(padded, data.data(), data.size_bytes()); buffer.setData(gsl::span{reinterpret_cast(padded), - static_cast(static_cast(data.size_bytes() + padding))}); + static_cast(static_cast(padded_size))}); free(padded); } diff --git a/src/plugins/metal/src/metal_painter.h b/src/plugins/metal/src/metal_painter.h index 34a9c9d9a3..3e0111ff74 100644 --- a/src/plugins/metal/src/metal_painter.h +++ b/src/plugins/metal/src/metal_painter.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include "metal_depth_stencil.h" +#include "metal_render_target.h" namespace Halley { class MetalVideo; @@ -19,16 +21,27 @@ namespace Halley { void setClip(Rect4i clip, bool enable) override; void setMaterialData(const Material& material) override; void onUpdateProjection(Material& material, bool hashChanged) override; - void startEncoding(id texture); + void startEncoding(IMetalRenderTarget * renderTarget); void endEncoding(); private: void setBlending(BlendType blendType, MTLRenderPipelineColorAttachmentDescriptor* colorAttachment); + void setDepthStencil(const MaterialDepthStencil& depthStencilDefinition); void setBlendFactor(MTLRenderPipelineColorAttachmentDescriptor* colorAttachment, MTLBlendFactor src, MTLBlendFactor dst); - MTLRenderPassDescriptor* renderPassDescriptorForTextureAndColour(id texture, Colour& colour); + MTLRenderPassDescriptor* renderPassDescriptorForTexture(id texture, id depthTexture); + void ensureEncoder(); + + MetalDepthStencil& getDepthStencil(const MaterialDepthStencil& depthStencilDefinition); MetalVideo& video; id encoder; id indexBuffer; + MTLRenderPassDescriptor* nextRenderPassDescriptor; + IMetalRenderTarget *currentRenderTarget = nullptr; + std::optional viewPort; + std::optional clipRect; + + HashMap> depthStencils; + MetalDepthStencil* curDepthStencil = nullptr; }; } diff --git a/src/plugins/metal/src/metal_painter.mm b/src/plugins/metal/src/metal_painter.mm index 5a45a3c512..9604e6a0ed 100644 --- a/src/plugins/metal/src/metal_painter.mm +++ b/src/plugins/metal/src/metal_painter.mm @@ -8,19 +8,33 @@ using namespace Halley; MetalPainter::MetalPainter(MetalVideo& video, Resources& resources) - : Painter(resources) + : Painter(video, resources) , video(video) , indexBuffer(nil) + , nextRenderPassDescriptor(nil) {} void MetalPainter::doClear(std::optional colour, std::optional depth, std::optional stencil) { - [encoder endEncoding]; - auto& renderTarget = dynamic_cast(getActiveRenderTarget()); + + if(!nextRenderPassDescriptor) { + throw Exception( "Clearing without bound rendertarget", HalleyExceptions::VideoPlugin); + } + if (colour) { - auto descriptor = renderPassDescriptorForTextureAndColour(renderTarget.getMetalTexture(), colour.value()); - encoder = [video.getCommandBuffer() renderCommandEncoderWithDescriptor:descriptor]; + nextRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(colour->r, colour->g, colour->b, colour->a); + nextRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + } + + if (depth && nextRenderPassDescriptor.depthAttachment.texture) { + nextRenderPassDescriptor.depthAttachment.clearDepth = *depth; + nextRenderPassDescriptor.depthAttachment.loadAction = MTLLoadActionClear; + } + + if (stencil && nextRenderPassDescriptor.stencilAttachment.texture) { + throw Exception( "Not implemented yet", HalleyExceptions::Graphics ); + //nextRenderPassDescriptor.stencilAttachment.clearStencil = *stencil; + //nextRenderPassDescriptor.stencilAttachment.loadAction = MTLLoadActionClear; } - // TODO: depth and stencil } void MetalPainter::setMaterialPass(const Material& material, int passNumber) { @@ -30,10 +44,17 @@ auto pipelineStateDescriptor = shader.setupMaterial(material); setBlending(pass.getBlend(), pipelineStateDescriptor.colorAttachments[0]); + setDepthStencil(material.getDepthStencil(passNumber)); + + if( currentRenderTarget && currentRenderTarget->getMetalDepthTexture() ) { + pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float; + } + NSError* error = NULL; id pipelineState = [[video.getDevice() newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error ] autorelease]; + if (!pipelineState) { std::cout << "Failed to create pipeline descriptor for material " << material.getDefinition().getName() << ", pass " << passNumber << "." << std::endl; @@ -43,13 +64,19 @@ [encoder setRenderPipelineState:pipelineState]; // Metal requires the global material to be bound for each material pass, as it has no 'global' state. - static_cast(halleyGlobalMaterial->getDataBlocks().front().getConstantBuffer()).bindVertex(encoder, 0); + static_cast(getConstantBuffer(halleyGlobalMaterial->getDataBlocks().front())).bindVertex(encoder, 0); // Bind textures - int texIndex = 0; - for (auto& tex : material.getTextures()) { - auto texture = std::static_pointer_cast(tex); - texture->bind(encoder, texIndex++); + int textureUnit = 0; + for (auto& tex : material.getDefinition().getTextures()) { + auto texture = std::static_pointer_cast(material.getTexture(textureUnit)); + if (!texture) { + throw Exception("Error binding texture to texture unit #" + toString(textureUnit) + " with material \"" + material.getDefinition().getName() + "\": texture is null.", HalleyExceptions::VideoPlugin); + } + + texture->bind(encoder, textureUnit); + + ++textureUnit; } } @@ -59,20 +86,22 @@ void MetalPainter::doEndRender() { } -void MetalPainter::startEncoding(id texture) { - auto col = Colour4f(0); - auto descriptor = renderPassDescriptorForTextureAndColour(texture, col); - encoder = [video.getCommandBuffer() renderCommandEncoderWithDescriptor:descriptor]; +void MetalPainter::startEncoding(IMetalRenderTarget * renderTarget) { + nextRenderPassDescriptor = renderPassDescriptorForTexture(renderTarget->getMetalTexture(), renderTarget->getMetalDepthTexture()); + currentRenderTarget = renderTarget; } void MetalPainter::endEncoding() { + ensureEncoder(); // :TRICKY: If no draw occured, we still need to clear [encoder endEncoding]; + encoder = nil; } void MetalPainter::setVertices( const MaterialDefinition& material, size_t numVertices, const void* vertexData, size_t numIndices, const IndexType* indices, bool standardQuadsOnly ) { + ensureEncoder(); Expects(numVertices > 0); Expects(numIndices >= numVertices); Expects(vertexData); @@ -106,35 +135,61 @@ } void MetalPainter::setViewPort(Rect4i rect) { - [encoder setViewport:(MTLViewport){ - static_cast(rect.getTopLeft().x), - static_cast(rect.getTopLeft().y), - static_cast(rect.getWidth()), - static_cast(rect.getHeight()), - 0.0, 1.0 - }]; + if( encoder ) { + int scaleFactor = dynamic_cast( getActiveRenderTarget() ).getScaleFactor(); + [encoder setViewport:(MTLViewport){ + static_cast(rect.getTopLeft().x * scaleFactor), + static_cast(rect.getTopLeft().y * scaleFactor), + static_cast(rect.getWidth() * scaleFactor), + static_cast(rect.getHeight() * scaleFactor), + 0.0, 1.0 + }]; + } else { + viewPort = rect; + } } void MetalPainter::setClip(Rect4i rect, bool) { - [encoder setScissorRect:(MTLScissorRect){ - static_cast(rect.getTopLeft().x), - static_cast(rect.getTopLeft().y), - static_cast(rect.getWidth()), - static_cast(rect.getHeight()) - }]; + Rect4i screenRect = rect.intersection( getActiveRenderTarget().getViewPort() ); + + // Tricky: it can happen the position is outside the screen + // In this case, the intersection will be empty + // yet position is still outside the area and metal does not like it + if( screenRect.isEmpty() ) + { + screenRect.setX(0); + screenRect.setY(0); + } + if( encoder ) { + int scaleFactor = dynamic_cast( getActiveRenderTarget() ).getScaleFactor(); + [encoder setScissorRect:(MTLScissorRect){ + static_cast(screenRect.getTopLeft().x * scaleFactor), + static_cast(screenRect.getTopLeft().y * scaleFactor), + static_cast(screenRect.getWidth() * scaleFactor), + static_cast(screenRect.getHeight() * scaleFactor) + }]; + } else { + clipRect = rect; + } } void MetalPainter::setMaterialData(const Material& material) { + + for (auto& dataBlock : material.getDataBlocks()) { if (dataBlock.getType() != MaterialDataBlockType::SharedExternal) { - static_cast(dataBlock.getConstantBuffer()).bindFragment(encoder, 0); + int bind_point = dataBlock.getBindPoint(); + + static_cast(getConstantBuffer(dataBlock)).bindVertex(encoder, bind_point); + // Tricky : The Vertex binding start at one, but the pixel at 0 + static_cast(getConstantBuffer(dataBlock)).bindFragment(encoder, bind_point - 1); } } } void MetalPainter::onUpdateProjection(Material& material, bool hashChanged) { if (hashChanged) { - material.uploadData(*this); + //:TODO: Check if needed : material.uploadData(*this); setMaterialData(material); } } @@ -158,11 +213,25 @@ case BlendMode::Alpha: setBlendFactor(colorAttachment, blendType.premultiplied ? MTLBlendFactorOne : MTLBlendFactorSourceAlpha, MTLBlendFactorOneMinusSourceAlpha); break; + case BlendMode::Add: + colorAttachment.sourceRGBBlendFactor = blendType.premultiplied ? MTLBlendFactorOne : MTLBlendFactorSourceAlpha; + colorAttachment.sourceAlphaBlendFactor = MTLBlendFactorOne; + colorAttachment.destinationRGBBlendFactor = MTLBlendFactorOne; + colorAttachment.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + break; case BlendMode::Multiply: setBlendFactor(colorAttachment, MTLBlendFactorDestinationColor, MTLBlendFactorOneMinusSourceAlpha); break; + case BlendMode::Max: + colorAttachment.sourceRGBBlendFactor = blendType.premultiplied ? MTLBlendFactorOne : MTLBlendFactorSourceAlpha; + colorAttachment.sourceAlphaBlendFactor = MTLBlendFactorOne; + colorAttachment.destinationRGBBlendFactor = MTLBlendFactorOne; + colorAttachment.destinationAlphaBlendFactor = MTLBlendFactorOne; + colorAttachment.rgbBlendOperation = MTLBlendOperationMax; + colorAttachment.alphaBlendOperation = MTLBlendOperationMax; + break; default: - setBlendFactor(colorAttachment, MTLBlendFactorSourceAlpha, MTLBlendFactorOne); + throw Exception("Not implemented yet", HalleyExceptions::Graphics); } } @@ -173,11 +242,74 @@ colorAttachment.destinationAlphaBlendFactor = dst; } -MTLRenderPassDescriptor* MetalPainter::renderPassDescriptorForTextureAndColour(id texture, Colour& colour) { +MTLRenderPassDescriptor* MetalPainter::renderPassDescriptorForTexture(id texture, id depthTexture) { MTLRenderPassDescriptor *pass = [MTLRenderPassDescriptor renderPassDescriptor]; - pass.colorAttachments[0].clearColor = MTLClearColorMake(colour.r, colour.g, colour.b, colour.a); - pass.colorAttachments[0].loadAction = MTLLoadActionClear; + pass.colorAttachments[0].loadAction = MTLLoadActionLoad; pass.colorAttachments[0].storeAction = MTLStoreActionStore; pass.colorAttachments[0].texture = texture; + + if (depthTexture != nil) { + pass.depthAttachment.loadAction = MTLLoadActionLoad; + pass.depthAttachment.storeAction = MTLStoreActionStore; + pass.depthAttachment.texture = depthTexture; + } + return pass; } + +void MetalPainter::ensureEncoder() +{ + if( encoder ) return; + encoder = [video.getCommandBuffer() renderCommandEncoderWithDescriptor:nextRenderPassDescriptor]; + nextRenderPassDescriptor = nil; + + if( viewPort ) { + auto & rect = *viewPort; + int scaleFactor = dynamic_cast( getActiveRenderTarget() ).getScaleFactor(); + [encoder setViewport:(MTLViewport){ + static_cast(rect.getTopLeft().x * scaleFactor), + static_cast(rect.getTopLeft().y * scaleFactor), + static_cast(rect.getWidth() * scaleFactor), + static_cast(rect.getHeight() * scaleFactor), + 0.0, 1.0 + }]; + viewPort.reset(); + } + + if( clipRect ) { + auto & rect = *clipRect; + int scaleFactor = dynamic_cast( getActiveRenderTarget() ).getScaleFactor(); + [encoder setScissorRect:(MTLScissorRect){ + static_cast(rect.getTopLeft().x * scaleFactor), + static_cast(rect.getTopLeft().y * scaleFactor), + static_cast(rect.getWidth() * scaleFactor), + static_cast(rect.getHeight() * scaleFactor) + }]; + clipRect.reset(); + } + + if( curDepthStencil ) { + curDepthStencil->bind( encoder ); + } +} + +MetalDepthStencil& MetalPainter::getDepthStencil(const MaterialDepthStencil& depthStencilDefinition) +{ + const auto iter = depthStencils.find(depthStencilDefinition); + if (iter == depthStencils.end()) { + auto depthStencil = std::make_unique(video, depthStencilDefinition); + const auto result = depthStencil.get(); + depthStencils[depthStencilDefinition] = std::move(depthStencil); + return *result; + } + + return *iter->second; +} + +void MetalPainter::setDepthStencil(const MaterialDepthStencil& depthStencilDefinition) +{ + if (!curDepthStencil || curDepthStencil->getDefinition() != depthStencilDefinition) { + curDepthStencil = &getDepthStencil(depthStencilDefinition); + curDepthStencil->bind( encoder ); + } +} diff --git a/src/plugins/metal/src/metal_render_target.h b/src/plugins/metal/src/metal_render_target.h index 452ae7c989..014b8dfa0b 100644 --- a/src/plugins/metal/src/metal_render_target.h +++ b/src/plugins/metal/src/metal_render_target.h @@ -3,6 +3,7 @@ #include #include #include "metal_video.h" +#include "metal_texture.h" namespace Halley { @@ -11,20 +12,26 @@ namespace Halley { public: virtual ~IMetalRenderTarget() {} virtual id getMetalTexture() = 0; + virtual id getMetalDepthTexture() = 0; + virtual int getScaleFactor() = 0; }; class MetalScreenRenderTarget : public ScreenRenderTarget, public IMetalRenderTarget { public: - explicit MetalScreenRenderTarget(MetalVideo& video, const Rect4i& viewPort); + explicit MetalScreenRenderTarget(MetalVideo& video, const Rect4i& viewPort, int scaleFactor); bool getViewportFlipVertical() const override; bool getProjectionFlipVertical() const override; void onBind(Painter& painter) override; void onUnbind(Painter& painter) override; id getMetalTexture() override; + id getMetalDepthTexture() override; + int getScaleFactor() override; private: MetalVideo& video; + int scaleFactor; + std::unique_ptr depthStencilBuffer; }; class MetalTextureRenderTarget : public TextureRenderTarget, public IMetalRenderTarget @@ -36,6 +43,8 @@ namespace Halley { void onUnbind(Painter& painter) override; id getMetalTexture() override; + id getMetalDepthTexture() override; + int getScaleFactor() override; }; } diff --git a/src/plugins/metal/src/metal_render_target.mm b/src/plugins/metal/src/metal_render_target.mm index e4c21f323c..991adca621 100644 --- a/src/plugins/metal/src/metal_render_target.mm +++ b/src/plugins/metal/src/metal_render_target.mm @@ -5,10 +5,16 @@ using namespace Halley; -MetalScreenRenderTarget::MetalScreenRenderTarget(MetalVideo& video, const Rect4i& viewPort) +MetalScreenRenderTarget::MetalScreenRenderTarget(MetalVideo& video, const Rect4i& viewPort, int scaleFactor) : ScreenRenderTarget(viewPort) , video(video) -{} + , scaleFactor(scaleFactor) +{ + depthStencilBuffer = std::make_unique(video, viewPort.getSize() ); + TextureDescriptor descriptor{ viewPort.getSize(), TextureFormat::Depth }; + descriptor.isDepthStencil = true; + depthStencilBuffer->load( std::move( descriptor )); +} bool MetalScreenRenderTarget::getViewportFlipVertical() const { return false; @@ -19,7 +25,7 @@ } void MetalScreenRenderTarget::onBind(Painter& painter) { - dynamic_cast(painter).startEncoding(getMetalTexture()); + dynamic_cast(painter).startEncoding( this ); } void MetalScreenRenderTarget::onUnbind(Painter& painter) { @@ -28,7 +34,19 @@ id MetalScreenRenderTarget::getMetalTexture() { return video.getSurface().texture; -}; +} + +id MetalScreenRenderTarget::getMetalDepthTexture() { + if (depthStencilBuffer) { + return depthStencilBuffer->metalTexture; + } + + return nil; +} + +int MetalScreenRenderTarget::getScaleFactor() { + return scaleFactor; +} bool MetalTextureRenderTarget::getViewportFlipVertical() const { return false; @@ -39,7 +57,7 @@ } void MetalTextureRenderTarget::onBind(Painter& painter) { - dynamic_cast(painter).startEncoding(getMetalTexture()); + dynamic_cast(painter).startEncoding(this); } void MetalTextureRenderTarget::onUnbind(Painter& painter) { @@ -48,4 +66,13 @@ id MetalTextureRenderTarget::getMetalTexture() { return std::static_pointer_cast(getTexture(0))->metalTexture; -}; +} + +id MetalTextureRenderTarget::getMetalDepthTexture() { + auto texture = std::static_pointer_cast(getDepthTexture()); + return texture ? texture->metalTexture : nil; +} + +int MetalTextureRenderTarget::getScaleFactor() { + return 1; +} diff --git a/src/plugins/metal/src/metal_texture.h b/src/plugins/metal/src/metal_texture.h index 9ac60ce765..806c110a46 100644 --- a/src/plugins/metal/src/metal_texture.h +++ b/src/plugins/metal/src/metal_texture.h @@ -1,6 +1,5 @@ #pragma once -#include "metal_render_target.h" #include #include @@ -12,6 +11,7 @@ namespace Halley { class MetalTexture : public Texture { friend class MetalTextureRenderTarget; + friend class MetalScreenRenderTarget; public: explicit MetalTexture(MetalVideo& video, Vector2i size); void doLoad(TextureDescriptor& descriptor) override; diff --git a/src/plugins/metal/src/metal_texture.mm b/src/plugins/metal/src/metal_texture.mm index bc2f651711..2405e14d4e 100644 --- a/src/plugins/metal/src/metal_texture.mm +++ b/src/plugins/metal/src/metal_texture.mm @@ -24,7 +24,7 @@ throw Exception("RGB textures are not supported", HalleyExceptions::VideoPlugin); break; case TextureFormat::RGBA: - pixelFormat = MTLPixelFormatRGBA8Unorm; + pixelFormat = descriptor.isRenderTarget ? MTLPixelFormatBGRA8Unorm : MTLPixelFormatRGBA8Unorm; bytesPerPixel = 4; break; case TextureFormat::Depth: @@ -35,33 +35,42 @@ throw Exception("Unknown texture format", HalleyExceptions::VideoPlugin); } - auto textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixelFormat + MTLTextureDescriptor * textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixelFormat width:descriptor.size.x height:descriptor.size.y mipmapped:descriptor.useMipMap ]; + + if( descriptor.isRenderTarget || descriptor.isDepthStencil ) { + textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; + textureDescriptor.storageMode = MTLStorageModePrivate; + } + metalTexture = [video.getDevice() newTextureWithDescriptor:textureDescriptor]; - NSUInteger bytesPerRow = bytesPerPixel * descriptor.size.x; - MTLRegion region = { - { 0, 0, 0 }, - {static_cast(descriptor.size.x), static_cast(descriptor.size.y), 1} - }; + if( !descriptor.isRenderTarget && !descriptor.isDepthStencil ) { + NSUInteger bytesPerRow = bytesPerPixel * descriptor.size.x; + MTLRegion region = { + { 0, 0, 0 }, + {static_cast(descriptor.size.x), static_cast(descriptor.size.y), 1} + }; + + Byte* imageBytes; - Byte* imageBytes; - if (descriptor.pixelData.empty()) { Vector blank; - blank.resize(size.x * size.y * TextureDescriptor::getBitsPerPixel(descriptor.format)); - imageBytes = blank.data(); - } else { - imageBytes = descriptor.pixelData.getBytes(); - } + if (descriptor.pixelData.empty()) { + blank.resize(size.x * size.y * TextureDescriptor::getBytesPerPixel(descriptor.format)); + imageBytes = blank.data(); + } else { + imageBytes = descriptor.pixelData.getBytes(); + } - [metalTexture replaceRegion:region - mipmapLevel:0 - withBytes:imageBytes - bytesPerRow:bytesPerRow - ]; + [metalTexture replaceRegion:region + mipmapLevel:0 + withBytes:imageBytes + bytesPerRow:bytesPerRow + ]; + } MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; samplerDescriptor.maxAnisotropy = 1; @@ -98,5 +107,7 @@ return MTLSamplerAddressModeMirrorRepeat; case TextureAddressMode::Repeat: return MTLSamplerAddressModeRepeat; + case TextureAddressMode::Border: + return MTLSamplerAddressModeClampToBorderColor; } } diff --git a/src/plugins/metal/src/metal_video.h b/src/plugins/metal/src/metal_video.h index 5a75175a75..ccb9466c8f 100644 --- a/src/plugins/metal/src/metal_video.h +++ b/src/plugins/metal/src/metal_video.h @@ -38,6 +38,8 @@ namespace Halley { id getDevice(); id getCommandBuffer(); + void addBufferToRelease( id buffer ); + private: std::shared_ptr window; std::unique_ptr loader; @@ -48,8 +50,10 @@ namespace Halley { id command_queue; NSAutoreleasePool *pool; id command_buffer; + std::vector> bufferToRelease; void initSwapChain(Window& window); + void purgeBuffers(); }; } diff --git a/src/plugins/metal/src/metal_video.mm b/src/plugins/metal/src/metal_video.mm index 21fbbb92cc..f98ac919a0 100644 --- a/src/plugins/metal/src/metal_video.mm +++ b/src/plugins/metal/src/metal_video.mm @@ -6,7 +6,14 @@ #include #include +#if __has_include() +#include +#include +#else #include +#include + +#endif #include using namespace Halley; @@ -31,6 +38,7 @@ void MetalVideo::startRender() { + purgeBuffers(); pool = [[NSAutoreleasePool alloc] init]; surface = [swap_chain nextDrawable]; command_buffer = [command_queue commandBuffer]; @@ -47,6 +55,10 @@ void MetalVideo::setWindow(WindowDefinition&& windowDescriptor) { + if( window ) + { + system.destroyWindow( window ); + } window = system.createWindow(windowDescriptor); initSwapChain(*window); } @@ -94,7 +106,21 @@ std::unique_ptr MetalVideo::createScreenRenderTarget() { - return std::make_unique(*this, Rect4i({}, getWindow().getWindowRect().getSize())); + auto & window = getWindow(); + if (window.getNativeHandleType() != "SDL") { + throw Exception("Only SDL2 windows are supported by Metal", HalleyExceptions::VideoPlugin); + } + + Vector2i size; + SDL_Window* sdl_window = static_cast(window.getNativeHandle()); + SDL_Metal_GetDrawableSize(sdl_window, &size.x, &size.y); + + Vector2i logical_size = window.getWindowRect().getSize(); + + int scale_factor = size.x / logical_size.x; + + // :TODO: Make sure size.y / logical_size.y is the same + return std::make_unique(*this, Rect4i({}, logical_size), scale_factor); } std::unique_ptr MetalVideo::createConstantBuffer() @@ -128,3 +154,18 @@ id MetalVideo::getCommandBuffer() { return command_buffer; } + +void MetalVideo::addBufferToRelease( id buffer ) +{ + bufferToRelease.push_back(buffer); +} + +void MetalVideo::purgeBuffers() +{ + for(auto & buffer : bufferToRelease) + { + [buffer setPurgeableState:MTLPurgeableStateEmpty]; + [buffer release]; + } + bufferToRelease.clear(); +} diff --git a/src/plugins/sdl/CMakeLists.txt b/src/plugins/sdl/CMakeLists.txt index 209e7824f0..7829e00ee3 100644 --- a/src/plugins/sdl/CMakeLists.txt +++ b/src/plugins/sdl/CMakeLists.txt @@ -36,3 +36,13 @@ assign_source_group(${HEADERS}) add_library (halley-sdl ${SOURCES} ${HEADERS}) target_link_libraries(halley-sdl halley-engine ${SDL2_LIBRARIES}) + +if(APPLE) + target_link_options( halley-sdl PUBLIC "-Wl,-weak_framework,GameController" "-Wl,-weak_framework,CoreHaptics") + + if(IOS OR TVOS) + target_link_options( halley-sdl PUBLIC "-Wl,-framework,Metal") + else() + target_link_options( halley-sdl PUBLIC "-Wl,-weak_framework,Metal") + endif() +endif() \ No newline at end of file diff --git a/src/tools/editor/CMakeLists.txt b/src/tools/editor/CMakeLists.txt index ce1a544cae..3e8def7bd9 100644 --- a/src/tools/editor/CMakeLists.txt +++ b/src/tools/editor/CMakeLists.txt @@ -20,7 +20,11 @@ endif() include_directories(${FREETYPE_INCLUDE_DIR} "../tools/include") halleyProjectCodegenV2(halley-editor "${editor_sources}" "${editor_resources}" ${CMAKE_CURRENT_SOURCE_DIR}/../../../bin) -add_dependencies(halley-editor halley-cmd halley-engine halley-opengl) +add_dependencies(halley-editor halley-cmd halley-engine) + +if (USE_OPENGL) + add_dependencies(halley-editor halley-opengl) +endif() if (USE_SDL2) add_dependencies(halley-editor halley-sdl) elseif (USE_SDL3) diff --git a/src/tools/editor/src/halley_editor.cpp b/src/tools/editor/src/halley_editor.cpp index 5c3004b22a..38b6ed901b 100644 --- a/src/tools/editor/src/halley_editor.cpp +++ b/src/tools/editor/src/halley_editor.cpp @@ -35,7 +35,7 @@ int HalleyEditor::initPlugins(IPluginRegistry ®istry) #ifdef _WIN32 initDX11Plugin(registry); -#elif USE_METAL +#elif defined WITH_METAL initMetalPlugin(registry); #else initOpenGLPlugin(registry); diff --git a/src/tools/tools/src/assets/importers/shader_importer_dxc.cpp b/src/tools/tools/src/assets/importers/shader_importer_dxc.cpp index cecc7053dd..231599700d 100644 --- a/src/tools/tools/src/assets/importers/shader_importer_dxc.cpp +++ b/src/tools/tools/src/assets/importers/shader_importer_dxc.cpp @@ -8,12 +8,9 @@ #include #include using namespace Microsoft::WRL; -#endif #include "shader_importer_dxc.inl" -using namespace Halley; - static DxcCreateInstanceProc getDxcCreateInstanceFunction(const char* dllName) { // NOTE: This leaks the DLL module, FreeLibrary() is never called. @@ -34,6 +31,9 @@ static DxcCreateInstanceProc getDxcCreateInstanceFunction(const char* dllName) return fn; } +#endif + +using namespace Halley; Bytes ShaderImporterDXC::compileDXIL(const String& name, ShaderType type, const Bytes& bytes, const String& language, const MaterialDefinition& material) { #ifdef _MSC_VER