Add Metal implementation

Fixes #621
This commit is contained in:
Hajime Hoshi 2018-11-13 00:00:10 +09:00
parent d738639c48
commit ff62876552
27 changed files with 2882 additions and 60 deletions

View File

@ -2,6 +2,7 @@
Andrew Gerrand <nf@wh3rd.net>
Ben Echols <lologarithm@gmail.com>
Brett Chalupa <brett@brettchalupa.com>
Dmitri Shuralyov <dmitri@shuralyov.com>
Evan Leis <explodes@users.noreply.github.com>
Floppy <voidcrystal200@gmail.com>
gonutz <gonutz@users.noreply.github.com>

View File

@ -18,6 +18,8 @@ import (
"math"
"github.com/hajimehoshi/ebiten/internal/clock"
"github.com/hajimehoshi/ebiten/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/internal/hooks"
"github.com/hajimehoshi/ebiten/internal/shareable"
"github.com/hajimehoshi/ebiten/internal/ui"
@ -122,13 +124,21 @@ func (c *graphicsContext) Update(afterFrameUpdate func()) error {
}
op := &DrawImageOptions{}
switch graphicscommand.Driver().VDirection() {
case graphicsdriver.VDownward:
// c.screen is special: its Y axis is down to up,
// and the origin point is lower left.
op.GeoM.Scale(c.screenScale, -c.screenScale)
// Make the screen height an even number to fit the upper side of the screen (#662).
op.GeoM.Translate(0, float64((c.screenHeight+1)/2*2))
op.GeoM.Translate(c.offsetX, c.offsetY)
case graphicsdriver.VUpward:
op.GeoM.Scale(c.screenScale, c.screenScale)
default:
panic("not reached")
}
op.GeoM.Translate(c.offsetX, c.offsetY)
op.CompositeMode = CompositeModeCopy
// filterScreen works with >=1 scale, but does not well with <1 scale.

View File

@ -31,6 +31,8 @@ const (
CompositeModeXor
CompositeModeLighter
CompositeModeUnknown
CompositeModeMax = CompositeModeLighter
)
type Operation int

View File

@ -165,7 +165,7 @@ func (q *commandQueue) Flush() {
nc++
}
if 0 < ne {
driver().SetVertices(vs[:nv], es[:ne])
Driver().SetVertices(vs[:nv], es[:ne])
es = es[ne:]
vs = vs[nv:]
}
@ -185,7 +185,7 @@ func (q *commandQueue) Flush() {
}
if 0 < nc {
// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
driver().Flush()
Driver().Flush()
}
q.commands = q.commands[nc:]
}
@ -230,7 +230,7 @@ func (c *drawImageCommand) Exec(indexOffset int) error {
c.dst.image.SetAsDestination()
c.src.image.SetAsSource()
if err := driver().Draw(c.nindices, indexOffset, c.mode, c.color, c.filter); err != nil {
if err := Driver().Draw(c.nindices, indexOffset, c.mode, c.color, c.filter); err != nil {
return err
}
return nil
@ -394,7 +394,7 @@ func (c *newImageCommand) String() string {
// Exec executes a newImageCommand.
func (c *newImageCommand) Exec(indexOffset int) error {
i, err := driver().NewImage(c.width, c.height)
i, err := Driver().NewImage(c.width, c.height)
if err != nil {
return err
}
@ -434,7 +434,7 @@ func (c *newScreenFramebufferImageCommand) String() string {
// Exec executes a newScreenFramebufferImageCommand.
func (c *newScreenFramebufferImageCommand) Exec(indexOffset int) error {
var err error
c.result.image, err = driver().NewScreenFramebufferImage(c.width, c.height)
c.result.image, err = Driver().NewScreenFramebufferImage(c.width, c.height)
return err
}
@ -458,5 +458,5 @@ func (c *newScreenFramebufferImageCommand) CanMerge(dst, src *Image, color *affi
// ResetGraphicsDriverState resets or initializes the current graphics driver state.
func ResetGraphicsDriverState() error {
return driver().Reset()
return Driver().Reset()
}

View File

@ -0,0 +1,27 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin,!ios
// +build !js
package graphicscommand
import (
"github.com/hajimehoshi/ebiten/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal"
)
func Driver() graphicsdriver.GraphicsDriver {
return metal.Get()
}

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !darwin ios js
package graphicscommand
import (
@ -19,6 +21,6 @@ import (
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl"
)
func driver() graphicsdriver.GraphicsDriver {
func Driver() graphicsdriver.GraphicsDriver {
return opengl.Get()
}

View File

@ -20,12 +20,16 @@ import (
)
type GraphicsDriver interface {
SetWindow(window uintptr)
SetVertices(vertices []float32, indices []uint16)
Flush()
NewImage(width, height int) (Image, error)
NewScreenFramebufferImage(width, height int) (Image, error)
Reset() error
Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter) error
SetVsyncEnabled(enabled bool)
VDirection() VDirection
IsGL() bool
}
type Image interface {
@ -36,3 +40,10 @@ type Image interface {
SetAsSource()
ReplacePixels(pixels []byte, x, y, width, height int)
}
type VDirection int
const (
VUpward VDirection = iota
VDownward
)

View File

@ -0,0 +1,5 @@
These packages are copied from Dmitri Shuralyov's mtl packages and edited with Dmitri's permission:
* `github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca` (copied from `dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca`)
* `github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl` (copied from `dmitri.shuralyov.com/gpu/mtl`)
* `github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ns` (copied from `dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ns`)

View File

@ -0,0 +1,150 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
// Package ca provides access to Apple's Core Animation API (https://developer.apple.com/documentation/quartzcore).
//
// This package is in very early stages of development.
// It's a minimal implementation with scope limited to
// supporting the movingtriangle example.
package ca
import (
"errors"
"unsafe"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
)
// #cgo LDFLAGS: -framework QuartzCore -framework Foundation -framework CoreGraphics
//
// #include "ca.h"
import "C"
// Layer is an object that manages image-based content and
// allows you to perform animations on that content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/calayer.
type Layer interface {
// Layer returns the underlying CALayer * pointer.
Layer() unsafe.Pointer
}
// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
type MetalLayer struct {
metalLayer unsafe.Pointer
}
// MakeMetalLayer creates a new Core Animation Metal layer.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
func MakeMetalLayer() MetalLayer {
return MetalLayer{C.MakeMetalLayer()}
}
// Layer implements the Layer interface.
func (ml MetalLayer) Layer() unsafe.Pointer { return ml.metalLayer }
// PixelFormat returns the pixel format of textures for rendering layer content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
return mtl.PixelFormat(C.MetalLayer_PixelFormat(ml.metalLayer))
}
// SetDevice sets the Metal device responsible for the layer's drawable resources.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
func (ml MetalLayer) SetDevice(device mtl.Device) {
C.MetalLayer_SetDevice(ml.metalLayer, device.Device())
}
// SetPixelFormat controls the pixel format of textures for rendering layer content.
//
// The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB,
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
// SetPixelFormat panics for other values.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
e := C.MetalLayer_SetPixelFormat(ml.metalLayer, C.uint16_t(pf))
if e != nil {
panic(errors.New(C.GoString(e)))
}
}
// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
// managed by Core Animation.
//
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
func (ml MetalLayer) SetMaximumDrawableCount(count int) {
e := C.MetalLayer_SetMaximumDrawableCount(ml.metalLayer, C.uint_t(count))
if e != nil {
panic(errors.New(C.GoString(e)))
}
}
// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
// are synchronized with the display's refresh rate.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled.
func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
switch enabled {
case true:
C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, 1)
case false:
C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, 0)
}
}
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
func (ml MetalLayer) SetDrawableSize(width, height int) {
C.MetalLayer_SetDrawableSize(ml.metalLayer, C.double(width), C.double(height))
}
// NextDrawable returns a Metal drawable.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
md := C.MetalLayer_NextDrawable(ml.metalLayer)
if md == nil {
return MetalDrawable{}, errors.New("nextDrawable returned nil")
}
return MetalDrawable{md}, nil
}
// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
type MetalDrawable struct {
metalDrawable unsafe.Pointer
}
// Drawable implements the mtl.Drawable interface.
func (md MetalDrawable) Drawable() unsafe.Pointer { return md.metalDrawable }
// Texture returns a Metal texture object representing the drawable object's content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
func (md MetalDrawable) Texture() mtl.Texture {
return mtl.NewTexture(C.MetalDrawable_Texture(md.metalDrawable))
}

View File

@ -0,0 +1,33 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
typedef signed char BOOL;
typedef unsigned long uint_t;
typedef unsigned short uint16_t;
void *MakeMetalLayer();
uint16_t MetalLayer_PixelFormat(void *metalLayer);
void MetalLayer_SetDevice(void *metalLayer, void *device);
const char *MetalLayer_SetPixelFormat(void *metalLayer, uint16_t pixelFormat);
const char *MetalLayer_SetMaximumDrawableCount(void *metalLayer,
uint_t maximumDrawableCount);
void MetalLayer_SetDisplaySyncEnabled(void *metalLayer,
BOOL displaySyncEnabled);
void MetalLayer_SetDrawableSize(void *metalLayer, double width, double height);
void *MetalLayer_NextDrawable(void *metalLayer);
void *MetalDrawable_Texture(void *drawable);

View File

@ -0,0 +1,75 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
#include "ca.h"
#import <QuartzCore/QuartzCore.h>
void *MakeMetalLayer() {
CAMetalLayer *layer = [[CAMetalLayer alloc] init];
// TODO: Expose a function to set color space.
CGColorSpaceRef colorspace =
CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3);
layer.colorspace = colorspace;
CGColorSpaceRelease(colorspace);
return layer;
}
uint16_t MetalLayer_PixelFormat(void *metalLayer) {
return ((CAMetalLayer *)metalLayer).pixelFormat;
}
void MetalLayer_SetDevice(void *metalLayer, void *device) {
((CAMetalLayer *)metalLayer).device = (id<MTLDevice>)device;
}
const char *MetalLayer_SetPixelFormat(void *metalLayer, uint16_t pixelFormat) {
@try {
((CAMetalLayer *)metalLayer).pixelFormat = (MTLPixelFormat)pixelFormat;
} @catch (NSException *exception) {
return exception.reason.UTF8String;
}
return NULL;
}
const char *MetalLayer_SetMaximumDrawableCount(void *metalLayer,
uint_t maximumDrawableCount) {
if (@available(macOS 10.13.2, *)) {
@try {
((CAMetalLayer *)metalLayer).maximumDrawableCount =
(NSUInteger)maximumDrawableCount;
} @catch (NSException *exception) {
return exception.reason.UTF8String;
}
}
return NULL;
}
void MetalLayer_SetDisplaySyncEnabled(void *metalLayer,
BOOL displaySyncEnabled) {
((CAMetalLayer *)metalLayer).displaySyncEnabled = displaySyncEnabled;
}
void MetalLayer_SetDrawableSize(void *metalLayer, double width, double height) {
((CAMetalLayer *)metalLayer).drawableSize = (CGSize){width, height};
}
void *MetalLayer_NextDrawable(void *metalLayer) {
return [(CAMetalLayer *)metalLayer nextDrawable];
}
void *MetalDrawable_Texture(void *metalDrawable) {
return ((id<CAMetalDrawable>)metalDrawable).texture;
}

View File

@ -0,0 +1,613 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
package metal
import (
"fmt"
"strings"
"unsafe"
"github.com/hajimehoshi/ebiten/internal/affine"
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ns"
"github.com/hajimehoshi/ebiten/internal/mainthread"
)
const source = `#include <metal_stdlib>
#define FILTER_NEAREST ({{.FilterNearest}})
#define FILTER_LINEAR ({{.FilterLinear}})
#define FILTER_SCREEN ({{.FilterScreen}})
using namespace metal;
struct VertexIn {
packed_float2 position;
packed_float4 tex;
packed_float4 color;
};
struct VertexOut {
float4 position [[position]];
float2 tex;
float2 tex_min;
float2 tex_max;
float4 color;
};
vertex VertexOut VertexShader(
uint vid [[vertex_id]],
device VertexIn* vertices [[buffer(0)]],
constant float2& viewport_size [[buffer(1)]]
) {
float4x4 projectionMatrix = float4x4(
float4(2.0 / viewport_size.x, 0, 0, 0),
float4(0, -2.0 / viewport_size.y, 0, 0),
float4(0, 0, 1, 0),
float4(-1, 1, 0, 1)
);
VertexIn in = vertices[vid];
VertexOut out = {
.position = projectionMatrix * float4(in.position, 0, 1),
.tex = float2(in.tex[0], in.tex[1]),
.color = in.color,
};
if (in.tex[2] >= 0 && in.tex[3] >= 0) {
out.tex_min = float2(min(in.tex[0], in.tex[2]), min(in.tex[1], in.tex[3]));
out.tex_max = float2(max(in.tex[0], in.tex[2]), max(in.tex[1], in.tex[3]));
} else {
out.tex_min = float2(0);
out.tex_max = float2(1);
}
return out;
}
fragment float4 FragmentShader(VertexOut v [[stage_in]],
texture2d<float> texture [[texture(0)]],
constant float4x4& color_matrix_body [[buffer(2)]],
constant float4& color_matrix_translation [[buffer(3)]],
constant uint8_t& filter [[buffer(4)]],
constant float& scale [[buffer(5)]]) {
constexpr sampler texture_sampler(filter::nearest);
float2 source_size = 1;
while (source_size.x < texture.get_width()) {
source_size.x *= 2;
}
while (source_size.y < texture.get_height()) {
source_size.y *= 2;
}
const float2 texel_size = 1 / source_size;
float4 c;
switch (filter) {
case FILTER_NEAREST: {
c = texture.sample(texture_sampler, v.tex);
if (v.tex.x < v.tex_min.x ||
v.tex.y < v.tex_min.y ||
(v.tex_max.x - texel_size.x / 512.0) <= v.tex.x ||
(v.tex_max.y - texel_size.y / 512.0) <= v.tex.y) {
c = 0;
}
break;
}
case FILTER_LINEAR: {
float2 p0 = v.tex - texel_size / 2.0;
float2 p1 = v.tex + texel_size / 2.0;
float4 c0 = texture.sample(texture_sampler, p0);
float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y));
float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y));
float4 c3 = texture.sample(texture_sampler, p1);
if (p0.x < v.tex_min.x) {
c0 = 0;
c2 = 0;
}
if (p0.y < v.tex_min.y) {
c0 = 0;
c1 = 0;
}
if ((v.tex_max.x - texel_size.x / 512.0) <= p1.x) {
c1 = 0;
c3 = 0;
}
if ((v.tex_max.y - texel_size.y / 512.0) <= p1.y) {
c2 = 0;
c3 = 0;
}
float2 rate = fract(p0 * source_size);
c = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
break;
}
case FILTER_SCREEN: {
float2 p0 = v.tex - texel_size / 2.0 / scale;
float2 p1 = v.tex + texel_size / 2.0 / scale;
float4 c0 = texture.sample(texture_sampler, p0);
float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y));
float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y));
float4 c3 = texture.sample(texture_sampler, p1);
float2 rate_center = float2(1.0, 1.0) - texel_size / 2.0 / scale;
float2 rate = clamp(((fract(p0 * source_size) - rate_center) * scale) + rate_center, 0.0, 1.0);
c = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
break;
}
default:
// Not reached.
discard_fragment();
return float4(0);
}
if (0 < c.a) {
c.rgb /= c.a;
}
c = (color_matrix_body * c) + color_matrix_translation;
c *= v.color;
c = clamp(c, 0, 1);
c.rgb *= c.a;
return c;
}
`
type Driver struct {
window uintptr
device mtl.Device
ml ca.MetalLayer
screenRPS mtl.RenderPipelineState
rpss map[graphics.CompositeMode]mtl.RenderPipelineState
cq mtl.CommandQueue
cb mtl.CommandBuffer
screenDrawable ca.MetalDrawable
vb mtl.Buffer
ib mtl.Buffer
src *Image
dst *Image
maxImageSize int
}
var theDriver Driver
func Get() *Driver {
return &theDriver
}
func (d *Driver) SetWindow(window uintptr) {
// Note that [NSApp mainWindow] returns nil when the window is borderless.
// Then the window is needed to be given.
d.window = window
}
func (d *Driver) SetVertices(vertices []float32, indices []uint16) {
mainthread.Run(func() error {
if d.vb != (mtl.Buffer{}) {
d.vb.Release()
}
if d.ib != (mtl.Buffer{}) {
d.ib.Release()
}
d.vb = d.device.MakeBuffer(unsafe.Pointer(&vertices[0]), unsafe.Sizeof(vertices[0])*uintptr(len(vertices)), mtl.ResourceStorageModeManaged)
d.ib = d.device.MakeBuffer(unsafe.Pointer(&indices[0]), unsafe.Sizeof(indices[0])*uintptr(len(indices)), mtl.ResourceStorageModeManaged)
return nil
})
}
func (d *Driver) Flush() {
mainthread.Run(func() error {
if d.cb == (mtl.CommandBuffer{}) {
return nil
}
if d.screenDrawable != (ca.MetalDrawable{}) {
d.cb.PresentDrawable(d.screenDrawable)
}
d.cb.Commit()
d.cb.WaitUntilCompleted()
d.cb = mtl.CommandBuffer{}
d.screenDrawable = ca.MetalDrawable{}
return nil
})
}
func (d *Driver) checkSize(width, height int) {
m := 0
mainthread.Run(func() error {
if d.maxImageSize == 0 {
d.maxImageSize = 4096
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
switch {
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily5_v1):
d.maxImageSize = 16384
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily4_v1):
d.maxImageSize = 16384
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily3_v1):
d.maxImageSize = 16384
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v2):
d.maxImageSize = 8192
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v1):
d.maxImageSize = 4096
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v2):
d.maxImageSize = 8192
case d.device.SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v1):
d.maxImageSize = 4096
case d.device.SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily2_v1):
d.maxImageSize = 16384
case d.device.SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily1_v1):
d.maxImageSize = 8192
case d.device.SupportsFeatureSet(mtl.FeatureSet_macOS_GPUFamily1_v1):
d.maxImageSize = 16384
default:
panic("metal: there is no supported feature set")
}
}
m = d.maxImageSize
return nil
})
if width < 1 {
panic(fmt.Sprintf("metal: width (%d) must be equal or more than 1", width))
}
if height < 1 {
panic(fmt.Sprintf("metal: height (%d) must be equal or more than 1", height))
}
if width > m {
panic(fmt.Sprintf("metal: width (%d) must be less than or equal to %d", width, m))
}
if height > m {
panic(fmt.Sprintf("metal: height (%d) must be less than or equal to %d", height, m))
}
}
func (d *Driver) NewImage(width, height int) (graphicsdriver.Image, error) {
d.checkSize(width, height)
td := mtl.TextureDescriptor{
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: graphics.NextPowerOf2Int(width),
Height: graphics.NextPowerOf2Int(height),
StorageMode: mtl.StorageModeManaged,
// MTLTextureUsageRenderTarget might cause a problematic render result. Not sure the reason.
// Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget
Usage: mtl.TextureUsageShaderRead,
}
var t mtl.Texture
mainthread.Run(func() error {
t = d.device.MakeTexture(td)
return nil
})
return &Image{
driver: d,
width: width,
height: height,
texture: t,
}, nil
}
func (d *Driver) NewScreenFramebufferImage(width, height int) (graphicsdriver.Image, error) {
mainthread.Run(func() error {
d.ml.SetDrawableSize(width, height)
return nil
})
return &Image{
driver: d,
width: width,
height: height,
screen: true,
}, nil
}
func (d *Driver) Reset() error {
if err := mainthread.Run(func() error {
if d.cq != (mtl.CommandQueue{}) {
d.cq.Release()
d.cq = mtl.CommandQueue{}
}
// TODO: Release existing rpss
if d.rpss == nil {
d.rpss = map[graphics.CompositeMode]mtl.RenderPipelineState{}
}
var err error
d.device, err = mtl.CreateSystemDefaultDevice()
if err != nil {
return err
}
d.ml = ca.MakeMetalLayer()
d.ml.SetDevice(d.device)
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat
//
// The pixel format for a Metal layer must be MTLPixelFormatBGRA8Unorm,
// MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or
// MTLPixelFormatBGRA10_XR_sRGB.
d.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
d.ml.SetMaximumDrawableCount(3)
d.ml.SetDisplaySyncEnabled(true)
replaces := map[string]string{
"{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest),
"{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear),
"{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen),
}
src := source
for k, v := range replaces {
src = strings.Replace(src, k, v, -1)
}
lib, err := d.device.MakeLibrary(src, mtl.CompileOptions{})
if err != nil {
return err
}
vs, err := lib.MakeFunction("VertexShader")
if err != nil {
return err
}
fs, err := lib.MakeFunction("FragmentShader")
if err != nil {
return err
}
rpld := mtl.RenderPipelineDescriptor{
VertexFunction: vs,
FragmentFunction: fs,
}
rpld.ColorAttachments[0].PixelFormat = d.ml.PixelFormat()
rpld.ColorAttachments[0].BlendingEnabled = true
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = mtl.BlendFactorZero
rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero
rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne
rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne
rps, err := d.device.MakeRenderPipelineState(rpld)
if err != nil {
return err
}
d.screenRPS = rps
conv := func(c graphics.Operation) mtl.BlendFactor {
switch c {
case graphics.Zero:
return mtl.BlendFactorZero
case graphics.One:
return mtl.BlendFactorOne
case graphics.SrcAlpha:
return mtl.BlendFactorSourceAlpha
case graphics.DstAlpha:
return mtl.BlendFactorDestinationAlpha
case graphics.OneMinusSrcAlpha:
return mtl.BlendFactorOneMinusSourceAlpha
case graphics.OneMinusDstAlpha:
return mtl.BlendFactorOneMinusDestinationAlpha
default:
panic("not reached")
}
}
for c := graphics.CompositeModeSourceOver; c <= graphics.CompositeModeMax; c++ {
rpld := mtl.RenderPipelineDescriptor{
VertexFunction: vs,
FragmentFunction: fs,
}
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rpld.ColorAttachments[0].BlendingEnabled = true
src, dst := c.Operations()
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = conv(dst)
rpld.ColorAttachments[0].DestinationRGBBlendFactor = conv(dst)
rpld.ColorAttachments[0].SourceAlphaBlendFactor = conv(src)
rpld.ColorAttachments[0].SourceRGBBlendFactor = conv(src)
rps, err := d.device.MakeRenderPipelineState(rpld)
if err != nil {
return err
}
d.rpss[c] = rps
}
d.cq = d.device.MakeCommandQueue()
return nil
}); err != nil {
return err
}
return nil
}
func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter) error {
if err := mainthread.Run(func() error {
// NSView can be changed anytime (probably). Set this everyframe.
cocoaWindow := ns.NewWindow(unsafe.Pointer(d.window))
cocoaWindow.ContentView().SetLayer(d.ml)
cocoaWindow.ContentView().SetWantsLayer(true)
rpd := mtl.RenderPassDescriptor{}
if d.dst.screen {
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionDontCare
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
} else {
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionLoad
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
}
var t mtl.Texture
if d.dst.screen {
if d.screenDrawable == (ca.MetalDrawable{}) {
drawable, err := d.ml.NextDrawable()
if err != nil {
return err
}
d.screenDrawable = drawable
}
t = d.screenDrawable.Texture()
} else {
d.screenDrawable = ca.MetalDrawable{}
t = d.dst.texture
}
rpd.ColorAttachments[0].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
w, h := d.dst.viewportSize()
if d.cb == (mtl.CommandBuffer{}) {
d.cb = d.cq.MakeCommandBuffer()
}
rce := d.cb.MakeRenderCommandEncoder(rpd)
if d.dst.screen {
rce.SetRenderPipelineState(d.screenRPS)
} else {
rce.SetRenderPipelineState(d.rpss[mode])
}
rce.SetViewport(mtl.Viewport{0, 0, float64(w), float64(h), -1, 1})
rce.SetVertexBuffer(d.vb, 0, 0)
viewportSize := [...]float32{float32(w), float32(h)}
rce.SetVertexBytes(unsafe.Pointer(&viewportSize[0]), unsafe.Sizeof(viewportSize), 1)
esBody, esTranslate := colorM.UnsafeElements()
rce.SetFragmentBytes(unsafe.Pointer(&esBody[0]), unsafe.Sizeof(esBody[0])*uintptr(len(esBody)), 2)
rce.SetFragmentBytes(unsafe.Pointer(&esTranslate[0]), unsafe.Sizeof(esTranslate[0])*uintptr(len(esTranslate)), 3)
f := uint8(filter)
rce.SetFragmentBytes(unsafe.Pointer(&f), 1, 4)
scale := float32(d.dst.width) / float32(d.src.width)
rce.SetFragmentBytes(unsafe.Pointer(&scale), unsafe.Sizeof(scale), 5)
if d.src != nil {
rce.SetFragmentTexture(d.src.texture, 0)
} else {
rce.SetFragmentTexture(mtl.Texture{}, 0)
}
rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, d.ib, indexOffset*2)
rce.EndEncoding()
return nil
}); err != nil {
return err
}
return nil
}
func (d *Driver) ResetSource() {
d.src = nil
}
func (d *Driver) SetVsyncEnabled(enabled bool) {
d.ml.SetDisplaySyncEnabled(enabled)
}
func (d *Driver) VDirection() graphicsdriver.VDirection {
return graphicsdriver.VUpward
}
func (d *Driver) IsGL() bool {
return false
}
type Image struct {
driver *Driver
width int
height int
screen bool
texture mtl.Texture
}
func (i *Image) viewportSize() (int, int) {
if i.screen {
return i.width, i.height
}
return graphics.NextPowerOf2Int(i.width), graphics.NextPowerOf2Int(i.height)
}
func (i *Image) Dispose() {
i.texture.Release()
}
func (i *Image) IsInvalidated() bool {
// TODO: Does Metal cause context lost?
// https://developer.apple.com/documentation/metal/mtlresource/1515898-setpurgeablestate
// https://developer.apple.com/documentation/metal/mtldevicenotificationhandler
return false
}
func (i *Image) syncTexture() {
mainthread.Run(func() error {
if i.driver.cb != (mtl.CommandBuffer{}) {
panic("not reached")
}
cb := i.driver.cq.MakeCommandBuffer()
bce := cb.MakeBlitCommandEncoder()
bce.SynchronizeTexture(i.texture, 0, 0)
bce.EndEncoding()
cb.Commit()
cb.WaitUntilCompleted()
return nil
})
}
func (i *Image) Pixels() ([]byte, error) {
i.driver.Flush()
i.syncTexture()
b := make([]byte, 4*i.width*i.height)
mainthread.Run(func() error {
i.texture.GetBytes(&b[0], uintptr(4*i.width), mtl.Region{
Size: mtl.Size{i.width, i.height, 1},
}, 0)
return nil
})
return b, nil
}
func (i *Image) SetAsDestination() {
i.driver.dst = i
}
func (i *Image) SetAsSource() {
i.driver.src = i
}
func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
i.driver.Flush()
mainthread.Run(func() error {
i.texture.ReplaceRegion(mtl.Region{
Origin: mtl.Origin{x, y, 0},
Size: mtl.Size{width, height, 1},
}, 0, unsafe.Pointer(&pixels[0]), 4*width)
return nil
})
}

