Skip to content

Commit

Permalink
Get Metal (mostly) working with ShaderConductor
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelOtter authored and amzeratul committed Apr 18, 2020
1 parent f5cbf02 commit ed766bc
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ obj/
*.log
_ReSharper*/
*.sbr
.clangd/

# Build system
/build/
Expand Down
2 changes: 1 addition & 1 deletion cmake/HalleyProject.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ endif ()
if (APPLE)
set(USE_AVFOUNDATION 1)
set(USE_METAL 1)
set(USE_ASIO 0)
set(USE_ASIO 1)
endif ()

# Libs
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/metal/src/metal_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace Halley {
MetalBuffer& operator=(MetalBuffer&& other) = delete;

void setData(gsl::span<const gsl::byte> data);
void bind(id<MTLRenderCommandEncoder> encoder, int bindPoint);
void bindVertex(id<MTLRenderCommandEncoder> encoder, int bindPoint);
void bindFragment(id<MTLRenderCommandEncoder> encoder, int bindPoint);
id<MTLBuffer> getBuffer();

private:
Expand Down
9 changes: 6 additions & 3 deletions src/plugins/metal/src/metal_buffer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
[oldBuffer release];
}

void MetalBuffer::bind(id<MTLRenderCommandEncoder> encoder, int bindPoint) {
[encoder setVertexBuffer:buffer offset:0 atIndex:bindPoint+1];
[encoder setFragmentBuffer:buffer offset:0 atIndex:bindPoint+1];
void MetalBuffer::bindVertex(id<MTLRenderCommandEncoder> encoder, int bindPoint) {
[encoder setVertexBuffer:buffer offset:0 atIndex:bindPoint];
}

void MetalBuffer::bindFragment(id<MTLRenderCommandEncoder> encoder, int bindPoint) {
[encoder setFragmentBuffer:buffer offset:0 atIndex:bindPoint];
}
3 changes: 2 additions & 1 deletion src/plugins/metal/src/metal_material_constant_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ namespace Halley
explicit MetalMaterialConstantBuffer(MetalVideo& video);
~MetalMaterialConstantBuffer();
void update(const MaterialDataBlock& dataBlock) override;
void bind(id<MTLRenderCommandEncoder> encoder, int bindPoint);
void bindVertex(id<MTLRenderCommandEncoder> encoder, int bindPoint);
void bindFragment(id<MTLRenderCommandEncoder> encoder, int bindPoint);
private:
MetalBuffer buffer;
};
Expand Down
21 changes: 18 additions & 3 deletions src/plugins/metal/src/metal_material_constant_buffer.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "metal_material_constant_buffer.h"
#include "halley/utils/utils.h"
#include "metal_video.h"
#include <gsl/gsl>

using namespace Halley;

Expand All @@ -10,10 +12,23 @@
MetalMaterialConstantBuffer::~MetalMaterialConstantBuffer() {}

void MetalMaterialConstantBuffer::update(const MaterialDataBlock& dataBlock) {
buffer.setData(dataBlock.getData());
// We must pad up to a multiple of 16 (float4)
// TODO we ought to move this somewhere it won't be called so often.
auto data = dataBlock.getData();
const size_t padding = alignUp<char>(data.size_bytes(), 16);

auto padded = malloc(data.length_bytes() + padding);
memcpy(padded, data.data(), data.size_bytes());

buffer.setData(gsl::span{reinterpret_cast<gsl::byte *>(padded),
static_cast<long>(data.length_bytes() + padding)});
}


