mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
parent
d738639c48
commit
ff62876552
1
AUTHORS
1
AUTHORS
@ -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>
|
||||
|
@ -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{}
|
||||
// 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)
|
||||
|
||||
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))
|
||||
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.
|
||||
|
@ -31,6 +31,8 @@ const (
|
||||
CompositeModeXor
|
||||
CompositeModeLighter
|
||||
CompositeModeUnknown
|
||||
|
||||
CompositeModeMax = CompositeModeLighter
|
||||
)
|
||||
|
||||
type Operation int
|
||||
|
@ -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()
|
||||
}
|
||||
|
27
internal/graphicscommand/driver_mac.go
Normal file
27
internal/graphicscommand/driver_mac.go
Normal 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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
)
|
||||
|
5
internal/graphicsdriver/metal/README.md
Normal file
5
internal/graphicsdriver/metal/README.md
Normal 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`)
|
150
internal/graphicsdriver/metal/ca/ca.go
Normal file
150
internal/graphicsdriver/metal/ca/ca.go
Normal 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))
|
||||
}
|
33
internal/graphicsdriver/metal/ca/ca.h
Normal file
33
internal/graphicsdriver/metal/ca/ca.h
Normal 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);
|
75
internal/graphicsdriver/metal/ca/ca.m
Normal file
75
internal/graphicsdriver/metal/ca/ca.m
Normal 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;
|
||||
}
|
613
internal/graphicsdriver/metal/driver.go
Normal file
613
internal/graphicsdriver/metal/driver.go
Normal 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
|
||||
})
|
||||
}
|
231
internal/graphicsdriver/metal/mtl/example_test.go
Normal file
231
internal/graphicsdriver/metal/mtl/example_test.go
Normal 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:
|
||||
// ████████████████████████████████████████████████████████████████████████████████
|
||||
// ████████████████████████████████████████████████████████████████████████████████
|
||||
// ████████████████████████████████████████████████████████████████████████████████
|
||||
// ██████████████████████████████████████ ██████████████████████████████████████
|
||||
// ████████████████████████████████████ ████████████████████████████████████
|
||||
// ██████████████████████████████████ ░░░░██████████████████████████████████
|
||||
// ████████████████████████████████ ░░░░░░░░████████████████████████████████
|
||||
// ██████████████████████████████ ░░░░░░░░░░░░██████████████████████████████
|
||||
// ████████████████████████████ ░░░░░░░░░░░░▒▒▒▒████████████████████████████
|
||||
// ██████████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒██████████████████████████
|
||||
// ████████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒████████████████████████
|
||||
// ██████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████████████
|
||||
// ████████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████████████████
|
||||
// ██████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓██████████████████
|
||||
// ████████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓████████████████
|
||||
// ██████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓██████████████
|
||||
// ████████████ ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓████████████████
|
||||
// ████████████████████████████████████████████████████████████████████████████████
|
||||
// ████████████████████████████████████████████████████████████████████████████████
|
||||
// ████████████████████████████████████████████████████████████████████████████████
|
||||
}
|
867
internal/graphicsdriver/metal/mtl/mtl.go
Normal file
867
internal/graphicsdriver/metal/mtl/mtl.go
Normal 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),
|
||||
}
|
||||
}
|
175
internal/graphicsdriver/metal/mtl/mtl.h
Normal file
175
internal/graphicsdriver/metal/mtl/mtl.h
Normal 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);
|
318
internal/graphicsdriver/metal/mtl/mtl.m
Normal file
318
internal/graphicsdriver/metal/mtl/mtl.m
Normal 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]; }
|
179
internal/graphicsdriver/metal/mtl/mtl_test.go
Normal file
179
internal/graphicsdriver/metal/mtl/mtl_test.go
Normal 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
|
||||
}
|
76
internal/graphicsdriver/metal/ns/ns.go
Normal file
76
internal/graphicsdriver/metal/ns/ns.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -26,6 +26,11 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/internal/restorable"
|
||||
)
|
||||
|
||||
const (
|
||||
initSize = 1024
|
||||
maxSize = 4096
|
||||
)
|
||||
|
||||
type backend struct {
|
||||
restorable *restorable.Image
|
||||
|
||||
|
@ -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
|
||||
|
||||
u.window.MakeContextCurrent()
|
||||
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() {
|
||||
u.window.SwapBuffers()
|
||||
if graphicscommand.Driver().IsGL() {
|
||||
u.window.SwapBuffers()
|
||||
}
|
||||
}
|
||||
|
||||
// setScreenSize must be called from the main thread.
|
||||
@ -831,18 +830,21 @@ func (u *userInterface) forceSetScreenSize(width, height int, scale float64, ful
|
||||
u.window.SetTitle(u.title)
|
||||
}
|
||||
|
||||
// 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).
|
||||
//
|
||||
// TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called,
|
||||
// but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple
|
||||
// buffering, what will happen?
|
||||
if u.vsync {
|
||||
glfw.SwapInterval(1)
|
||||
} else {
|
||||
glfw.SwapInterval(0)
|
||||
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).
|
||||
//
|
||||
// TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called,
|
||||
// but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple
|
||||
// buffering, what will happen?
|
||||
if u.vsync {
|
||||
glfw.SwapInterval(1)
|
||||
} else {
|
||||
glfw.SwapInterval(0)
|
||||
}
|
||||
}
|
||||
graphicscommand.Driver().SetVsyncEnabled(vsync)
|
||||
|
||||
u.toChangeSize = true
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -44,3 +44,7 @@ func (u *userInterface) currentMonitorImpl() *glfw.Monitor {
|
||||
}
|
||||
return glfw.GetPrimaryMonitor()
|
||||
}
|
||||
|
||||
func (u *userInterface) setWindowToDriver() {
|
||||
// Do nothing
|
||||
}
|
||||
|
@ -164,3 +164,7 @@ func (u *userInterface) currentMonitorImpl() *glfw.Monitor {
|
||||
}
|
||||
return glfw.GetPrimaryMonitor()
|
||||
}
|
||||
|
||||
func (u *userInterface) setWindowToDriver() {
|
||||
// Do nothing
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user