View File

@ -0,0 +1,231 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
package mtl_test
import (
"encoding/json"
"fmt"
"image"
"image/color"
"log"
"os"
"unsafe"
"golang.org/x/image/math/f32"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
)
func Example_listDevices() {
allDevices := mtl.CopyAllDevices()
printJSON("all Metal devices in the system = ", allDevices)
device, err := mtl.CreateSystemDefaultDevice()
if err != nil {
log.Fatalln(err)
}
printJSON("preferred system default Metal device = ", device)
fmt.Println("device supports the macOS GPU family 1, version 1 feature set:", device.SupportsFeatureSet(mtl.MacOSGPUFamily1V1))
fmt.Println("device supports the macOS GPU family 1, version 2 feature set:", device.SupportsFeatureSet(mtl.MacOSGPUFamily1V2))
fmt.Println("device supports the macOS read-write texture, tier 2 feature set:", device.SupportsFeatureSet(mtl.MacOSReadWriteTextureTier2))
fmt.Println("device supports the macOS GPU family 1, version 3 feature set:", device.SupportsFeatureSet(mtl.MacOSGPUFamily1V3))
fmt.Println("device supports the macOS GPU family 1, version 4 feature set:", device.SupportsFeatureSet(mtl.MacOSGPUFamily1V4))
fmt.Println("device supports the macOS GPU family 2, version 1 feature set:", device.SupportsFeatureSet(mtl.MacOSGPUFamily2V1))
// Sample output:
// all Metal devices in the system = [
// {
// "Headless": false,
// "LowPower": true,
// "Removable": false,
// "RegistryID": 4294968287,
// "Name": "Intel Iris Pro Graphics"
// },
// {
// "Headless": false,
// "LowPower": false,
// "Removable": false,
// "RegistryID": 4294968322,
// "Name": "AMD Radeon R9 M370X"
// }
// ]
// preferred system default Metal device = {
// "Headless": false,
// "LowPower": false,
// "Removable": false,
// "RegistryID": 4294968322,
// "Name": "AMD Radeon R9 M370X"
// }
// device supports the macOS GPU family 1, version 1 feature set: true
// device supports the macOS GPU family 1, version 2 feature set: true
// device supports the macOS read-write texture, tier 2 feature set: true
// device supports the macOS GPU family 1, version 3 feature set: true
// device supports the macOS GPU family 1, version 4 feature set: true
// device supports the macOS GPU family 2, version 1 feature set: true
}
// printJSON prints label, then v as JSON encoded with indent to stdout. It panics on any error.
// It's meant to be used by examples to print the output.
func printJSON(label string, v interface{}) {
fmt.Print(label)
w := json.NewEncoder(os.Stdout)
w.SetIndent("", "\t")
err := w.Encode(v)
if err != nil {
panic(err)
}
}
func Example_renderTriangle() {
device, err := mtl.CreateSystemDefaultDevice()
if err != nil {
log.Fatalln(err)
}
// Create a render pipeline state.
const source = `#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
float4 color;
};
vertex Vertex VertexShader(
uint vertexID [[vertex_id]],
device Vertex * vertices [[buffer(0)]]
) {
return vertices[vertexID];
}
fragment float4 FragmentShader(Vertex in [[stage_in]]) {
return in.color;
}
`
lib, err := device.MakeLibrary(source, mtl.CompileOptions{})
if err != nil {
log.Fatalln(err)
}
vs, err := lib.MakeFunction("VertexShader")
if err != nil {
log.Fatalln(err)
}
fs, err := lib.MakeFunction("FragmentShader")
if err != nil {
log.Fatalln(err)
}
var rpld mtl.RenderPipelineDescriptor
rpld.VertexFunction = vs
rpld.FragmentFunction = fs
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rps, err := device.MakeRenderPipelineState(rpld)
if err != nil {
log.Fatalln(err)
}
// Create a vertex buffer.
type Vertex struct {
Position f32.Vec4
Color f32.Vec4
}
vertexData := [...]Vertex{
{f32.Vec4{+0.00, +0.75, 0, 1}, f32.Vec4{1, 1, 1, 1}},
{f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{1, 1, 1, 1}},
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 0, 1}},
}
vertexBuffer := device.MakeBuffer(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
// Create an output texture to render into.
td := mtl.TextureDescriptor{
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: 80,
Height: 20,
StorageMode: mtl.StorageModeManaged,
}
texture := device.MakeTexture(td)
cq := device.MakeCommandQueue()
cb := cq.MakeCommandBuffer()
// Encode all render commands.
var rpd mtl.RenderPassDescriptor
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0, Green: 0, Blue: 0, Alpha: 1}
rpd.ColorAttachments[0].Texture = texture
rce := cb.MakeRenderCommandEncoder(rpd)
rce.SetRenderPipelineState(rps)
rce.SetVertexBuffer(vertexBuffer, 0, 0)
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
rce.EndEncoding()
// Encode all blit commands.
bce := cb.MakeBlitCommandEncoder()
bce.Synchronize(texture)
bce.EndEncoding()
cb.Commit()
cb.WaitUntilCompleted()
// Read pixels from output texture into an image.
img := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
bytesPerRow := 4 * texture.Width
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
texture.GetBytes(&img.Pix[0], uintptr(bytesPerRow), region, 0)
// Output image to stdout as grayscale ASCII art.
levels := []struct {
MinY uint8
Shade string
}{{220, " "}, {170, "░"}, {85, "▒"}, {35, "▓"}, {0, "█"}}
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
c := color.GrayModel.Convert(img.At(x, y)).(color.Gray)
for _, l := range levels {
if c.Y >= l.MinY {
fmt.Print(l.Shade)
break
}
}
}
fmt.Println()
}
// Output:
// ████████████████████████████████████████████████████████████████████████████████
// ████████████████████████████████████████████████████████████████████████████████
// ████████████████████████████████████████████████████████████████████████████████
// ██████████████████████████████████████ ██████████████████████████████████████
// ████████████████████████████████████ ████████████████████████████████████
// ██████████████████████████████████ ░░░░██████████████████████████████████
// ████████████████████████████████ ░░░░░░░░████████████████████████████████
// ██████████████████████████████ ░░░░░░░░░░░░██████████████████████████████
// ████████████████████████████ ░░░░░░░░░░░░▒▒▒▒████████████████████████████
// ██████████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒██████████████████████████
// ████████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒████████████████████████
// ██████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████████████
// ████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████████████████
// ██████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓██████████████████
// ████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓████████████████
// ██████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓██████████████
// ████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓████████████████
// ████████████████████████████████████████████████████████████████████████████████
// ████████████████████████████████████████████████████████████████████████████████
// ████████████████████████████████████████████████████████████████████████████████
}