void MetalMaterialConstantBuffer::bind(id<MTLRenderCommandEncoder> encoder, int bindPoint) {
buffer.bind(encoder, bindPoint);
void MetalMaterialConstantBuffer::bindVertex(id<MTLRenderCommandEncoder> encoder, int bindPoint) {
buffer.bindVertex(encoder, bindPoint);
}

void MetalMaterialConstantBuffer::bindFragment(id<MTLRenderCommandEncoder> encoder, int bindPoint) {
buffer.bindFragment(encoder, bindPoint);
}
14 changes: 4 additions & 10 deletions src/plugins/metal/src/metal_painter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
#include "metal_texture.h"
#include "metal_video.h"

#include <halley/core/graphics/material/material_definition.h>

using namespace Halley;

MetalPainter::MetalPainter(MetalVideo& video, Resources& resources)
Expand All @@ -23,11 +21,7 @@
auto& pass = material.getDefinition().getPass(passNumber);
MetalShader& shader = static_cast<MetalShader&>(pass.getShader());

MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
pipelineStateDescriptor.vertexFunction = shader.getVertexFunc();
pipelineStateDescriptor.fragmentFunction = shader.getFragmentFunc();
pipelineStateDescriptor.label = [NSString stringWithUTF8String:material.getDefinition().getName().c_str()];
pipelineStateDescriptor.colorAttachments[0].pixelFormat = video.getSurface().texture.pixelFormat;
auto pipelineStateDescriptor = shader.setupMaterial(material);
setBlending(pass.getBlend(), pipelineStateDescriptor.colorAttachments[0]);

NSError* error = NULL;
Expand All @@ -43,7 +37,7 @@
[encoder setRenderPipelineState:pipelineState];

// Metal requires the global material to be bound for each material pass, as it has no 'global' state.
static_cast<MetalMaterialConstantBuffer&>(halleyGlobalMaterial->getDataBlocks().front().getConstantBuffer()).bind(encoder, 0);
static_cast<MetalMaterialConstantBuffer&>(halleyGlobalMaterial->getDataBlocks().front().getConstantBuffer()).bindVertex(encoder, 0);

// Bind textures
int texIndex = 0;
Expand Down Expand Up @@ -80,7 +74,7 @@
length:bytesSize
options:MTLResourceStorageModeShared
] autorelease];
[encoder setVertexBuffer:buffer offset:0 atIndex:0];
[encoder setVertexBuffer:buffer offset:0 atIndex:MaxMetalBufferIndex];