View File

@ -0,0 +1,867 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
// Package mtl provides access to Apple's Metal API (https://developer.apple.com/documentation/metal).
//
// Package mtl requires macOS version 10.13 or newer.
//
// This package is in very early stages of development.
// The API will change when opportunities for improvement are discovered; it is not yet frozen.
// Less than 20% of the Metal API surface is implemented.
// Current functionality is sufficient to render very basic geometry.
package mtl
import (
"errors"
"fmt"
"unsafe"
)
// #cgo LDFLAGS: -framework Metal -framework Foundation
//
// #include <stdlib.h>
// #include "mtl.h"
// struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) {
// return Device_MakeLibrary(device, _GoStringPtr(source), _GoStringLen(source));
// }
import "C"
// FeatureSet defines a specific platform, hardware, and software configuration.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
type FeatureSet uint16
// The device feature sets that define specific platform, hardware, and software configurations.
const (
MacOSGPUFamily1V1 FeatureSet = 10000 // The GPU family 1, version 1 feature set for macOS.
MacOSGPUFamily1V2 FeatureSet = 10001 // The GPU family 1, version 2 feature set for macOS.
MacOSReadWriteTextureTier2 FeatureSet = 10002 // The read-write texture, tier 2 feature set for macOS.
MacOSGPUFamily1V3 FeatureSet = 10003 // The GPU family 1, version 3 feature set for macOS.
MacOSGPUFamily1V4 FeatureSet = 10004 // The GPU family 1, version 4 feature set for macOS.
MacOSGPUFamily2V1 FeatureSet = 10005 // The GPU family 2, version 1 feature set for macOS.
)
const (
FeatureSet_iOS_GPUFamily1_v1 FeatureSet = 0
FeatureSet_iOS_GPUFamily1_v2 FeatureSet = 2
FeatureSet_iOS_GPUFamily1_v3 FeatureSet = 5
FeatureSet_iOS_GPUFamily1_v4 FeatureSet = 8
FeatureSet_iOS_GPUFamily1_v5 FeatureSet = 12
FeatureSet_iOS_GPUFamily2_v1 FeatureSet = 1
FeatureSet_iOS_GPUFamily2_v2 FeatureSet = 3
FeatureSet_iOS_GPUFamily2_v3 FeatureSet = 6
FeatureSet_iOS_GPUFamily2_v4 FeatureSet = 9
FeatureSet_iOS_GPUFamily2_v5 FeatureSet = 13
FeatureSet_iOS_GPUFamily3_v1 FeatureSet = 4
FeatureSet_iOS_GPUFamily3_v2 FeatureSet = 7
FeatureSet_iOS_GPUFamily3_v3 FeatureSet = 10
FeatureSet_iOS_GPUFamily3_v4 FeatureSet = 14
FeatureSet_iOS_GPUFamily4_v1 FeatureSet = 11
FeatureSet_iOS_GPUFamily4_v2 FeatureSet = 15
FeatureSet_iOS_GPUFamily5_v1 FeatureSet = 16
FeatureSet_tvOS_GPUFamily1_v1 FeatureSet = 30000
FeatureSet_tvOS_GPUFamily1_v2 FeatureSet = 30001
FeatureSet_tvOS_GPUFamily1_v3 FeatureSet = 30002
FeatureSet_tvOS_GPUFamily1_v4 FeatureSet = 30004
FeatureSet_tvOS_GPUFamily2_v1 FeatureSet = 30003
FeatureSet_tvOS_GPUFamily2_v2 FeatureSet = 30005
FeatureSet_macOS_GPUFamily1_v1 FeatureSet = 10000
FeatureSet_macOS_GPUFamily1_v2 FeatureSet = 10001
FeatureSet_macOS_GPUFamily1_v3 FeatureSet = 10003
FeatureSet_macOS_GPUFamily1_v4 FeatureSet = 10004
FeatureSet_macOS_GPUFamily2_v1 FeatureSet = 10005
FeatureSet_macOS_ReadWriteTextureTier2 FeatureSet = 10002
)
// PixelFormat defines data formats that describe the organization
// and characteristics of individual pixels in a texture.
//
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat.
type PixelFormat uint16
// The data formats that describe the organization and characteristics
// of individual pixels in a texture.
const (
PixelFormatRGBA8UNorm PixelFormat = 70 // Ordinary format with four 8-bit normalized unsigned integer components in RGBA order.
PixelFormatRGBA8UNormSRGB PixelFormat = 71 // Ordinary format with four 8-bit normalized unsigned integer components in RGBA order with conversion between sRGB and linear space.
PixelFormatBGRA8UNorm PixelFormat = 80 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order.
PixelFormatBGRA8UNormSRGB PixelFormat = 81 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order with conversion between sRGB and linear space.
)
// PrimitiveType defines geometric primitive types for drawing commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype.
type PrimitiveType uint8
// Geometric primitive types for drawing commands.
const (
PrimitiveTypePoint PrimitiveType = 0
PrimitiveTypeLine PrimitiveType = 1
PrimitiveTypeLineStrip PrimitiveType = 2
PrimitiveTypeTriangle PrimitiveType = 3
PrimitiveTypeTriangleStrip PrimitiveType = 4
)
// LoadAction defines actions performed at the start of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction.
type LoadAction uint8
// Actions performed at the start of a rendering pass for a render command encoder.
const (
LoadActionDontCare LoadAction = 0
LoadActionLoad LoadAction = 1
LoadActionClear LoadAction = 2
)
// StoreAction defines actions performed at the end of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction.
type StoreAction uint8
// Actions performed at the end of a rendering pass for a render command encoder.
const (
StoreActionDontCare StoreAction = 0
StoreActionStore StoreAction = 1
StoreActionMultisampleResolve StoreAction = 2
StoreActionStoreAndMultisampleResolve StoreAction = 3
StoreActionUnknown StoreAction = 4
StoreActionCustomSampleDepthStore StoreAction = 5
)
// StorageMode defines defines the memory location and access permissions of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode.
type StorageMode uint8
const (
// StorageModeShared indicates that the resource is stored in system memory
// accessible to both the CPU and the GPU.
StorageModeShared StorageMode = 0
// StorageModeManaged indicates that the resource exists as a synchronized
// memory pair with one copy stored in system memory accessible to the CPU
// and another copy stored in video memory accessible to the GPU.
StorageModeManaged StorageMode = 1
// StorageModePrivate indicates that the resource is stored in memory
// only accessible to the GPU. In iOS and tvOS, the resource is stored in
// system memory. In macOS, the resource is stored in video memory.
StorageModePrivate StorageMode = 2
// StorageModeMemoryless indicates that the resource is stored in on-tile memory,
// without CPU or GPU memory backing. The contents of the on-tile memory are undefined
// and do not persist; the only way to populate the resource is to render into it.
// Memoryless resources are limited to temporary render targets (i.e., Textures configured
// with a TextureDescriptor and used with a RenderPassAttachmentDescriptor).
StorageModeMemoryless StorageMode = 3
)
// ResourceOptions defines optional arguments used to create
// and influence behavior of buffer and texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions.
type ResourceOptions uint16
const (
// ResourceCPUCacheModeDefaultCache is the default CPU cache mode for the resource.
// Guarantees that read and write operations are executed in the expected order.
ResourceCPUCacheModeDefaultCache ResourceOptions = ResourceOptions(CPUCacheModeDefaultCache) << resourceCPUCacheModeShift
// ResourceCPUCacheModeWriteCombined is a write-combined CPU cache mode for the resource.
// Optimized for resources that the CPU will write into, but never read.
ResourceCPUCacheModeWriteCombined ResourceOptions = ResourceOptions(CPUCacheModeWriteCombined) << resourceCPUCacheModeShift
// ResourceStorageModeShared indicates that the resource is stored in system memory
// accessible to both the CPU and the GPU.
ResourceStorageModeShared ResourceOptions = ResourceOptions(StorageModeShared) << resourceStorageModeShift
// ResourceStorageModeManaged indicates that the resource exists as a synchronized
// memory pair with one copy stored in system memory accessible to the CPU
// and another copy stored in video memory accessible to the GPU.
ResourceStorageModeManaged ResourceOptions = ResourceOptions(StorageModeManaged) << resourceStorageModeShift
// ResourceStorageModePrivate indicates that the resource is stored in memory
// only accessible to the GPU. In iOS and tvOS, the resource is stored
// in system memory. In macOS, the resource is stored in video memory.
ResourceStorageModePrivate ResourceOptions = ResourceOptions(StorageModePrivate) << resourceStorageModeShift
// ResourceStorageModeMemoryless indicates that the resource is stored in on-tile memory,
// without CPU or GPU memory backing. The contents of the on-tile memory are undefined
// and do not persist; the only way to populate the resource is to render into it.
// Memoryless resources are limited to temporary render targets (i.e., Textures configured
// with a TextureDescriptor and used with a RenderPassAttachmentDescriptor).
ResourceStorageModeMemoryless ResourceOptions = ResourceOptions(StorageModeMemoryless) << resourceStorageModeShift
// ResourceHazardTrackingModeUntracked indicates that the command encoder dependencies
// for this resource are tracked manually with Fence objects. This value is always set
// for resources sub-allocated from a Heap object and may optionally be specified for
// non-heap resources.
ResourceHazardTrackingModeUntracked ResourceOptions = 1 << resourceHazardTrackingModeShift
)
const (
resourceCPUCacheModeShift = 0
resourceStorageModeShift = 4
resourceHazardTrackingModeShift = 8
)
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode.
type CPUCacheMode uint8
const (
// CPUCacheModeDefaultCache is the default CPU cache mode for the resource.
// Guarantees that read and write operations are executed in the expected order.
CPUCacheModeDefaultCache CPUCacheMode = 0
// CPUCacheModeWriteCombined is a write-combined CPU cache mode for the resource.
// Optimized for resources that the CPU will write into, but never read.
CPUCacheModeWriteCombined CPUCacheMode = 1
)
// IndexType is the index type for an index buffer that references vertices of geometric primitives.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode
type IndexType uint8
const (
// IndexTypeUInt16 is a 16-bit unsigned integer used as a primitive index.
IndexTypeUInt16 IndexType = 0
// IndexTypeUInt32 is a 32-bit unsigned integer used as a primitive index.
IndexTypeUInt32 IndexType = 1
)
type TextureUsage uint8
const (
TextureUsageUnknown TextureUsage = 0x0000
TextureUsageShaderRead TextureUsage = 0x0001
TextureUsageShaderWrite TextureUsage = 0x0002
TextureUsageRenderTarget TextureUsage = 0x0004
TextureUsagePixelFormatView TextureUsage = 0x0008
)
type BlendFactor uint8
const (
BlendFactorZero BlendFactor = 0
BlendFactorOne BlendFactor = 1
BlendFactorSourceColor BlendFactor = 2
BlendFactorOneMinusSourceColor BlendFactor = 3
BlendFactorSourceAlpha BlendFactor = 4
BlendFactorOneMinusSourceAlpha BlendFactor = 5
BlendFactorDestinationColor BlendFactor = 6
BlendFactorOneMinusDestinationColor BlendFactor = 7
BlendFactorDestinationAlpha BlendFactor = 8
BlendFactorOneMinusDestinationAlpha BlendFactor = 9
BlendFactorSourceAlphaSaturated BlendFactor = 10
BlendFactorBlendColor BlendFactor = 11
BlendFactorOneMinusBlendColor BlendFactor = 12
BlendFactorBlendAlpha BlendFactor = 13
BlendFactorOneMinusBlendAlpha BlendFactor = 14
BlendFactorSource1Color BlendFactor = 15
BlendFactorOneMinusSource1Color BlendFactor = 16
BlendFactorSource1Alpha BlendFactor = 17
BlendFactorOneMinusSource1Alpha BlendFactor = 18
)
// Resource represents a memory allocation for storing specialized data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
type Resource interface {
// resource returns the underlying id<MTLResource> pointer.
resource() unsafe.Pointer
}
// RenderPipelineDescriptor configures new RenderPipelineState objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor.
type RenderPipelineDescriptor struct {
// VertexFunction is a programmable function that processes individual vertices in a rendering pass.
VertexFunction Function
// FragmentFunction is a programmable function that processes individual fragments in a rendering pass.
FragmentFunction Function
// ColorAttachments is an array of attachments that store color data.
ColorAttachments [1]RenderPipelineColorAttachmentDescriptor
}
// RenderPipelineColorAttachmentDescriptor describes a color render target that specifies
// the color configuration and color operations associated with a render pipeline.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor.
type RenderPipelineColorAttachmentDescriptor struct {
// PixelFormat is the pixel format of the color attachment's texture.
PixelFormat PixelFormat
BlendingEnabled bool
DestinationAlphaBlendFactor BlendFactor
DestinationRGBBlendFactor BlendFactor
SourceAlphaBlendFactor BlendFactor
SourceRGBBlendFactor BlendFactor
}
// RenderPassDescriptor describes a group of render targets that serve as
// the output destination for pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor.
type RenderPassDescriptor struct {
// ColorAttachments is array of state information for attachments that store color data.
ColorAttachments [1]RenderPassColorAttachmentDescriptor
}
// RenderPassColorAttachmentDescriptor describes a color render target that serves
// as the output destination for color pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor.
type RenderPassColorAttachmentDescriptor struct {
RenderPassAttachmentDescriptor
ClearColor ClearColor
}
// RenderPassAttachmentDescriptor describes a render target that serves
// as the output destination for pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor.
type RenderPassAttachmentDescriptor struct {
LoadAction LoadAction
StoreAction StoreAction
Texture Texture
}
// ClearColor is an RGBA value used for a color pixel.
//
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor.
type ClearColor struct {
Red, Green, Blue, Alpha float64
}
// TextureDescriptor configures new Texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor.
type TextureDescriptor struct {
PixelFormat PixelFormat
Width int
Height int
StorageMode StorageMode
Usage TextureUsage
}
// Device is abstract representation of the GPU that
// serves as the primary interface for a Metal app.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice.
type Device struct {
device unsafe.Pointer
// Headless indicates whether a device is configured as headless.
Headless bool
// LowPower indicates whether a device is low-power.
LowPower bool
// Removable determines whether or not a GPU is removable.
Removable bool
// RegistryID is the registry ID value for the device.
RegistryID uint64
// Name is the name of the device.
Name string
}
// CreateSystemDefaultDevice returns the preferred system default Metal device.
//
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice.
func CreateSystemDefaultDevice() (Device, error) {
d := C.CreateSystemDefaultDevice()
if d.Device == nil {
return Device{}, errors.New("Metal is not supported on this system")
}
return Device{
device: d.Device,
Headless: d.Headless != 0,
LowPower: d.LowPower != 0,
Removable: d.Removable != 0,
RegistryID: uint64(d.RegistryID),
Name: C.GoString(d.Name),
}, nil
}
// CopyAllDevices returns all Metal devices in the system.
//
// Reference: https://developer.apple.com/documentation/metal/1433367-mtlcopyalldevices.
func CopyAllDevices() []Device {
d := C.CopyAllDevices()
defer C.free(unsafe.Pointer(d.Devices))
ds := make([]Device, d.Length)
for i := 0; i < len(ds); i++ {
d := (*C.struct_Device)(unsafe.Pointer(uintptr(unsafe.Pointer(d.Devices)) + uintptr(i)*C.sizeof_struct_Device))
ds[i].device = d.Device
ds[i].Headless = d.Headless != 0
ds[i].LowPower = d.LowPower != 0
ds[i].Removable = d.Removable != 0
ds[i].RegistryID = uint64(d.RegistryID)
ds[i].Name = C.GoString(d.Name)
}
return ds
}
// Device returns the underlying id<MTLDevice> pointer.
func (d Device) Device() unsafe.Pointer { return d.device }
// SupportsFeatureSet reports whether device d supports feature set fs.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
return C.Device_SupportsFeatureSet(d.device, C.uint16_t(fs)) != 0
}
// MakeCommandQueue creates a serial command submission queue.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue.
func (d Device) MakeCommandQueue() CommandQueue {
return CommandQueue{C.Device_MakeCommandQueue(d.device)}
}
// MakeLibrary creates a new library that contains
// the functions stored in the specified source string.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary.
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) {
l := C.Go_Device_MakeLibrary(d.device, source) // TODO: opt.
if l.Library == nil {
return Library{}, errors.New(C.GoString(l.Error))
}
return Library{l.Library}, nil
}
// MakeRenderPipelineState creates a render pipeline state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate.
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
blendingEnabled := C.BOOL(0)
if rpd.ColorAttachments[0].BlendingEnabled {
blendingEnabled = C.BOOL(1)
}
c := &rpd.ColorAttachments[0]
descriptor := C.struct_RenderPipelineDescriptor{
VertexFunction: rpd.VertexFunction.function,
FragmentFunction: rpd.FragmentFunction.function,
ColorAttachment0PixelFormat: C.uint16_t(c.PixelFormat),
ColorAttachment0BlendingEnabled: C.BOOL(blendingEnabled),
ColorAttachment0DestinationAlphaBlendFactor: C.uint8_t(c.DestinationAlphaBlendFactor),
ColorAttachment0DestinationRGBBlendFactor: C.uint8_t(c.DestinationRGBBlendFactor),
ColorAttachment0SourceAlphaBlendFactor: C.uint8_t(c.SourceAlphaBlendFactor),
ColorAttachment0SourceRGBBlendFactor: C.uint8_t(c.SourceRGBBlendFactor),
}
rps := C.Device_MakeRenderPipelineState(d.device, descriptor)
if rps.RenderPipelineState == nil {
return RenderPipelineState{}, errors.New(C.GoString(rps.Error))
}
return RenderPipelineState{rps.RenderPipelineState}, nil
}
// MakeBuffer allocates a new buffer of a given length
// and initializes its contents by copying existing data into it.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer.
func (d Device) MakeBuffer(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
return Buffer{C.Device_MakeBuffer(d.device, bytes, C.size_t(length), C.uint16_t(opt))}
}
// MakeTexture creates a texture object with privately owned storage
// that contains texture state.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture.
func (d Device) MakeTexture(td TextureDescriptor) Texture {
descriptor := C.struct_TextureDescriptor{
PixelFormat: C.uint16_t(td.PixelFormat),
Width: C.uint_t(td.Width),
Height: C.uint_t(td.Height),
StorageMode: C.uint8_t(td.StorageMode),
Usage: C.uint8_t(td.Usage),
}
return Texture{
texture: C.Device_MakeTexture(d.device, descriptor),
Width: td.Width, // TODO: Fetch dimensions of actually created texture.
Height: td.Height, // TODO: Fetch dimensions of actually created texture.
}
}
// CompileOptions specifies optional compilation settings for
// the graphics or compute functions within a library.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
type CompileOptions struct {
// TODO.
}
// Drawable is a displayable resource that can be rendered or written to.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
type Drawable interface {
// Drawable returns the underlying id<MTLDrawable> pointer.
Drawable() unsafe.Pointer
}
// CommandQueue is a queue that organizes the order
// in which command buffers are executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue.
type CommandQueue struct {
commandQueue unsafe.Pointer
}
func (c CommandQueue) Release() {
C.CommandQueue_Release(c.commandQueue)
}
// MakeCommandBuffer creates a command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer.
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
return CommandBuffer{C.CommandQueue_MakeCommandBuffer(cq.commandQueue)}
}
// CommandBuffer is a container that stores encoded commands
// that are committed to and executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
type CommandBuffer struct {
commandBuffer unsafe.Pointer
}
// PresentDrawable registers a drawable presentation to occur as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
func (cb CommandBuffer) PresentDrawable(d Drawable) {
C.CommandBuffer_PresentDrawable(cb.commandBuffer, d.Drawable())
}
// Commit commits this command buffer for execution as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
func (cb CommandBuffer) Commit() {
C.CommandBuffer_Commit(cb.commandBuffer)
}
// WaitUntilCompleted waits for the execution of this command buffer to complete.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted.
func (cb CommandBuffer) WaitUntilCompleted() {
C.CommandBuffer_WaitUntilCompleted(cb.commandBuffer)
}
// MakeRenderCommandEncoder creates an encoder object that can
// encode graphics rendering commands into this command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder.
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder {
descriptor := C.struct_RenderPassDescriptor{
ColorAttachment0LoadAction: C.uint8_t(rpd.ColorAttachments[0].LoadAction),
ColorAttachment0StoreAction: C.uint8_t(rpd.ColorAttachments[0].StoreAction),
ColorAttachment0ClearColor: C.struct_ClearColor{
Red: C.double(rpd.ColorAttachments[0].ClearColor.Red),
Green: C.double(rpd.ColorAttachments[0].ClearColor.Green),
Blue: C.double(rpd.ColorAttachments[0].ClearColor.Blue),
Alpha: C.double(rpd.ColorAttachments[0].ClearColor.Alpha),
},
ColorAttachment0Texture: rpd.ColorAttachments[0].Texture.texture,
}
return RenderCommandEncoder{CommandEncoder{C.CommandBuffer_MakeRenderCommandEncoder(cb.commandBuffer, descriptor)}}
}
// MakeBlitCommandEncoder creates an encoder object that can encode
// memory operation (blit) commands into this command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder.
func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
return BlitCommandEncoder{CommandEncoder{C.CommandBuffer_MakeBlitCommandEncoder(cb.commandBuffer)}}
}
// CommandEncoder is an encoder that writes sequential GPU commands
// into a command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder.
type CommandEncoder struct {
commandEncoder unsafe.Pointer
}
// EndEncoding declares that all command generation from this encoder is completed.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding.
func (ce CommandEncoder) EndEncoding() {
C.CommandEncoder_EndEncoding(ce.commandEncoder)
}
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands
// and executes graphics functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder.
type RenderCommandEncoder struct {
CommandEncoder
}
func (rce RenderCommandEncoder) Release() {
C.RenderCommandEncoder_Release(rce.commandEncoder)
}
// SetRenderPipelineState sets the current render pipeline state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate.
func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
C.RenderCommandEncoder_SetRenderPipelineState(rce.commandEncoder, rps.renderPipelineState)
}
func (rce RenderCommandEncoder) SetViewport(viewport Viewport) {
C.RenderCommandEncoder_SetViewport(rce.commandEncoder, viewport.c())
}
// SetVertexBuffer sets a buffer for the vertex shader function at an index
// in the buffer argument table with an offset that specifies the start of the data.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer.
func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
C.RenderCommandEncoder_SetVertexBuffer(rce.commandEncoder, buf.buffer, C.uint_t(offset), C.uint_t(index))
}
// SetVertexBytes sets a block of data for the vertex function.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes.
func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
C.RenderCommandEncoder_SetVertexBytes(rce.commandEncoder, bytes, C.size_t(length), C.uint_t(index))
}
func (rce RenderCommandEncoder) SetFragmentBytes(bytes unsafe.Pointer, length uintptr, index int) {
C.RenderCommandEncoder_SetFragmentBytes(rce.commandEncoder, bytes, C.size_t(length), C.uint_t(index))
}
// SetFragmentTexture sets a texture for the fragment function at an index in the texture argument table.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture
func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) {
C.RenderCommandEncoder_SetFragmentTexture(rce.commandEncoder, texture.texture, C.uint_t(index))
}
func (rce RenderCommandEncoder) SetBlendColor(red, green, blue, alpha float32) {
C.RenderCommandEncoder_SetBlendColor(rce.commandEncoder, C.float(red), C.float(green), C.float(blue), C.float(alpha))
}
// DrawPrimitives renders one instance of primitives using vertex data
// in contiguous array elements.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives.
func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
C.RenderCommandEncoder_DrawPrimitives(rce.commandEncoder, C.uint8_t(typ), C.uint_t(vertexStart), C.uint_t(vertexCount))
}
// DrawIndexedPrimitives encodes a command to render one instance of primitives using an index list specified in a buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515542-drawindexedprimitives
func (rce RenderCommandEncoder) DrawIndexedPrimitives(typ PrimitiveType, indexCount int, indexType IndexType, indexBuffer Buffer, indexBufferOffset int) {
C.RenderCommandEncoder_DrawIndexedPrimitives(rce.commandEncoder, C.uint8_t(typ), C.uint_t(indexCount), C.uint8_t(indexType), indexBuffer.buffer, C.uint_t(indexBufferOffset))
}
// BlitCommandEncoder is an encoder that specifies resource copy
// and resource synchronization commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder.
type BlitCommandEncoder struct {
CommandEncoder
}
// Synchronize flushes any copy of the specified resource from its corresponding
// Device caches and, if needed, invalidates any CPU caches.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize.
func (bce BlitCommandEncoder) Synchronize(resource Resource) {
C.BlitCommandEncoder_Synchronize(bce.commandEncoder, resource.resource())
}
func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) {
C.BlitCommandEncoder_SynchronizeTexture(bce.commandEncoder, texture.texture, C.uint_t(slice), C.uint_t(level))
}
// Library is a collection of compiled graphics or compute functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
type Library struct {
library unsafe.Pointer
}
// MakeFunction returns a pre-compiled, non-specialized function.
//
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-makefunction.
func (l Library) MakeFunction(name string) (Function, error) {
f := C.Library_MakeFunction(l.library, C.CString(name))
if f == nil {
return Function{}, fmt.Errorf("function %q not found", name)
}
return Function{f}, nil
}
// Texture is a memory allocation for storing formatted
// image data that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
type Texture struct {
texture unsafe.Pointer
// TODO: Change these fields into methods.
// Width is the width of the texture image for the base level mipmap, in pixels.
Width int
// Height is the height of the texture image for the base level mipmap, in pixels.
Height int
}
// NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer.
func NewTexture(texture unsafe.Pointer) Texture {
return Texture{texture: texture}
}
// resource implements the Resource interface.
func (t Texture) resource() unsafe.Pointer { return t.texture }
func (t Texture) Release() {
C.Texture_Release(t.texture)
}
// GetBytes copies a block of pixels from the storage allocation of texture
// slice zero into system memory at a specified address.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes.
func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, level int) {
r := region.c()
C.Texture_GetBytes(t.texture, unsafe.Pointer(pixelBytes), C.size_t(bytesPerRow), r, C.uint_t(level))
}
// ReplaceRegion copies a block of pixels from the caller's pointer into the storage allocation for slice 0 of a texture.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion
func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Pointer, bytesPerRow int) {
r := region.c()
C.Texture_ReplaceRegion(t.texture, r, C.uint_t(level), pixelBytes, C.uint_t(bytesPerRow))
}
// Buffer is a memory allocation for storing unformatted data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer.
type Buffer struct {
buffer unsafe.Pointer
}
func (b Buffer) Release() {
C.Buffer_Release(b.buffer)
}
// Function represents a programmable graphics or compute function executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfunction.
type Function struct {
function unsafe.Pointer
}
// RenderPipelineState contains the graphics functions
// and configuration state used in a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate.
type RenderPipelineState struct {
renderPipelineState unsafe.Pointer
}
// Region is a rectangular block of pixels in an image or texture,
// defined by its upper-left corner and its size.
//
// Reference: https://developer.apple.com/documentation/metal/mtlregion.
type Region struct {
Origin Origin // The location of the upper-left corner of the block.
Size Size // The size of the block.
}
func (r *Region) c() C.struct_Region {
return C.struct_Region{
Origin: C.struct_Origin{
X: C.uint_t(r.Origin.X),
Y: C.uint_t(r.Origin.Y),
Z: C.uint_t(r.Origin.Z),
},
Size: C.struct_Size{
Width: C.uint_t(r.Size.Width),
Height: C.uint_t(r.Size.Height),
Depth: C.uint_t(r.Size.Depth),
},
}
}
// Origin represents the location of a pixel in an image or texture relative
// to the upper-left corner, whose coordinates are (0, 0).
//
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
type Origin struct{ X, Y, Z int }
// Size represents the set of dimensions that declare the size of an object,
// such as an image, texture, threadgroup, or grid.
//
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
type Size struct{ Width, Height, Depth int }
// RegionMake2D returns a 2D, rectangular region for image or texture data.
//
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
func RegionMake2D(x, y, width, height int) Region {
return Region{
Origin: Origin{x, y, 0},
Size: Size{width, height, 1},
}
}
type Viewport struct {
OriginX float64
OriginY float64
Width float64
Height float64
ZNear float64
ZFar float64
}
func (v *Viewport) c() C.struct_Viewport {
return C.struct_Viewport{
OriginX: C.double(v.OriginX),
OriginY: C.double(v.OriginY),
Width: C.double(v.Width),
Height: C.double(v.Height),
ZNear: C.double(v.ZNear),
ZFar: C.double(v.ZFar),
}
}

View File

@ -0,0 +1,175 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
#include <stddef.h>
typedef signed char BOOL;
typedef unsigned long uint_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long long uint64_t;
struct Device {
void *Device;
BOOL Headless;
BOOL LowPower;
BOOL Removable;
uint64_t RegistryID;
const char *Name;
};
struct Devices {
struct Device *Devices;
int Length;
};
struct Library {
void *Library;
const char *Error;
};
struct RenderPipelineDescriptor {
void *VertexFunction;
void *FragmentFunction;
uint16_t ColorAttachment0PixelFormat;
BOOL ColorAttachment0BlendingEnabled;
uint8_t ColorAttachment0DestinationAlphaBlendFactor;
uint8_t ColorAttachment0DestinationRGBBlendFactor;
uint8_t ColorAttachment0SourceAlphaBlendFactor;
uint8_t ColorAttachment0SourceRGBBlendFactor;
};
struct RenderPipelineState {
void *RenderPipelineState;
const char *Error;
};
struct ClearColor {
double Red;
double Green;
double Blue;
double Alpha;
};
struct RenderPassDescriptor {
uint8_t ColorAttachment0LoadAction;
uint8_t ColorAttachment0StoreAction;
struct ClearColor ColorAttachment0ClearColor;
void *ColorAttachment0Texture;
};
struct TextureDescriptor {
uint16_t PixelFormat;
uint_t Width;
uint_t Height;
uint8_t StorageMode;
uint8_t Usage;
};
struct Origin {
uint_t X;
uint_t Y;
uint_t Z;
};
struct Size {
uint_t Width;
uint_t Height;
uint_t Depth;
};
struct Region {
struct Origin Origin;
struct Size Size;
};
struct Viewport {
double OriginX;
double OriginY;
double Width;
double Height;
double ZNear;
double ZFar;
};
struct Device CreateSystemDefaultDevice();
struct Devices CopyAllDevices();
BOOL Device_SupportsFeatureSet(void *device, uint16_t featureSet);
void *Device_MakeCommandQueue(void *device);
struct Library Device_MakeLibrary(void *device, const char *source,
size_t sourceLength);
struct RenderPipelineState
Device_MakeRenderPipelineState(void *device,
struct RenderPipelineDescriptor descriptor);
void *Device_MakeBuffer(void *device, const void *bytes, size_t length,
uint16_t options);
void *Device_MakeTexture(void *device, struct TextureDescriptor descriptor);
void CommandQueue_Release(void *commandQueue);
void *CommandQueue_MakeCommandBuffer(void *commandQueue);
void CommandBuffer_Release(void *commandBuffer);
void CommandBuffer_PresentDrawable(void *commandBuffer, void *drawable);
void CommandBuffer_Commit(void *commandBuffer);
void CommandBuffer_WaitUntilCompleted(void *commandBuffer);
void *
CommandBuffer_MakeRenderCommandEncoder(void *commandBuffer,
struct RenderPassDescriptor descriptor);
void *CommandBuffer_MakeBlitCommandEncoder(void *commandBuffer);
void CommandEncoder_EndEncoding(void *commandEncoder);
void RenderCommandEncoder_Release(void *renderCommandEncoder);
void RenderCommandEncoder_SetRenderPipelineState(void *renderCommandEncoder,
void *renderPipelineState);
void RenderCommandEncoder_SetViewport(void *renderCommandEncoder,
struct Viewport viewport);
void RenderCommandEncoder_SetVertexBuffer(void *renderCommandEncoder,
void *buffer, uint_t offset,
uint_t index);
void RenderCommandEncoder_SetVertexBytes(void *renderCommandEncoder,
const void *bytes, size_t length,
uint_t index);
void RenderCommandEncoder_SetFragmentBytes(void *renderCommandEncoder,
const void *bytes, size_t length,
uint_t index);
void RenderCommandEncoder_SetBlendColor(void *renderCommandEncoder, float red,
float green, float blue, float alpha);
void RenderCommandEncoder_SetFragmentTexture(void *renderCommandEncoder,
void *texture, uint_t index);
void RenderCommandEncoder_DrawPrimitives(void *renderCommandEncoder,
uint8_t primitiveType,
uint_t vertexStart,
uint_t vertexCount);
void RenderCommandEncoder_DrawIndexedPrimitives(
void *renderCommandEncoder, uint8_t primitiveType, uint_t indexCount,
uint8_t indexType, void *indexBuffer, uint_t indexBufferOffset);
void BlitCommandEncoder_Synchronize(void *blitCommandEncoder, void *resource);
void BlitCommandEncoder_SynchronizeTexture(void *blitCommandEncoder,
void *texture, uint_t slice,
uint_t level);
void *Library_MakeFunction(void *library, const char *name);
void Texture_Release(void *texture);
void Texture_GetBytes(void *texture, void *pixelBytes, size_t bytesPerRow,
struct Region region, uint_t level);
void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
void *pixelBytes, uint_t bytesPerRow);
void Buffer_Release(void *buffer);