if (indexBuffer != nil) {
[indexBuffer autorelease];
Expand Down Expand Up @@ -124,7 +118,7 @@
void MetalPainter::setMaterialData(const Material& material) {
for (auto& dataBlock : material.getDataBlocks()) {
if (dataBlock.getType() != MaterialDataBlockType::SharedExternal) {
static_cast<MetalMaterialConstantBuffer&>(dataBlock.getConstantBuffer()).bind(encoder, dataBlock.getBindPoint());
static_cast<MetalMaterialConstantBuffer&>(dataBlock.getConstantBuffer()).bindFragment(encoder, 0);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/plugins/metal/src/metal_shader.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ namespace Halley {
~MetalShader();
int getUniformLocation(const String& name, ShaderType stage) override;
int getBlockLocation(const String& name, ShaderType stage) override;
id<MTLFunction> getVertexFunc();
id<MTLFunction> getFragmentFunc();
MTLRenderPipelineDescriptor* setupMaterial(const Material& material);

private:
MetalVideo& video;
MTLRenderPipelineDescriptor* descriptor = nullptr;
MTLVertexDescriptor* vertex_descriptor;
id<MTLFunction> vertex_func;
id<MTLFunction> fragment_func;

MTLVertexFormat getVertexFormatForShaderParameterType(ShaderParameterType type);
};
}
62 changes: 54 additions & 8 deletions src/plugins/metal/src/metal_shader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@
std::cout << "Metal shader compilation failed for material " << definition.name << std::endl;
throw Exception([[compileError localizedDescription] UTF8String], HalleyExceptions::VideoPlugin);
}
auto func_names = [lib functionNames];
if (func_names.count < 1) {
throw Exception("Shader for " + definition.name + " has no functions", HalleyExceptions::VideoPlugin);
}
switch (shaderDef.first) {
case ShaderType::Pixel:
fragment_func = [lib newFunctionWithName:@"pixel_func"];
fragment_func = [lib newFunctionWithName:func_names[0]];
if (fragment_func == nil) {
throw Exception("Shader for " + definition.name + " is missing a pixel function.", HalleyExceptions::VideoPlugin);
}
break;
case ShaderType::Vertex:
vertex_func = [lib newFunctionWithName:@"vertex_func"];
vertex_func = [lib newFunctionWithName:func_names[0]];
if (vertex_func == nil) {
throw Exception("Shader for " + definition.name + " is missing a vertex function.", HalleyExceptions::VideoPlugin);
}
Expand All @@ -42,6 +46,11 @@
MetalShader::~MetalShader() {
[vertex_func release];
[fragment_func release];

if (descriptor) {
[vertex_descriptor release];
[descriptor release];
}
}

int MetalShader::getUniformLocation(const String& name, ShaderType type)
Expand All @@ -54,12 +63,49 @@
return -1;
}

id<MTLFunction> MetalShader::getVertexFunc() {
return vertex_func;
}
MTLRenderPipelineDescriptor* MetalShader::setupMaterial(const Material& material) {
if (descriptor) {
return descriptor;
}

id<MTLFunction> MetalShader::getFragmentFunc() {
return fragment_func;
}
descriptor = [[MTLRenderPipelineDescriptor alloc] init];
descriptor.vertexFunction = vertex_func;
descriptor.fragmentFunction = fragment_func;
descriptor.label = [NSString stringWithUTF8String:material.getDefinition().getName().c_str()];
descriptor.colorAttachments[0].pixelFormat = video.getSurface().texture.pixelFormat;

vertex_descriptor = [[MTLVertexDescriptor alloc] init];
for (size_t i = 0; i < material.getDefinition().getAttributes().size(); ++i) {
const auto& a = material.getDefinition().getAttributes()[i];
vertex_descriptor.attributes[i].bufferIndex = MaxMetalBufferIndex;
vertex_descriptor.attributes[i].offset = a.offset;
vertex_descriptor.attributes[i].format = getVertexFormatForShaderParameterType(a.type);
}
vertex_descriptor.layouts[MaxMetalBufferIndex].stride = material.getDefinition().getVertexStride();
descriptor.vertexDescriptor = vertex_descriptor;

return descriptor;
}

MTLVertexFormat MetalShader::getVertexFormatForShaderParameterType(ShaderParameterType type) {
switch (type) {
case ShaderParameterType::Float:
return MTLVertexFormatFloat;
case ShaderParameterType::Float2:
return MTLVertexFormatFloat2;
case ShaderParameterType::Float3:
return MTLVertexFormatFloat3;
case ShaderParameterType::Float4:
return MTLVertexFormatFloat4;
case ShaderParameterType::Int:
return MTLVertexFormatInt;
case ShaderParameterType::Int2:
return MTLVertexFormatInt2;
case ShaderParameterType::Int3:
return MTLVertexFormatInt3;
case ShaderParameterType::Int4:
return MTLVertexFormatInt4;
default:
throw Exception("Unknown shader parameter type: " + toString(int(type)), HalleyExceptions::VideoPlugin);
}
}
4 changes: 4 additions & 0 deletions src/plugins/metal/src/metal_video.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
#include "metal_shader.h"
#include <halley/core/api/halley_api_internal.h>
#include <halley/core/graphics/painter.h>
#include <halley/core/graphics/material/material_definition.h>
#include <halley/core/graphics/render_target/render_target_texture.h>
#include <halley/core/graphics/render_target/render_target_screen.h>
#include <QuartzCore/CAMetalLayer.h>

namespace Halley {
// Place vertex buffer at end of table to avoid collisions.
static const size_t MaxMetalBufferIndex = 30;

class MetalVideo final : public VideoAPIInternal
{
Expand All @@ -28,6 +31,7 @@ namespace Halley {
void deInit() override;
std::unique_ptr<Painter> makePainter(Resources& resources) override;
String getShaderLanguage() override;
bool isColumnMajor() const override;

id<CAMetalDrawable> getSurface();
id<MTLCommandQueue> getCommandQueue();
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/metal/src/metal_video.mm
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
return "metal";
}

bool MetalVideo::isColumnMajor() const
{
return true;
}

std::unique_ptr<Painter> MetalVideo::makePainter(Resources& resources)
{
return std::make_unique<MetalPainter>(*this, resources);
Expand Down

0 comments on commit ed766bc

Please sign in to comment.