View File

@ -0,0 +1,318 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
#include "mtl.h"
#import <Metal/Metal.h>
#include <stdlib.h>
struct Device CreateSystemDefaultDevice() {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (!device) {
struct Device d;
d.Device = NULL;
return d;
}
struct Device d;
d.Device = device;
d.Headless = device.headless;
d.LowPower = device.lowPower;
d.Removable = device.removable;
d.RegistryID = device.registryID;
d.Name = device.name.UTF8String;
return d;
}
// Caller must call free(d.devices).
struct Devices CopyAllDevices() {
NSArray<id<MTLDevice>> *devices = MTLCopyAllDevices();
struct Devices d;
d.Devices = malloc(devices.count * sizeof(struct Device));
for (int i = 0; i < devices.count; i++) {
d.Devices[i].Device = devices[i];
d.Devices[i].Headless = devices[i].headless;
d.Devices[i].LowPower = devices[i].lowPower;
d.Devices[i].Removable = devices[i].removable;
d.Devices[i].RegistryID = devices[i].registryID;
d.Devices[i].Name = devices[i].name.UTF8String;
}
d.Length = devices.count;
return d;
}
BOOL Device_SupportsFeatureSet(void *device, uint16_t featureSet) {
return [(id<MTLDevice>)device supportsFeatureSet:featureSet];
}
void *Device_MakeCommandQueue(void *device) {
return [(id<MTLDevice>)device newCommandQueue];
}
struct Library Device_MakeLibrary(void *device, const char *source,
size_t sourceLength) {
NSError *error;
id<MTLLibrary> library = [(id<MTLDevice>)device
newLibraryWithSource:[[NSString alloc] initWithBytes:source
length:sourceLength
encoding:NSUTF8StringEncoding]
options:NULL // TODO.
error:&error];
struct Library l;
l.Library = library;
if (!library) {
l.Error = error.localizedDescription.UTF8String;
}
return l;
}
struct RenderPipelineState
Device_MakeRenderPipelineState(void *device,
struct RenderPipelineDescriptor descriptor) {
MTLRenderPipelineDescriptor *renderPipelineDescriptor =
[[MTLRenderPipelineDescriptor alloc] init];
renderPipelineDescriptor.vertexFunction = descriptor.VertexFunction;
renderPipelineDescriptor.fragmentFunction = descriptor.FragmentFunction;
renderPipelineDescriptor.colorAttachments[0].pixelFormat =
descriptor.ColorAttachment0PixelFormat;
renderPipelineDescriptor.colorAttachments[0].blendingEnabled =
descriptor.ColorAttachment0BlendingEnabled;
renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor =
descriptor.ColorAttachment0DestinationAlphaBlendFactor;
renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor =
descriptor.ColorAttachment0DestinationRGBBlendFactor;
renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor =
descriptor.ColorAttachment0SourceAlphaBlendFactor;
renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor =
descriptor.ColorAttachment0SourceRGBBlendFactor;
NSError *error;
id<MTLRenderPipelineState> renderPipelineState = [(id<MTLDevice>)device
newRenderPipelineStateWithDescriptor:renderPipelineDescriptor
error:&error];
[renderPipelineDescriptor release];
struct RenderPipelineState rps;
rps.RenderPipelineState = renderPipelineState;
if (!renderPipelineState) {
rps.Error = error.localizedDescription.UTF8String;
}
return rps;
}
void *Device_MakeBuffer(void *device, const void *bytes, size_t length,
uint16_t options) {
return [(id<MTLDevice>)device newBufferWithBytes:(const void *)bytes
length:(NSUInteger)length
options:(MTLResourceOptions)options];
}
void *Device_MakeTexture(void *device, struct TextureDescriptor descriptor) {
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
textureDescriptor.pixelFormat = descriptor.PixelFormat;
textureDescriptor.width = descriptor.Width;
textureDescriptor.height = descriptor.Height;
textureDescriptor.storageMode = descriptor.StorageMode;
textureDescriptor.usage = descriptor.Usage;
id<MTLTexture> texture =
[(id<MTLDevice>)device newTextureWithDescriptor:textureDescriptor];
[textureDescriptor release];
return texture;
}
void CommandQueue_Release(void *commandQueue) {
[(id<MTLCommandQueue>)commandQueue release];
}
void *CommandQueue_MakeCommandBuffer(void *commandQueue) {
return [(id<MTLCommandQueue>)commandQueue commandBuffer];
}
void CommandBuffer_PresentDrawable(void *commandBuffer, void *drawable) {
[(id<MTLCommandBuffer>)commandBuffer
presentDrawable:(id<MTLDrawable>)drawable];
}
void CommandBuffer_Commit(void *commandBuffer) {
[(id<MTLCommandBuffer>)commandBuffer commit];
}
void CommandBuffer_WaitUntilCompleted(void *commandBuffer) {
[(id<MTLCommandBuffer>)commandBuffer waitUntilCompleted];
}
void *
CommandBuffer_MakeRenderCommandEncoder(void *commandBuffer,
struct RenderPassDescriptor descriptor) {
MTLRenderPassDescriptor *renderPassDescriptor =
[[MTLRenderPassDescriptor alloc] init];
renderPassDescriptor.colorAttachments[0].loadAction =
descriptor.ColorAttachment0LoadAction;
renderPassDescriptor.colorAttachments[0].storeAction =
descriptor.ColorAttachment0StoreAction;
renderPassDescriptor.colorAttachments[0].clearColor =
MTLClearColorMake(descriptor.ColorAttachment0ClearColor.Red,
descriptor.ColorAttachment0ClearColor.Green,
descriptor.ColorAttachment0ClearColor.Blue,
descriptor.ColorAttachment0ClearColor.Alpha);
renderPassDescriptor.colorAttachments[0].texture =
(id<MTLTexture>)descriptor.ColorAttachment0Texture;
id<MTLRenderCommandEncoder> rce = [(id<MTLCommandBuffer>)commandBuffer
renderCommandEncoderWithDescriptor:renderPassDescriptor];
[renderPassDescriptor release];
return rce;
}
void *CommandBuffer_MakeBlitCommandEncoder(void *commandBuffer) {
return [(id<MTLCommandBuffer>)commandBuffer blitCommandEncoder];
}
void CommandEncoder_EndEncoding(void *commandEncoder) {
[(id<MTLCommandEncoder>)commandEncoder endEncoding];
}
void RenderCommandEncoder_Release(void *renderCommandEncoder) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder release];
}
void RenderCommandEncoder_SetRenderPipelineState(void *renderCommandEncoder,
void *renderPipelineState) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
setRenderPipelineState:(id<MTLRenderPipelineState>)renderPipelineState];
}
void RenderCommandEncoder_SetViewport(void *renderCommandEncoder,
struct Viewport viewport) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
setViewport:(MTLViewport){
viewport.OriginX,
viewport.OriginY,
viewport.Width,
viewport.Height,
viewport.ZNear,
viewport.ZFar,
}];
}
void RenderCommandEncoder_SetVertexBuffer(void *renderCommandEncoder,
void *buffer, uint_t offset,
uint_t index) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
setVertexBuffer:(id<MTLBuffer>)buffer
offset:(NSUInteger)offset
atIndex:(NSUInteger)index];
}
void RenderCommandEncoder_SetVertexBytes(void *renderCommandEncoder,
const void *bytes, size_t length,
uint_t index) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
setVertexBytes:bytes
length:(NSUInteger)length
atIndex:(NSUInteger)index];
}
void RenderCommandEncoder_SetFragmentBytes(void *renderCommandEncoder,
const void *bytes, size_t length,
uint_t index) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
setFragmentBytes:bytes
length:(NSUInteger)length
atIndex:(NSUInteger)index];
}
void RenderCommandEncoder_SetFragmentTexture(void *renderCommandEncoder,
void *texture, uint_t index) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
setFragmentTexture:(id<MTLTexture>)texture
atIndex:(NSUInteger)index];
}
void RenderCommandEncoder_SetBlendColor(void *renderCommandEncoder, float red,
float green, float blue, float alpha) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder setBlendColorRed:red
green:green
blue:blue
alpha:alpha];
}
void RenderCommandEncoder_DrawPrimitives(void *renderCommandEncoder,
uint8_t primitiveType,
uint_t vertexStart,
uint_t vertexCount) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
drawPrimitives:(MTLPrimitiveType)primitiveType
vertexStart:(NSUInteger)vertexStart
vertexCount:(NSUInteger)vertexCount];
}
void RenderCommandEncoder_DrawIndexedPrimitives(
void *renderCommandEncoder, uint8_t primitiveType, uint_t indexCount,
uint8_t indexType, void *indexBuffer, uint_t indexBufferOffset) {
[(id<MTLRenderCommandEncoder>)renderCommandEncoder
drawIndexedPrimitives:(MTLPrimitiveType)primitiveType
indexCount:(NSUInteger)indexCount
indexType:(MTLIndexType)indexType
indexBuffer:(id<MTLBuffer>)indexBuffer
indexBufferOffset:(NSUInteger)indexBufferOffset];
}
void BlitCommandEncoder_Synchronize(void *blitCommandEncoder, void *resource) {
[(id<MTLBlitCommandEncoder>)blitCommandEncoder
synchronizeResource:(id<MTLResource>)resource];
}
void BlitCommandEncoder_SynchronizeTexture(void *blitCommandEncoder,
void *texture, uint_t slice,
uint_t level) {
[(id<MTLBlitCommandEncoder>)blitCommandEncoder
synchronizeTexture:(id<MTLTexture>)texture
slice:(NSUInteger)slice
level:(NSUInteger)level];
}
void *Library_MakeFunction(void *library, const char *name) {
return [(id<MTLLibrary>)library
newFunctionWithName:[NSString stringWithUTF8String:name]];
}
void Texture_Release(void *texture) { [(id<MTLTexture>)texture release]; }
void Texture_GetBytes(void *texture, void *pixelBytes, size_t bytesPerRow,
struct Region region, uint_t level) {
[(id<MTLTexture>)texture
getBytes:(void *)pixelBytes
bytesPerRow:(NSUInteger)bytesPerRow
fromRegion:(MTLRegion) {
{region.Origin.X, region.Origin.Y, region.Origin.Z}, {
region.Size.Width, region.Size.Height, region.Size.Depth
}
}
mipmapLevel:(NSUInteger)level];
}
void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
void *bytes, uint_t bytesPerRow) {
[(id<MTLTexture>)texture replaceRegion:(MTLRegion) {
{region.Origin.X, region.Origin.Y, region.Origin.Z}, {
region.Size.Width, region.Size.Height, region.Size.Depth
}
}
mipmapLevel:(NSUInteger)level
withBytes:bytes
bytesPerRow:(NSUInteger)bytesPerRow];
}
void Buffer_Release(void *buffer) { [(id<MTLBuffer>)buffer release]; }

View File

@ -0,0 +1,179 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
package mtl_test
import (
"fmt"
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"testing"
"unsafe"
"golang.org/x/image/math/f32"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
)
func Disabled_TestRenderTriangle(t *testing.T) {
device, err := mtl.CreateSystemDefaultDevice()
if err != nil {
t.Fatal(err)
}
// Create a render pipeline state.
const source = `#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
float4 color;
};
vertex Vertex VertexShader(
uint vertexID [[vertex_id]],
device Vertex * vertices [[buffer(0)]]
) {
return vertices[vertexID];
}
fragment float4 FragmentShader(Vertex in [[stage_in]]) {
return in.color;
}
`
lib, err := device.MakeLibrary(source, mtl.CompileOptions{})
if err != nil {
t.Fatal(err)
}
vs, err := lib.MakeFunction("VertexShader")
if err != nil {
t.Fatal(err)
}
fs, err := lib.MakeFunction("FragmentShader")
if err != nil {
t.Fatal(err)
}
var rpld mtl.RenderPipelineDescriptor
rpld.VertexFunction = vs
rpld.FragmentFunction = fs
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rps, err := device.MakeRenderPipelineState(rpld)
if err != nil {
t.Fatal(err)
}
// Create a vertex buffer.
type Vertex struct {
Position f32.Vec4
Color f32.Vec4
}
vertexData := [...]Vertex{
{f32.Vec4{+0.00, +0.75, 0, 1}, f32.Vec4{1, 0, 0, 1}},
{f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{0, 1, 0, 1}},
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 1, 1}},
}
vertexBuffer := device.MakeBuffer(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
// Create an output texture to render into.
td := mtl.TextureDescriptor{
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: 512,
Height: 512,
StorageMode: mtl.StorageModeManaged,
}
texture := device.MakeTexture(td)
cq := device.MakeCommandQueue()
cb := cq.MakeCommandBuffer()
// Encode all render commands.
var rpd mtl.RenderPassDescriptor
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0.35, Green: 0.65, Blue: 0.85, Alpha: 1}
rpd.ColorAttachments[0].Texture = texture
rce := cb.MakeRenderCommandEncoder(rpd)
rce.SetRenderPipelineState(rps)
rce.SetVertexBuffer(vertexBuffer, 0, 0)
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
rce.EndEncoding()
// Encode all blit commands.
bce := cb.MakeBlitCommandEncoder()
bce.Synchronize(texture)
bce.EndEncoding()
cb.Commit()
cb.WaitUntilCompleted()
// Read pixels from output texture into an image.
got := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
bytesPerRow := 4 * texture.Width
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
texture.GetBytes(&got.Pix[0], uintptr(bytesPerRow), region, 0)
// TODO: Embed this file?
want, err := readPNG(filepath.Join("testdata", "triangle.png"))
if err != nil {
t.Fatal(err)
}
if err := imageEq(got, want); err != nil {
t.Errorf("got image != want: %v", err)
}
}
func readPNG(name string) (image.Image, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return png.Decode(f)
}
// imageEq reports whether images m, n are considered equivalent. Two images are considered
// equivalent if they have same bounds, and all pixel colors are within a small margin.
func imageEq(m, n image.Image) error {
if m.Bounds() != n.Bounds() {
return fmt.Errorf("bounds don't match: %v != %v", m.Bounds(), n.Bounds())
}
for y := m.Bounds().Min.Y; y < m.Bounds().Max.Y; y++ {
for x := m.Bounds().Min.X; x < m.Bounds().Max.X; x++ {
c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA)
d := color.NRGBAModel.Convert(n.At(x, y)).(color.NRGBA)
if !colorEq(c, d) {
return fmt.Errorf("pixel (%v, %v) doesn't match: %+v != %+v", x, y, c, d)
}
}
}
return nil
}
// colorEq reports whether colors c, d are considered equivalent, i.e., within a small margin.
func colorEq(c, d color.NRGBA) bool {
return eqEpsilon(c.R, d.R) && eqEpsilon(c.G, d.G) && eqEpsilon(c.B, d.B) && eqEpsilon(c.A, d.A)
}
// eqEpsilon reports whether a and b are within epsilon of each other.
func eqEpsilon(a, b uint8) bool {
const epsilon = 1
return uint16(a)-uint16(b) <= epsilon || uint16(b)-uint16(a) <= epsilon
}

View File

@ -0,0 +1,76 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
// Package ns provides access to Apple's AppKit API (https://developer.apple.com/documentation/appkit).
//
// This package is in very early stages of development.
// It's a minimal implementation with scope limited to
// supporting the movingtriangle example.
package ns
import (
"unsafe"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca"
)
// #include "ns.h"
import "C"
// Window is a window that an app displays on the screen.
//
// Reference: https://developer.apple.com/documentation/appkit/nswindow.
type Window struct {
window unsafe.Pointer
}
// NewWindow returns a Window that wraps an existing NSWindow * pointer.
func NewWindow(window unsafe.Pointer) Window {
return Window{window}
}
// ContentView returns the window's content view, the highest accessible View
// in the window's view hierarchy.
//
// Reference: https://developer.apple.com/documentation/appkit/nswindow/1419160-contentview.
func (w Window) ContentView() View {
return View{C.Window_ContentView(w.window)}
}
// View is the infrastructure for drawing, printing, and handling events in an app.
//
// Reference: https://developer.apple.com/documentation/appkit/nsview.
type View struct {
view unsafe.Pointer
}
// SetLayer sets v.layer to l.
//
// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer.
func (v View) SetLayer(l ca.Layer) {
C.View_SetLayer(v.view, l.Layer())
}
// SetWantsLayer sets v.wantsLayer to wantsLayer.
//
// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer.
func (v View) SetWantsLayer(wantsLayer bool) {
if wantsLayer {
C.View_SetWantsLayer(v.view, 1)
} else {
C.View_SetWantsLayer(v.view, 0)
}
}

View File

@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !darwin ios js
// +build darwin
package shareable
typedef signed char BOOL;
const (
initSize = 1024
maxSize = 4096
)
void *Window_ContentView(void *window);
void View_SetLayer(void *view, void *layer);
void View_SetWantsLayer(void *view, BOOL wantsLayer);

View File

@ -13,17 +13,18 @@
// limitations under the License.
// +build darwin
// +build !ios
// +build !js
package shareable
#include "ns.h"
#import <Cocoa/Cocoa.h>
// On MacBook Pro 2013 (Late), there is a bug in texture rendering and
// extending shareable textures sometimes fail (#593). This is due to
// a bug in the grahics driver, and there is nothing we can do. Let's
// not extend shareable textures in such environment.
void *Window_ContentView(void *window) {
return ((NSWindow *)window).contentView;
}
const (
initSize = 4096
maxSize = 4096
)
void View_SetLayer(void *view, void *layer) {
((NSView *)view).layer = (CALayer *)layer;
}
void View_SetWantsLayer(void *view, BOOL wantsLayer) {
((NSView *)view).wantsLayer = wantsLayer;
}

View File

@ -33,6 +33,10 @@ type Driver struct {
context context
}
func (d *Driver) SetWindow(window uintptr) {
// Do nothing.
}
func (d *Driver) checkSize(width, height int) {
if width < 1 {
panic(fmt.Sprintf("opengl: width (%d) must be equal or more than 1", width))
@ -105,3 +109,15 @@ func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode
func (d *Driver) Flush() {
d.context.flush()
}
func (d *Driver) SetVsyncEnabled(enabled bool) {
// Do nothing
}
func (d *Driver) VDirection() graphicsdriver.VDirection {
return graphicsdriver.VDownward
}
func (d *Driver) IsGL() bool {
return true
}

View File

@ -105,6 +105,11 @@ func (i *Image) BasePixelsForTesting() []byte {
return i.basePixels
}
func (i *Image) Pixels() []byte {
i.readPixelsFromGPUIfNeeded()
return i.basePixels
}
// Size returns the image's size.
func (i *Image) Size() (int, int) {
return i.image.Size()
@ -245,6 +250,15 @@ func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, indices
i.drawImageHistory = append(i.drawImageHistory, item)
}
func (i *Image) readPixelsFromGPUIfNeeded() {
if i.basePixels == nil || i.drawImageHistory != nil || i.stale {
graphicscommand.FlushCommands()
i.readPixelsFromGPU()
i.drawImageHistory = nil
i.stale = false
}
}
// At returns a color value at (x, y).
//
// Note that this must not be called until context is available.
@ -254,12 +268,7 @@ func (i *Image) At(x, y int) color.RGBA {
return color.RGBA{}
}
if i.basePixels == nil || i.drawImageHistory != nil || i.stale {
graphicscommand.FlushCommands()
i.readPixelsFromGPU()
i.drawImageHistory = nil
i.stale = false
}
i.readPixelsFromGPUIfNeeded()
// Even after readPixelsFromGPU, basePixels might be nil when OpenGL error happens.
if i.basePixels == nil {

View File

@ -26,6 +26,11 @@ import (
"github.com/hajimehoshi/ebiten/internal/restorable"
)
const (
initSize = 1024
maxSize = 4096
)
type backend struct {
restorable *restorable.Image

View File

@ -29,6 +29,7 @@ import (
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/hajimehoshi/ebiten/internal/devicescale"
"github.com/hajimehoshi/ebiten/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/internal/hooks"
"github.com/hajimehoshi/ebiten/internal/input"
"github.com/hajimehoshi/ebiten/internal/mainthread"
@ -90,6 +91,9 @@ func initialize() error {
if err := glfw.Init(); err != nil {
return err
}
if !graphicscommand.Driver().IsGL() {
glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
}
glfw.WindowHint(glfw.Visible, glfw.False)
glfw.WindowHint(glfw.Resizable, glfw.False)
@ -542,7 +546,9 @@ func Run(width, height int, scale float64, title string, g GraphicsContext, main
}
u.window = window
if graphicscommand.Driver().IsGL() {
u.window.MakeContextCurrent()
}
u.window.SetInputMode(glfw.StickyMouseButtonsMode, glfw.True)
u.window.SetInputMode(glfw.StickyKeysMode, glfw.True)
@ -589,6 +595,8 @@ func Run(width, height int, scale float64, title string, g GraphicsContext, main
y := my + (v.Height-h)/3
x, y = adjustWindowPosition(x, y)
u.window.SetPos(x, y)
u.setWindowToDriver()
return nil
})
return u.loop(g)
@ -726,18 +734,7 @@ func (u *userInterface) loop(g GraphicsContext) error {
u.swapBuffers()
return nil
}
n1 := time.Now().UnixNano()
u.swapBuffers()
n2 := time.Now().UnixNano()
d := time.Duration(n2 - n1)
// On macOS Mojave, vsync might not work (#692).
// As a tempoarry fix, just wait for a while not to consume CPU too much.
const threshold = 4 * time.Millisecond // 250 [Hz]
if d < threshold {
time.Sleep(threshold - d)
}
return nil
})
}
@ -745,7 +742,9 @@ func (u *userInterface) loop(g GraphicsContext) error {
// swapBuffers must be called from the main thread.
func (u *userInterface) swapBuffers() {
if graphicscommand.Driver().IsGL() {
u.window.SwapBuffers()
}
}
// setScreenSize must be called from the main thread.
@ -831,6 +830,7 @@ func (u *userInterface) forceSetScreenSize(width, height int, scale float64, ful
u.window.SetTitle(u.title)
}
if graphicscommand.Driver().IsGL() {
// SwapInterval is affected by the current monitor of the window.
// This needs to be called at least after SetMonitor.
// Without SwapInterval after SetMonitor, vsynch doesn't work (#375).
@ -843,6 +843,8 @@ func (u *userInterface) forceSetScreenSize(width, height int, scale float64, ful
} else {
glfw.SwapInterval(0)
}
}
graphicscommand.Driver().SetVsyncEnabled(vsync)
u.toChangeSize = true
}

View File

@ -46,6 +46,8 @@ import (
"unsafe"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/hajimehoshi/ebiten/internal/graphicscommand"
)
func glfwScale() float64 {
@ -70,3 +72,7 @@ func (u *userInterface) currentMonitorImpl() *glfw.Monitor {
}
return glfw.GetPrimaryMonitor()
}
func (u *userInterface) setWindowToDriver() {
graphicscommand.Driver().SetWindow(u.window.GetCocoaWindow())
}

View File

@ -44,3 +44,7 @@ func (u *userInterface) currentMonitorImpl() *glfw.Monitor {
}
return glfw.GetPrimaryMonitor()
}
func (u *userInterface) setWindowToDriver() {
// Do nothing
}

View File

@ -164,3 +164,7 @@ func (u *userInterface) currentMonitorImpl() *glfw.Monitor {
}
return glfw.GetPrimaryMonitor()
}
func (u *userInterface) setWindowToDriver() {
// Do nothing
}