updated ebiten version from 2.7.9 to 2.9.9

This commit is contained in:
2026-06-15 19:06:55 +02:00
parent 21edbc41c4
commit db1b625069
405 changed files with 31913 additions and 12595 deletions
+118 -24
View File
@@ -23,6 +23,8 @@ type Blend struct {
BlendOperationAlpha BlendOperation
}
// BlendFactor and BlendOperation must be synced with internal/graphicsdriver/playstation5/graphics_playstation5.h.
type BlendFactor byte
const (
@@ -49,29 +51,121 @@ const (
BlendOperationMax
)
var BlendSourceOver = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
var (
BlendSourceOver = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
var BlendClear = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendClear = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
var BlendCopy = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendCopy = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestination = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationOver = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceIn = Blend{
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationIn = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceOut = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationOut = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceAtop = Blend{
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationAtop = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendXor = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendLighter = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
)
@@ -16,94 +16,232 @@
package directx
// Some functions of ID3D12GraphicsCommandList has additional logics besides the original COM function call.
// Some functions must be called with C++ directly for some reasons.
// Then, instead of calling them by LazyProc.Call, we have to defer this call to the C++ side.
// These functions are chosen based on the DirectX header file's implementation.
//
// These functions should be defined on the C++ side like this:
//
// extern "C" {
// void Ebitengine_ID3D12GraphicsCommandList_ClearDepthStencilView(void* i, uintptr_t depthStencilView, int32_t clearFlags, float depth, uint8_t stencil, uint32_t numRects, void* pRects) {
// static_cast<ID3D12GraphicsCommandList*>(i)->ClearDepthStencilView(D3D12_CPU_DESCRIPTOR_HANDLE{ depthStencilView }, static_cast<D3D12_CLEAR_FLAGS>(clearFlags), depth, stencil, numRects, static_cast<D3D12_RECT*>(pRects));
// }
// void Ebitengine_ID3D12GraphicsCommandList_ClearRenderTargetView(void* i, uintptr_t pRenderTargetView, void* colorRGBA, uint32_t numRects, void* pRects) {
// static_cast<ID3D12GraphicsCommandList*>(i)->ClearRenderTargetView(D3D12_CPU_DESCRIPTOR_HANDLE{ pRenderTargetView }, static_cast<FLOAT*>(colorRGBA), numRects, static_cast<D3D12_RECT*>(pRects));
// }
// uintptr_t Ebitengine_ID3D12GraphicsCommandList_Close(void* i) {
// auto r = static_cast<ID3D12GraphicsCommandList*>(i)->Close();
// return uintptr_t(r);
// }
// void Ebitengine_ID3D12GraphicsCommandList_CopyTextureRegion(void* i, void* pDst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, void* pSrc, void* pSrcBox) {
// static_cast<ID3D12GraphicsCommandList*>(i)->CopyTextureRegion(static_cast<D3D12_TEXTURE_COPY_LOCATION*>(pDst), dstX, dstY, dstZ, static_cast<D3D12_TEXTURE_COPY_LOCATION*>(pSrc), static_cast<D3D12_BOX*>(pSrcBox));
// }
// void Ebitengine_ID3D12GraphicsCommandList_DrawIndexedInstanced(void* i, uint32_t indexCountPerInstance, uint32_t instanceCount, uint32_t startIndexLocation, int32_t baseVertexLocation, uint32_t startInstanceLocation) {
// static_cast<ID3D12GraphicsCommandList*>(i)->DrawIndexedInstanced(indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation);
// }
// void Ebitengine_ID3D12GraphicsCommandList_IASetIndexBuffer(void* i, void* pView) {
// static_cast<ID3D12GraphicsCommandList*>(i)->IASetIndexBuffer(static_cast<D3D12_INDEX_BUFFER_VIEW*>(pView));
// }
// void Ebitengine_ID3D12GraphicsCommandList_IASetPrimitiveTopology(void* i, int32_t primitiveTopology) {
// static_cast<ID3D12GraphicsCommandList*>(i)->IASetPrimitiveTopology(static_cast<D3D12_PRIMITIVE_TOPOLOGY>(primitiveTopology));
// }
// void Ebitengine_ID3D12GraphicsCommandList_IASetVertexBuffers(void* i, uint32_t startSlot, uint32_t numViews, void* pViews) {
// static_cast<ID3D12GraphicsCommandList*>(i)->IASetVertexBuffers(startSlot, numViews, static_cast<D3D12_VERTEX_BUFFER_VIEW*>(pViews));
// }
// void Ebitengine_ID3D12GraphicsCommandList_OMSetRenderTargets(void* i, uint32_t numRenderTargetDescriptors, void* pRenderTargetDescriptors, int rtsSingleHandleToDescriptorRange, void* pDepthStencilDescriptor) {
// static_cast<ID3D12GraphicsCommandList*>(i)->OMSetRenderTargets(numRenderTargetDescriptors, static_cast<D3D12_CPU_DESCRIPTOR_HANDLE*>(pRenderTargetDescriptors), static_cast<BOOL>(rtsSingleHandleToDescriptorRange), static_cast<D3D12_CPU_DESCRIPTOR_HANDLE*>(pDepthStencilDescriptor));
// }
// void Ebitengine_ID3D12GraphicsCommandList_OMSetStencilRef(void* i, uint32_t stencilRef) {
// static_cast<ID3D12GraphicsCommandList*>(i)->OMSetStencilRef(stencilRef);
// }
// uint32_t Ebitengine_ID3D12GraphicsCommandList_Release(void* i) {
// return static_cast<uint32_t>(static_cast<ID3D12GraphicsCommandList*>(i)->Release());
// }
// uintptr_t Ebitengine_ID3D12GraphicsCommandList_Reset(void* i, void* pAllocator, void* pInitialState) {
// auto r = static_cast<ID3D12GraphicsCommandList*>(i)->Reset(static_cast<ID3D12CommandAllocator*>(pAllocator), static_cast<ID3D12PipelineState*>(pInitialState));
// return static_cast<uintptr_t>(r);
// }
// void Ebitengine_ID3D12GraphicsCommandList_ResourceBarrier(void* i, uint32_t numBarriers, void* pBarriers) {
// static_cast<ID3D12GraphicsCommandList*>(i)->ResourceBarrier(numBarriers, static_cast<D3D12_RESOURCE_BARRIER*>(pBarriers));
// }
// void Ebitengine_ID3D12GraphicsCommandList_RSSetViewports(void* i, uint32_t numViewports, void* pViewports) {
// static_cast<ID3D12GraphicsCommandList*>(i)->RSSetViewports(numViewports, static_cast<D3D12_VIEWPORT*>(pViewports));
// }
// void Ebitengine_ID3D12GraphicsCommandList_RSSetScissorRects(void* i, uint32_t numRects, void* pRects) {
// static_cast<ID3D12GraphicsCommandList*>(i)->RSSetScissorRects(numRects, static_cast<D3D12_RECT*>(pRects));
// }
// void Ebitengine_ID3D12GraphicsCommandList_SetDescriptorHeaps(void* i, uint32_t numDescriptorHeaps, void* ppDescriptorHeaps) {
// static_cast<ID3D12GraphicsCommandList*>(i)->SetDescriptorHeaps(numDescriptorHeaps, static_cast<ID3D12DescriptorHeap**>(ppDescriptorHeaps));
// }
// void Ebitengine_ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable(void* i, uint32_t rootParameterIndex, uint64_t baseDescriptorPtr) {
// static_cast<ID3D12GraphicsCommandList*>(i)->SetGraphicsRootDescriptorTable(rootParameterIndex, D3D12_GPU_DESCRIPTOR_HANDLE{ baseDescriptorPtr });
// }
// void Ebitengine_ID3D12GraphicsCommandList_SetGraphicsRootSignature(void* i, void* pRootSignature) {
// static_cast<ID3D12GraphicsCommandList*>(i)->SetGraphicsRootSignature(static_cast<ID3D12RootSignature*>(pRootSignature));
// }
// void Ebitengine_ID3D12GraphicsCommandList_SetPipelineState(void* i, void* pPipelineState) {
// static_cast<ID3D12GraphicsCommandList*>(i)->SetPipelineState(static_cast<ID3D12PipelineState*>(pPipelineState));
// }
// }
/*
extern "C" {
int32_t Ebitengine_D3D12_RESOURCE_STATE_PRESENT() {
return static_cast<int32_t>(D3D12_RESOURCE_STATE_PRESENT);
}
void Ebitengine_ID3D12CommandQueue_ExecuteCommandLists(void* i, uint32_t numCommandLists, void* ppCommandLists) {
static_cast<ID3D12CommandQueue*>(i)->ExecuteCommandLists(numCommandLists, static_cast<ID3D12CommandList**>(ppCommandLists));
}
uintptr_t Ebitengine_ID3D12CommandQueue_PresentX(void* i, uint32_t planeCount, void* pPlaneParameters, void* pPresentParameters) {
auto r = static_cast<ID3D12CommandQueue*>(i)->PresentX(planeCount, static_cast<D3D12XBOX_PRESENT_PLANE_PARAMETERS*>(pPlaneParameters), static_cast<D3D12XBOX_PRESENT_PARAMETERS*>(pPresentParameters));
return static_cast<uintptr_t>(r);
}
uint32_t Ebitengine_ID3D12CommandQueue_Release(void* i) {
auto r = static_cast<ID3D12CommandQueue*>(i)->Release();
return static_cast<uint32_t>(r);
}
uintptr_t Ebitengine_ID3D12CommandQueue_ResumeX(void* i) {
auto r = static_cast<ID3D12CommandQueue*>(i)->ResumeX();
return static_cast<uintptr_t>(r);
}
uintptr_t Ebitengine_ID3D12CommandQueue_Signal(void* i, void* pFence, uint64_t value) {
auto r = static_cast<ID3D12CommandQueue*>(i)->Signal(static_cast<ID3D12Fence*>(pFence), value);
return static_cast<uintptr_t>(r);
}
uintptr_t Ebitengine_ID3D12CommandQueue_SuspendX(void* i, uint32_t flags) {
auto r = static_cast<ID3D12CommandQueue*>(i)->SuspendX(flags);
return static_cast<uintptr_t>(r);
}
void Ebitengine_ID3D12GraphicsCommandList_ClearDepthStencilView(void* i, uintptr_t depthStencilView, int32_t clearFlags, float depth, uint8_t stencil, uint32_t numRects, void* pRects) {
static_cast<ID3D12GraphicsCommandList*>(i)->ClearDepthStencilView(D3D12_CPU_DESCRIPTOR_HANDLE{ depthStencilView }, static_cast<D3D12_CLEAR_FLAGS>(clearFlags), depth, stencil, numRects, static_cast<D3D12_RECT*>(pRects));
}
void Ebitengine_ID3D12GraphicsCommandList_ClearRenderTargetView(void* i, uintptr_t pRenderTargetView, void* colorRGBA, uint32_t numRects, void* pRects) {
static_cast<ID3D12GraphicsCommandList*>(i)->ClearRenderTargetView(D3D12_CPU_DESCRIPTOR_HANDLE{ pRenderTargetView }, static_cast<FLOAT*>(colorRGBA), numRects, static_cast<D3D12_RECT*>(pRects));
}
uintptr_t Ebitengine_ID3D12GraphicsCommandList_Close(void* i) {
auto r = static_cast<ID3D12GraphicsCommandList*>(i)->Close();
return uintptr_t(r);
}
void Ebitengine_ID3D12GraphicsCommandList_CopyTextureRegion(void* i, void* pDst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, void* pSrc, void* pSrcBox) {
static_cast<ID3D12GraphicsCommandList*>(i)->CopyTextureRegion(static_cast<D3D12_TEXTURE_COPY_LOCATION*>(pDst), dstX, dstY, dstZ, static_cast<D3D12_TEXTURE_COPY_LOCATION*>(pSrc), static_cast<D3D12_BOX*>(pSrcBox));
}
void Ebitengine_ID3D12GraphicsCommandList_DrawIndexedInstanced(void* i, uint32_t indexCountPerInstance, uint32_t instanceCount, uint32_t startIndexLocation, int32_t baseVertexLocation, uint32_t startInstanceLocation) {
static_cast<ID3D12GraphicsCommandList*>(i)->DrawIndexedInstanced(indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation);
}
void Ebitengine_ID3D12GraphicsCommandList_IASetIndexBuffer(void* i, void* pView) {
static_cast<ID3D12GraphicsCommandList*>(i)->IASetIndexBuffer(static_cast<D3D12_INDEX_BUFFER_VIEW*>(pView));
}
void Ebitengine_ID3D12GraphicsCommandList_IASetPrimitiveTopology(void* i, int32_t primitiveTopology) {
static_cast<ID3D12GraphicsCommandList*>(i)->IASetPrimitiveTopology(static_cast<D3D12_PRIMITIVE_TOPOLOGY>(primitiveTopology));
}
void Ebitengine_ID3D12GraphicsCommandList_IASetVertexBuffers(void* i, uint32_t startSlot, uint32_t numViews, void* pViews) {
static_cast<ID3D12GraphicsCommandList*>(i)->IASetVertexBuffers(startSlot, numViews, static_cast<D3D12_VERTEX_BUFFER_VIEW*>(pViews));
}
void Ebitengine_ID3D12GraphicsCommandList_OMSetRenderTargets(void* i, uint32_t numRenderTargetDescriptors, void* pRenderTargetDescriptors, int rtsSingleHandleToDescriptorRange, void* pDepthStencilDescriptor) {
static_cast<ID3D12GraphicsCommandList*>(i)->OMSetRenderTargets(numRenderTargetDescriptors, static_cast<D3D12_CPU_DESCRIPTOR_HANDLE*>(pRenderTargetDescriptors), static_cast<BOOL>(rtsSingleHandleToDescriptorRange), static_cast<D3D12_CPU_DESCRIPTOR_HANDLE*>(pDepthStencilDescriptor));
}
void Ebitengine_ID3D12GraphicsCommandList_OMSetStencilRef(void* i, uint32_t stencilRef) {
static_cast<ID3D12GraphicsCommandList*>(i)->OMSetStencilRef(stencilRef);
}
uint32_t Ebitengine_ID3D12GraphicsCommandList_Release(void* i) {
return static_cast<uint32_t>(static_cast<ID3D12GraphicsCommandList*>(i)->Release());
}
uintptr_t Ebitengine_ID3D12GraphicsCommandList_Reset(void* i, void* pAllocator, void* pInitialState) {
auto r = static_cast<ID3D12GraphicsCommandList*>(i)->Reset(static_cast<ID3D12CommandAllocator*>(pAllocator), static_cast<ID3D12PipelineState*>(pInitialState));
return static_cast<uintptr_t>(r);
}
void Ebitengine_ID3D12GraphicsCommandList_ResourceBarrier(void* i, uint32_t numBarriers, void* pBarriers) {
static_cast<ID3D12GraphicsCommandList*>(i)->ResourceBarrier(numBarriers, static_cast<D3D12_RESOURCE_BARRIER*>(pBarriers));
}
void Ebitengine_ID3D12GraphicsCommandList_RSSetViewports(void* i, uint32_t numViewports, void* pViewports) {
static_cast<ID3D12GraphicsCommandList*>(i)->RSSetViewports(numViewports, static_cast<D3D12_VIEWPORT*>(pViewports));
}
void Ebitengine_ID3D12GraphicsCommandList_RSSetScissorRects(void* i, uint32_t numRects, void* pRects) {
static_cast<ID3D12GraphicsCommandList*>(i)->RSSetScissorRects(numRects, static_cast<D3D12_RECT*>(pRects));
}
void Ebitengine_ID3D12GraphicsCommandList_SetDescriptorHeaps(void* i, uint32_t numDescriptorHeaps, void* ppDescriptorHeaps) {
static_cast<ID3D12GraphicsCommandList*>(i)->SetDescriptorHeaps(numDescriptorHeaps, static_cast<ID3D12DescriptorHeap**>(ppDescriptorHeaps));
}
void Ebitengine_ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable(void* i, uint32_t rootParameterIndex, uint64_t baseDescriptorPtr) {
static_cast<ID3D12GraphicsCommandList*>(i)->SetGraphicsRootDescriptorTable(rootParameterIndex, D3D12_GPU_DESCRIPTOR_HANDLE{ baseDescriptorPtr });
}
void Ebitengine_ID3D12GraphicsCommandList_SetGraphicsRootSignature(void* i, void* pRootSignature) {
static_cast<ID3D12GraphicsCommandList*>(i)->SetGraphicsRootSignature(static_cast<ID3D12RootSignature*>(pRootSignature));
}
void Ebitengine_ID3D12GraphicsCommandList_SetPipelineState(void* i, void* pPipelineState) {
static_cast<ID3D12GraphicsCommandList*>(i)->SetPipelineState(static_cast<ID3D12PipelineState*>(pPipelineState));
}
}
*/
// #include <stdint.h>
//
// #cgo noescape D3D12_RESOURCE_STATE_PRESENT
// #cgo nocallback D3D12_RESOURCE_STATE_PRESENT
// int32_t Ebitengine_D3D12_RESOURCE_STATE_PRESENT();
//
// #cgo noescape ID3D12CommandQueue_ExecuteCommandLists
// #cgo nocallback ID3D12CommandQueue_ExecuteCommandLists
// void Ebitengine_ID3D12CommandQueue_ExecuteCommandLists(void* i, uint32_t numCommandLists, void* ppCommandLists);
//
// #cgo noescape ID3D12CommandQueue_PresentX
// #cgo nocallback ID3D12CommandQueue_PresentX
// uintptr_t Ebitengine_ID3D12CommandQueue_PresentX(void* i, uint32_t planeCount, void* pPlaneParameters, void* pPresentParameters);
//
// #cgo noescape ID3D12CommandQueue_Release
// #cgo nocallback ID3D12CommandQueue_Release
// uint32_t Ebitengine_ID3D12CommandQueue_Release(void* i);
//
// #cgo noescape ID3D12CommandQueue_ResumeX
// #cgo nocallback ID3D12CommandQueue_ResumeX
// uintptr_t Ebitengine_ID3D12CommandQueue_ResumeX(void* i);
//
// #cgo noescape ID3D12CommandQueue_Signal
// #cgo nocallback ID3D12CommandQueue_Signal
// uintptr_t Ebitengine_ID3D12CommandQueue_Signal(void* i, void* pFence, uint64_t value);
//
// #cgo noescape ID3D12CommandQueue_SuspendX
// #cgo nocallback ID3D12CommandQueue_SuspendX
// uintptr_t Ebitengine_ID3D12CommandQueue_SuspendX(void* i, uint32_t flags);
//
// #cgo noescape ID3D12GraphicsCommandList_ClearDepthStencilView
// #cgo nocallback ID3D12GraphicsCommandList_ClearDepthStencilView
// void Ebitengine_ID3D12GraphicsCommandList_ClearDepthStencilView(void* i, uintptr_t depthStencilView, int32_t clearFlags, float depth, uint8_t stencil, uint32_t numRects, void* pRects);
//
// #cgo noescape ID3D12GraphicsCommandList_ClearRenderTargetView
// #cgo nocallback ID3D12GraphicsCommandList_ClearRenderTargetView
// void Ebitengine_ID3D12GraphicsCommandList_ClearRenderTargetView(void* i, uintptr_t pRenderTargetView, void* colorRGBA, uint32_t numRects, void* pRects);
//
// #cgo noescape ID3D12GraphicsCommandList_Close
// #cgo nocallback ID3D12GraphicsCommandList_Close
// uintptr_t Ebitengine_ID3D12GraphicsCommandList_Close(void* i);
//
// #cgo noescape ID3D12GraphicsCommandList_CopyTextureRegion
// #cgo nocallback ID3D12GraphicsCommandList_CopyTextureRegion
// void Ebitengine_ID3D12GraphicsCommandList_CopyTextureRegion(void* i, void* pDst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, void* pSrc, void* pSrcBox);
//
// #cgo noescape ID3D12GraphicsCommandList_DrawIndexedInstanced
// #cgo nocallback ID3D12GraphicsCommandList_DrawIndexedInstanced
// void Ebitengine_ID3D12GraphicsCommandList_DrawIndexedInstanced(void* i, uint32_t indexCountPerInstance, uint32_t instanceCount, uint32_t startIndexLocation, int32_t baseVertexLocation, uint32_t startInstanceLocation);
//
// #cgo noescape ID3D12GraphicsCommandList_IASetIndexBuffer
// #cgo nocallback ID3D12GraphicsCommandList_IASetIndexBuffer
// void Ebitengine_ID3D12GraphicsCommandList_IASetIndexBuffer(void* i, void* pView);
//
// #cgo noescape ID3D12GraphicsCommandList_IASetPrimitiveTopology
// #cgo nocallback ID3D12GraphicsCommandList_IASetPrimitiveTopology
// void Ebitengine_ID3D12GraphicsCommandList_IASetPrimitiveTopology(void* i, int32_t primitiveTopology);
//
// #cgo noescape ID3D12GraphicsCommandList_IASetVertexBuffers
// #cgo nocallback ID3D12GraphicsCommandList_IASetVertexBuffers
// void Ebitengine_ID3D12GraphicsCommandList_IASetVertexBuffers(void* i, uint32_t startSlot, uint32_t numViews, void* pViews);
//
// #cgo noescape ID3D12GraphicsCommandList_OMSetRenderTargets
// #cgo nocallback ID3D12GraphicsCommandList_OMSetRenderTargets
// void Ebitengine_ID3D12GraphicsCommandList_OMSetRenderTargets(void* i, uint32_t numRenderTargetDescriptors, void* pRenderTargetDescriptors, int rtsSingleHandleToDescriptorRange, void* pDepthStencilDescriptor);
//
// #cgo noescape ID3D12GraphicsCommandList_OMSetStencilRef
// #cgo nocallback ID3D12GraphicsCommandList_OMSetStencilRef
// void Ebitengine_ID3D12GraphicsCommandList_OMSetStencilRef(void* i, uint32_t stencilRef);
//
// #cgo noescape ID3D12GraphicsCommandList_Release
// #cgo nocallback ID3D12GraphicsCommandList_Release
// uint32_t Ebitengine_ID3D12GraphicsCommandList_Release(void* i);
//
// #cgo noescape ID3D12GraphicsCommandList_Reset
// #cgo nocallback ID3D12GraphicsCommandList_Reset
// uintptr_t Ebitengine_ID3D12GraphicsCommandList_Reset(void* i, void* pAllocator, void* pInitialState);
//
// #cgo noescape ID3D12GraphicsCommandList_ResourceBarrier
// #cgo nocallback ID3D12GraphicsCommandList_ResourceBarrier
// void Ebitengine_ID3D12GraphicsCommandList_ResourceBarrier(void* i, uint32_t numBarriers, void* pBarriers);
//
// #cgo noescape ID3D12GraphicsCommandList_RSSetViewports
// #cgo nocallback ID3D12GraphicsCommandList_RSSetViewports
// void Ebitengine_ID3D12GraphicsCommandList_RSSetViewports(void* i, uint32_t numViewports, void* pViewports);
//
// #cgo noescape ID3D12GraphicsCommandList_RSSetScissorRects
// #cgo nocallback ID3D12GraphicsCommandList_RSSetScissorRects
// void Ebitengine_ID3D12GraphicsCommandList_RSSetScissorRects(void* i, uint32_t numRects, void* pRects);
//
// #cgo noescape ID3D12GraphicsCommandList_SetDescriptorHeaps
// #cgo nocallback ID3D12GraphicsCommandList_SetDescriptorHeaps
// void Ebitengine_ID3D12GraphicsCommandList_SetDescriptorHeaps(void* i, uint32_t numDescriptorHeaps, void* ppDescriptorHeaps);
//
// #cgo noescape ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable
// #cgo nocallback ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable
// void Ebitengine_ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable(void* i, uint32_t rootParameterIndex, uint64_t baseDescriptorPtr);
//
// #cgo noescape ID3D12GraphicsCommandList_SetGraphicsRootSignature
// #cgo nocallback ID3D12GraphicsCommandList_SetGraphicsRootSignature
// void Ebitengine_ID3D12GraphicsCommandList_SetGraphicsRootSignature(void* i, void* pRootSignature);
//
// #cgo noescape ID3D12GraphicsCommandList_SetPipelineState
// #cgo nocallback ID3D12GraphicsCommandList_SetPipelineState
// void Ebitengine_ID3D12GraphicsCommandList_SetPipelineState(void* i, void* pPipelineState);
import "C"
@@ -111,6 +249,44 @@ import (
"unsafe"
)
func _D3D12_RESOURCE_STATE_PRESENT() _D3D12_RESOURCE_STATES {
// This value depends on the environment.
return _D3D12_RESOURCE_STATES(C.Ebitengine_D3D12_RESOURCE_STATE_PRESENT())
}
func _ID3D12CommandQueue_ExecuteCommandLists(i *_ID3D12CommandQueue, ppCommandLists []*_ID3D12GraphicsCommandList) {
var ppCommandListsPtr **_ID3D12GraphicsCommandList
if len(ppCommandLists) > 0 {
ppCommandListsPtr = &ppCommandLists[0]
}
C.Ebitengine_ID3D12CommandQueue_ExecuteCommandLists(unsafe.Pointer(i), C.uint32_t(len(ppCommandLists)), unsafe.Pointer(ppCommandListsPtr))
}
func _ID3D12CommandQueue_PresentX(i *_ID3D12CommandQueue, planeCount uint32, pPlaneParameters *_D3D12XBOX_PRESENT_PLANE_PARAMETERS, pPresentParameters *_D3D12XBOX_PRESENT_PARAMETERS) uintptr {
r := C.Ebitengine_ID3D12CommandQueue_PresentX(unsafe.Pointer(i), C.uint32_t(planeCount), unsafe.Pointer(pPlaneParameters), unsafe.Pointer(pPresentParameters))
return uintptr(r)
}
func _ID3D12CommandQueue_Release(i *_ID3D12CommandQueue) uint32 {
r := C.Ebitengine_ID3D12CommandQueue_Release(unsafe.Pointer(i))
return uint32(r)
}
func _ID3D12CommandQueue_ResumeX(i *_ID3D12CommandQueue) uintptr {
r := C.Ebitengine_ID3D12CommandQueue_ResumeX(unsafe.Pointer(i))
return uintptr(r)
}
func _ID3D12CommandQueue_Signal(i *_ID3D12CommandQueue, pFence *_ID3D12Fence, value uint64) uintptr {
r := C.Ebitengine_ID3D12CommandQueue_Signal(unsafe.Pointer(i), unsafe.Pointer(pFence), C.uint64_t(value))
return uintptr(r)
}
func _ID3D12CommandQueue_SuspendX(i *_ID3D12CommandQueue, flags uint32) uintptr {
r := C.Ebitengine_ID3D12CommandQueue_SuspendX(unsafe.Pointer(i), C.uint32_t(flags))
return uintptr(r)
}
func _ID3D12GraphicsCommandList_ClearDepthStencilView(i *_ID3D12GraphicsCommandList, depthStencilView _D3D12_CPU_DESCRIPTOR_HANDLE, clearFlags _D3D12_CLEAR_FLAGS, depth float32, stencil uint8, rects []_D3D12_RECT) {
var pRects *_D3D12_RECT
if len(rects) > 0 {
@@ -20,6 +20,34 @@ import (
"unsafe"
)
func _D3D12_RESOURCE_STATE_PRESENT() _D3D12_RESOURCE_STATES {
return 0
}
func _ID3D12CommandQueue_ExecuteCommandLists(i *_ID3D12CommandQueue, ppCommandLists []*_ID3D12GraphicsCommandList) {
panic("not implemented")
}
func _ID3D12CommandQueue_PresentX(i *_ID3D12CommandQueue, planeCount uint32, pPlaneParameters *_D3D12XBOX_PRESENT_PLANE_PARAMETERS, pPresentParameters *_D3D12XBOX_PRESENT_PARAMETERS) uintptr {
panic("not implemented")
}
func _ID3D12CommandQueue_Release(i *_ID3D12CommandQueue) uint32 {
panic("not implemented")
}
func _ID3D12CommandQueue_ResumeX(i *_ID3D12CommandQueue) uintptr {
panic("not implemented")
}
func _ID3D12CommandQueue_Signal(i *_ID3D12CommandQueue, pFence *_ID3D12Fence, value uint64) uintptr {
panic("not implemented")
}
func _ID3D12CommandQueue_SuspendX(i *_ID3D12CommandQueue, flags uint32) uintptr {
panic("not implemented")
}
func _ID3D12GraphicsCommandList_ClearDepthStencilView(i *_ID3D12GraphicsCommandList, depthStencilView _D3D12_CPU_DESCRIPTOR_HANDLE, clearFlags _D3D12_CLEAR_FLAGS, depth float32, stencil uint8, rects []_D3D12_RECT) {
panic("not implemented")
}
@@ -343,7 +343,6 @@ const (
_D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT _D3D12_RESOURCE_STATES = 0x200
_D3D12_RESOURCE_STATE_COPY_DEST _D3D12_RESOURCE_STATES = 0x400
_D3D12_RESOURCE_STATE_COPY_SOURCE _D3D12_RESOURCE_STATES = 0x800
_D3D12_RESOURCE_STATE_PRESENT _D3D12_RESOURCE_STATES = 0
)
func _D3D12_RESOURCE_STATE_GENERIC_READ() _D3D12_RESOURCE_STATES {
@@ -1066,33 +1065,24 @@ type _ID3D12CommandQueue_Vtbl struct {
Wait uintptr
GetTimestampFrequency uintptr
GetClockCalibration uintptr
GetDesc uintptr // Is this another function for Xbox?
// These members are for Xbox.
_ uintptr
_ uintptr
SuspendX uintptr
ResumeX uintptr
_ uintptr
_ uintptr
_ uintptr
_ uintptr // Is this GetDesc for Xbox?
_ uintptr
_ uintptr
_ uintptr
PresentX uintptr
_ uintptr
_ uintptr
GetDesc uintptr
}
func (i *_ID3D12CommandQueue) ExecuteCommandLists(ppCommandLists []*_ID3D12GraphicsCommandList) {
_, _, _ = syscall.Syscall(i.vtbl.ExecuteCommandLists, 3, uintptr(unsafe.Pointer(i)),
uintptr(len(ppCommandLists)), uintptr(unsafe.Pointer(&ppCommandLists[0])))
if microsoftgdk.IsXbox() {
_ID3D12CommandQueue_ExecuteCommandLists(i, ppCommandLists)
} else {
_, _, _ = syscall.Syscall(i.vtbl.ExecuteCommandLists, 3, uintptr(unsafe.Pointer(i)),
uintptr(len(ppCommandLists)), uintptr(unsafe.Pointer(&ppCommandLists[0])))
}
runtime.KeepAlive(ppCommandLists)
}
func (i *_ID3D12CommandQueue) PresentX(planeCount uint32, pPlaneParameters *_D3D12XBOX_PRESENT_PLANE_PARAMETERS, pPresentParameters *_D3D12XBOX_PRESENT_PARAMETERS) error {
r, _, _ := syscall.Syscall6(i.vtbl.PresentX, 4, uintptr(unsafe.Pointer(i)), uintptr(planeCount), uintptr(unsafe.Pointer(pPlaneParameters)), uintptr(unsafe.Pointer(pPresentParameters)), 0, 0)
if !microsoftgdk.IsXbox() {
panic("directx: ID3D12CommandQueue::PresentX is only available on Xbox")
}
r := _ID3D12CommandQueue_PresentX(i, planeCount, pPlaneParameters, pPresentParameters)
runtime.KeepAlive(pPlaneParameters)
runtime.KeepAlive(pPresentParameters)
if uint32(r) != uint32(windows.S_OK) {
@@ -1102,12 +1092,18 @@ func (i *_ID3D12CommandQueue) PresentX(planeCount uint32, pPlaneParameters *_D3D
}
func (i *_ID3D12CommandQueue) Release() uint32 {
if microsoftgdk.IsXbox() {
return _ID3D12CommandQueue_Release(i)
}
r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0)
return uint32(r)
}
func (i *_ID3D12CommandQueue) ResumeX() error {
if r, _, _ := syscall.Syscall(i.vtbl.ResumeX, 1, uintptr(unsafe.Pointer(i)), 0, 0); uint32(r) != uint32(windows.S_OK) {
if !microsoftgdk.IsXbox() {
panic("directx: ID3D12CommandQueue::ResumeX is only available on Xbox")
}
if r := _ID3D12CommandQueue_ResumeX(i); uint32(r) != uint32(windows.S_OK) {
return fmt.Errorf("directx: ID3D12CommandQueue::ResumeX failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
@@ -1115,7 +1111,9 @@ func (i *_ID3D12CommandQueue) ResumeX() error {
func (i *_ID3D12CommandQueue) Signal(signal *_ID3D12Fence, value uint64) error {
var r uintptr
if is64bit {
if microsoftgdk.IsXbox() {
r = _ID3D12CommandQueue_Signal(i, signal, value)
} else if is64bit {
r, _, _ = syscall.Syscall(i.vtbl.Signal, 3, uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(signal)), uintptr(value))
} else {
@@ -1130,7 +1128,10 @@ func (i *_ID3D12CommandQueue) Signal(signal *_ID3D12Fence, value uint64) error {
}
func (i *_ID3D12CommandQueue) SuspendX(flags uint32) error {
if r, _, _ := syscall.Syscall(i.vtbl.SuspendX, 2, uintptr(unsafe.Pointer(i)), uintptr(flags), 0); uint32(r) != uint32(windows.S_OK) {
if !microsoftgdk.IsXbox() {
panic("directx: ID3D12CommandQueue::SuspendX is only available on Xbox")
}
if r := _ID3D12CommandQueue_SuspendX(i, flags); uint32(r) != uint32(windows.S_OK) {
return fmt.Errorf("directx: ID3D12CommandQueue::SuspendX failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
@@ -71,7 +71,8 @@ const (
)
var (
procD3DCompile *windows.LazyProc
procD3DCompile *windows.LazyProc
procD3DCreateBlob *windows.LazyProc
)
func init() {
@@ -93,6 +94,7 @@ func init() {
}
procD3DCompile = d3dcompiler.NewProc("D3DCompile")
procD3DCreateBlob = d3dcompiler.NewProc("D3DCreateBlob")
}
func isD3DCompilerDLLAvailable() bool {
@@ -135,6 +137,19 @@ func _D3DCompile(srcData []byte, sourceName string, pDefines []_D3D_SHADER_MACRO
return code, nil
}
func _D3DCreateBlob(size uint) (*_ID3DBlob, error) {
if !isD3DCompilerDLLAvailable() {
return nil, fmt.Errorf("directx: d3dcompiler_*.dll is missing in this environment")
}
var blob *_ID3DBlob
r, _, _ := procD3DCreateBlob.Call(uintptr(size), uintptr(unsafe.Pointer(&blob)))
if uint32(r) != uint32(windows.S_OK) {
return nil, fmt.Errorf("directx: D3DCreateBlob failed: %w", handleError(windows.Handle(uint32(r))))
}
return blob, nil
}
type _D3D_SHADER_MACRO struct {
Name *byte
Definition *byte
@@ -27,34 +27,56 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl"
)
var inputElementDescsForDX11 = []_D3D11_INPUT_ELEMENT_DESC{
{
SemanticName: &([]byte("POSITION\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("TEXCOORD\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("COLOR\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32B32A32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
var inputElementDescsForDX11 []_D3D11_INPUT_ELEMENT_DESC
func init() {
inputElementDescsForDX11 = []_D3D11_INPUT_ELEMENT_DESC{
{
SemanticName: &([]byte("POSITION\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("TEXCOORD\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("COLOR\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32B32A32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
}
diff := graphics.VertexFloatCount - 8
if diff == 0 {
return
}
if diff%4 != 0 {
panic("directx: unexpected attribute layout")
}
for i := 0; i < diff/4; i++ {
inputElementDescsForDX11 = append(inputElementDescsForDX11, _D3D11_INPUT_ELEMENT_DESC{
SemanticName: &([]byte("COLOR\000"))[0],
SemanticIndex: uint32(i) + 1,
Format: _DXGI_FORMAT_R32G32B32A32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D11_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
})
}
}
func blendFactorToBlend11(f graphicsdriver.BlendFactor, alpha bool) _D3D11_BLEND {
@@ -482,8 +504,7 @@ func (g *graphics11) MaxImageSize() int {
}
func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
vs, ps, offsets := hlsl.Compile(program)
vsh, psh, err := compileShader(vs, ps)
vsh, psh, err := compileShader(program)
if err != nil {
return nil, err
}
@@ -492,7 +513,7 @@ func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader
graphics: g,
id: g.genNextShaderID(),
uniformTypes: program.Uniforms,
uniformOffsets: offsets,
uniformOffsets: hlsl.UniformVariableOffsetsInDwords(program),
vertexShaderBlob: vsh,
pixelShaderBlob: psh,
}
@@ -515,14 +536,14 @@ func (g *graphics11) removeShader(s *shader11) {
delete(g.shaders, s.id)
}
func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
// Remove bound textures first. This is needed to avoid warnings on the debugger.
g.deviceContext.OMSetRenderTargets([]*_ID3D11RenderTargetView{nil}, nil)
srvs := [graphics.ShaderImageCount]*_ID3D11ShaderResourceView{}
srvs := [graphics.ShaderSrcImageCount]*_ID3D11ShaderResourceView{}
g.deviceContext.PSSetShaderResources(0, srvs[:])
dst := g.images[dstID]
var srcs [graphics.ShaderImageCount]*image11
var srcs [graphics.ShaderSrcImageCount]*image11
for i, id := range srcIDs {
img := g.images[id]
if img == nil {
@@ -543,7 +564,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
},
})
if err := dst.setAsRenderTarget(fillRule != graphicsdriver.FillAll); err != nil {
if err := dst.setAsRenderTarget(fillRule != graphicsdriver.FillRuleFillAll); err != nil {
return err
}
@@ -553,7 +574,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
return err
}
if fillRule == graphicsdriver.FillAll {
if fillRule == graphicsdriver.FillRuleFillAll {
bs, err := g.blendState(blend, noStencil)
if err != nil {
return err
@@ -578,9 +599,9 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
})
switch fillRule {
case graphicsdriver.FillAll:
case graphicsdriver.FillRuleFillAll:
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
bs, err := g.blendState(blend, incrementStencil)
if err != nil {
return err
@@ -592,7 +613,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
}
g.deviceContext.OMSetDepthStencilState(dss, 0)
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
bs, err := g.blendState(blend, invertStencil)
if err != nil {
return err
@@ -606,7 +627,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
bs, err := g.blendState(blend, drawWithStencil)
if err != nil {
return err
@@ -17,6 +17,7 @@ package directx
import (
"errors"
"fmt"
"runtime"
"unsafe"
"golang.org/x/sys/windows"
@@ -48,7 +49,7 @@ type graphics12 struct {
renderTargets [frameCount]*_ID3D12Resource
framePipelineToken _D3D12XBOX_FRAME_PIPELINE_TOKEN
fence *_ID3D12Fence
fences [frameCount]*_ID3D12Fence
fenceValues [frameCount]uint64
fenceWaitEvent windows.Handle
@@ -93,6 +94,7 @@ type graphics12 struct {
shaders map[graphicsdriver.ShaderID]*shader12
nextShaderID graphicsdriver.ShaderID
disposedShaders [frameCount][]*shader12
tmpUniforms []uint32
vsyncEnabled bool
@@ -208,7 +210,7 @@ func (g *graphics12) initializeDesktop(useWARP bool, useDebugLayer bool, feature
}
g.device = (*_ID3D12Device)(d)
if err := g.initializeMembers(g.frameIndex); err != nil {
if err := g.initializeMembers(); err != nil {
return err
}
@@ -224,8 +226,6 @@ func (g *graphics12) initializeDesktop(useWARP bool, useDebugLayer bool, feature
}
func (g *graphics12) initializeXbox(useWARP bool, useDebugLayer bool) (ferr error) {
g = &graphics12{}
if err := d3d12x.Load(); err != nil {
return err
}
@@ -245,7 +245,7 @@ func (g *graphics12) initializeXbox(useWARP bool, useDebugLayer bool) (ferr erro
}
g.device = (*_ID3D12Device)(d)
if err := g.initializeMembers(g.frameIndex); err != nil {
if err := g.initializeMembers(); err != nil {
return err
}
@@ -302,7 +302,7 @@ func (g *graphics12) registerFrameEventForXbox() error {
return nil
}
func (g *graphics12) initializeMembers(frameIndex int) (ferr error) {
func (g *graphics12) initializeMembers() (ferr error) {
// Create an event for a fence.
e, err := windows.CreateEventEx(nil, nil, 0, windows.EVENT_MODIFY_STATE|windows.SYNCHRONIZE)
if err != nil {
@@ -355,18 +355,19 @@ func (g *graphics12) initializeMembers(frameIndex int) (ferr error) {
}
// Create a frame fence.
f, err := g.device.CreateFence(0, _D3D12_FENCE_FLAG_NONE)
if err != nil {
return err
}
g.fence = f
defer func() {
if ferr != nil {
g.fence.Release()
g.fence = nil
for i := range frameCount {
f, err := g.device.CreateFence(0, _D3D12_FENCE_FLAG_NONE)
if err != nil {
return err
}
}()
g.fenceValues[frameIndex]++
g.fences[i] = f
defer func() {
if ferr != nil {
g.fences[i].Release()
g.fences[i] = nil
}
}()
}
// Create command lists.
dcl, err := g.device.CreateCommandList(0, _D3D12_COMMAND_LIST_TYPE_DIRECT, g.drawCommandAllocators[0], nil)
@@ -537,7 +538,7 @@ func (g *graphics12) initSwapChainXbox(width, height int) (ferr error) {
},
Layout: _D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: _D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
}, _D3D12_RESOURCE_STATE_PRESENT, &_D3D12_CLEAR_VALUE{
}, _D3D12_RESOURCE_STATE_PRESENT(), &_D3D12_CLEAR_VALUE{
Format: _DXGI_FORMAT_B8G8R8A8_UNORM,
})
if err != nil {
@@ -569,10 +570,6 @@ func (g *graphics12) resizeSwapChainDesktop(width, height int) error {
}
g.releaseResources(g.frameIndex)
for i := 0; i < frameCount; i++ {
g.fenceValues[i] = g.fenceValues[g.frameIndex]
}
for _, r := range g.renderTargets {
r.Release()
}
@@ -676,7 +673,7 @@ func (g *graphics12) End(present bool) error {
// screenImage can be nil in tests.
if present && g.screenImage != nil {
if rb, ok := g.screenImage.transiteState(_D3D12_RESOURCE_STATE_PRESENT); ok {
if rb, ok := g.screenImage.transiteState(_D3D12_RESOURCE_STATE_PRESENT()); ok {
g.drawCommandList.ResourceBarrier([]_D3D12_RESOURCE_BARRIER_Transition{rb})
}
}
@@ -738,6 +735,9 @@ func (g *graphics12) presentDesktop() error {
}
func (g *graphics12) presentXbox() error {
var pinner runtime.Pinner
pinner.Pin(&g.renderTargets[g.frameIndex])
defer pinner.Unpin()
return g.commandQueue.PresentX(1, &_D3D12XBOX_PRESENT_PLANE_PARAMETERS{
Token: g.framePipelineToken,
ResourceCount: 1,
@@ -746,8 +746,9 @@ func (g *graphics12) presentXbox() error {
}
func (g *graphics12) moveToNextFrame() error {
g.fenceValues[g.frameIndex]++
fv := g.fenceValues[g.frameIndex]
if err := g.commandQueue.Signal(g.fence, fv); err != nil {
if err := g.commandQueue.Signal(g.fences[g.frameIndex], fv); err != nil {
return err
}
@@ -762,15 +763,14 @@ func (g *graphics12) moveToNextFrame() error {
g.frameIndex = idx
}
if g.fence.GetCompletedValue() < g.fenceValues[g.frameIndex] {
if err := g.fence.SetEventOnCompletion(g.fenceValues[g.frameIndex], g.fenceWaitEvent); err != nil {
if g.fences[g.frameIndex].GetCompletedValue() < g.fenceValues[g.frameIndex] {
if err := g.fences[g.frameIndex].SetEventOnCompletion(g.fenceValues[g.frameIndex], g.fenceWaitEvent); err != nil {
return err
}
if _, err := windows.WaitForSingleObject(g.fenceWaitEvent, windows.INFINITE); err != nil {
return err
}
}
g.fenceValues[g.frameIndex] = fv + 1
return nil
}
@@ -858,17 +858,17 @@ func (g *graphics12) flushCommandList(commandList *_ID3D12GraphicsCommandList) e
}
func (g *graphics12) waitForCommandQueue() error {
g.fenceValues[g.frameIndex]++
fv := g.fenceValues[g.frameIndex]
if err := g.commandQueue.Signal(g.fence, fv); err != nil {
if err := g.commandQueue.Signal(g.fences[g.frameIndex], fv); err != nil {
return err
}
if err := g.fence.SetEventOnCompletion(fv, g.fenceWaitEvent); err != nil {
if err := g.fences[g.frameIndex].SetEventOnCompletion(fv, g.fenceWaitEvent); err != nil {
return err
}
if _, err := windows.WaitForSingleObject(g.fenceWaitEvent, windows.INFINITE); err != nil {
return err
}
g.fenceValues[g.frameIndex]++
return nil
}
@@ -1064,8 +1064,7 @@ func (g *graphics12) MaxImageSize() int {
}
func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
vs, ps, offsets := hlsl.Compile(program)
vsh, psh, err := compileShader(vs, ps)
vsh, psh, err := compileShader(program)
if err != nil {
return nil, err
}
@@ -1074,7 +1073,7 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader
graphics: g,
id: g.genNextShaderID(),
uniformTypes: program.Uniforms,
uniformOffsets: offsets,
uniformOffsets: hlsl.UniformVariableOffsetsInDwords(program),
vertexShader: vsh,
pixelShader: psh,
}
@@ -1082,7 +1081,7 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader
return s, nil
}
func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("directx: shader ID is invalid")
}
@@ -1093,7 +1092,7 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
// Release constant buffers when too many ones will be created.
numPipelines := 1
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
numPipelines = 2
}
if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame {
@@ -1109,7 +1108,7 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
resourceBarriers = append(resourceBarriers, rb)
}
var srcImages [graphics.ShaderImageCount]*image12
var srcImages [graphics.ShaderSrcImageCount]*image12
for i, srcID := range srcs {
src := g.images[srcID]
if src == nil {
@@ -1125,12 +1124,12 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
g.drawCommandList.ResourceBarrier(resourceBarriers)
}
if err := dst.setAsRenderTarget(g.drawCommandList, g.device, fillRule != graphicsdriver.FillAll); err != nil {
if err := dst.setAsRenderTarget(g.drawCommandList, g.device, fillRule != graphicsdriver.FillRuleFillAll); err != nil {
return err
}
shader := g.shaders[shaderID]
adjustedUniforms := adjustUniforms(shader.uniformTypes, shader.uniformOffsets, uniforms)
g.tmpUniforms = appendAdjustedUniforms(g.tmpUniforms[:0], shader.uniformTypes, shader.uniformOffsets, uniforms)
w, h := dst.internalSize()
g.needFlushDrawCommandList = true
@@ -1158,7 +1157,7 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
Format: _DXGI_FORMAT_R32_UINT,
})
if err := g.pipelineStates.drawTriangles(g.device, g.drawCommandList, g.frameIndex, dst.screen, srcImages, shader, dstRegions, adjustedUniforms, blend, indexOffset, fillRule); err != nil {
if err := g.pipelineStates.drawTriangles(g.device, g.drawCommandList, g.frameIndex, dst.screen, srcImages, shader, dstRegions, g.tmpUniforms, blend, indexOffset, fillRule); err != nil {
return err
}
@@ -147,9 +147,7 @@ func NewGraphics() (graphicsdriver.Graphics, error) {
}
type graphicsInfra struct {
factory *_IDXGIFactory
swapChain *_IDXGISwapChain
swapChain4 *_IDXGISwapChain4
*graphicsInfraResources
allowTearing bool
@@ -160,14 +158,24 @@ type graphicsInfra struct {
lastTime time.Time
bufferCount int
cleanup runtime.Cleanup
}
type graphicsInfraResources struct {
factory *_IDXGIFactory
swapChain *_IDXGISwapChain
swapChain4 *_IDXGISwapChain4
}
// newGraphicsInfra takes the ownership of the given factory.
func newGraphicsInfra(factory *_IDXGIFactory) (*graphicsInfra, error) {
g := &graphicsInfra{
factory: factory,
graphicsInfraResources: &graphicsInfraResources{
factory: factory,
},
}
runtime.SetFinalizer(g, (*graphicsInfra).release)
g.cleanup = runtime.AddCleanup(g, (*graphicsInfraResources).releaseResources, g.graphicsInfraResources)
if f, err := g.factory.QueryInterface(&_IID_IDXGIFactory5); err == nil && f != nil {
factory := (*_IDXGIFactory5)(f)
@@ -183,6 +191,11 @@ func newGraphicsInfra(factory *_IDXGIFactory) (*graphicsInfra, error) {
}
func (g *graphicsInfra) release() {
g.releaseResources()
g.cleanup.Stop()
}
func (g *graphicsInfraResources) releaseResources() {
if g.factory != nil {
g.factory.Release()
g.factory = nil
@@ -23,34 +23,56 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)
var inputElementDescsForDX12 = []_D3D12_INPUT_ELEMENT_DESC{
{
SemanticName: &([]byte("POSITION\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("TEXCOORD\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("COLOR\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32B32A32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
var inputElementDescsForDX12 []_D3D12_INPUT_ELEMENT_DESC
func init() {
inputElementDescsForDX12 = []_D3D12_INPUT_ELEMENT_DESC{
{
SemanticName: &([]byte("POSITION\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("TEXCOORD\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
{
SemanticName: &([]byte("COLOR\000"))[0],
SemanticIndex: 0,
Format: _DXGI_FORMAT_R32G32B32A32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
}
diff := graphics.VertexFloatCount - 8
if diff == 0 {
return
}
if diff%4 != 0 {
panic("directx: unexpected attribute layout")
}
for i := 0; i < diff/4; i++ {
inputElementDescsForDX12 = append(inputElementDescsForDX12, _D3D12_INPUT_ELEMENT_DESC{
SemanticName: &([]byte("COLOR\000"))[0],
SemanticIndex: uint32(i) + 1,
Format: _DXGI_FORMAT_R32G32B32A32_FLOAT,
InputSlot: 0,
AlignedByteOffset: _D3D12_APPEND_ALIGNED_ELEMENT,
InputSlotClass: _D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
})
}
}
const numDescriptorsPerFrame = 32
@@ -128,7 +150,7 @@ type pipelineStates struct {
constantBufferMaps [frameCount][]uintptr
}
const numConstantBufferAndSourceTextures = 1 + graphics.ShaderImageCount
const numConstantBufferAndSourceTextures = 1 + graphics.ShaderSrcImageCount
func (p *pipelineStates) initialize(device *_ID3D12Device) (ferr error) {
// Create a CBV/SRV/UAV descriptor heap.
@@ -180,7 +202,7 @@ func (p *pipelineStates) initialize(device *_ID3D12Device) (ferr error) {
return nil
}
func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D12GraphicsCommandList, frameIndex int, screen bool, srcs [graphics.ShaderImageCount]*image12, shader *shader12, dstRegions []graphicsdriver.DstRegion, uniforms []uint32, blend graphicsdriver.Blend, indexOffset int, fillRule graphicsdriver.FillRule) error {
func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D12GraphicsCommandList, frameIndex int, screen bool, srcs [graphics.ShaderSrcImageCount]*image12, shader *shader12, dstRegions []graphicsdriver.DstRegion, uniforms []uint32, blend graphicsdriver.Blend, indexOffset int, fillRule graphicsdriver.FillRule) error {
idx := len(p.constantBuffers[frameIndex])
if idx >= numDescriptorsPerFrame {
return fmt.Errorf("directx: too many constant buffers")
@@ -289,7 +311,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
}
commandList.SetGraphicsRootDescriptorTable(2, sh)
if fillRule == graphicsdriver.FillAll {
if fillRule == graphicsdriver.FillRuleFillAll {
s, err := shader.pipelineState(blend, noStencil, screen)
if err != nil {
return err
@@ -307,16 +329,16 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
},
})
switch fillRule {
case graphicsdriver.FillAll:
case graphicsdriver.FillRuleFillAll:
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
s, err := shader.pipelineState(blend, incrementStencil, screen)
if err != nil {
return err
}
commandList.SetPipelineState(s)
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
s, err := shader.pipelineState(blend, invertStencil, screen)
if err != nil {
return err
@@ -325,7 +347,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
s, err := shader.pipelineState(blend, drawWithStencil, screen)
if err != nil {
return err
@@ -354,7 +376,7 @@ func (p *pipelineStates) ensureRootSignature(device *_ID3D12Device) (rootSignatu
}
srv := _D3D12_DESCRIPTOR_RANGE{
RangeType: _D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // t0
NumDescriptors: graphics.ShaderImageCount,
NumDescriptors: graphics.ShaderSrcImageCount,
BaseShaderRegister: 0,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: 1,
@@ -29,6 +29,7 @@ type shader11 struct {
uniformOffsets []int
vertexShaderBlob *_ID3DBlob
pixelShaderBlob *_ID3DBlob
tmpUniforms []uint32
inputLayout *_ID3D11InputLayout
vertexShader *_ID3D11VertexShader
@@ -78,7 +79,7 @@ func (s *shader11) disposeImpl() {
}
}
func (s *shader11) use(uniforms []uint32, srcs [graphics.ShaderImageCount]*image11) error {
func (s *shader11) use(uniforms []uint32, srcs [graphics.ShaderSrcImageCount]*image11) error {
vs, err := s.ensureVertexShader()
if err != nil {
return err
@@ -105,16 +106,16 @@ func (s *shader11) use(uniforms []uint32, srcs [graphics.ShaderImageCount]*image
s.graphics.deviceContext.PSSetConstantBuffers(0, []*_ID3D11Buffer{cb})
// Send the constant buffer data.
uniforms = adjustUniforms(s.uniformTypes, s.uniformOffsets, uniforms)
s.tmpUniforms = appendAdjustedUniforms(s.tmpUniforms[:0], s.uniformTypes, s.uniformOffsets, uniforms)
var mapped _D3D11_MAPPED_SUBRESOURCE
if err := s.graphics.deviceContext.Map(unsafe.Pointer(cb), 0, _D3D11_MAP_WRITE_DISCARD, 0, &mapped); err != nil {
return err
}
copy(unsafe.Slice((*uint32)(mapped.pData), len(uniforms)), uniforms)
copy(unsafe.Slice((*uint32)(mapped.pData), len(s.tmpUniforms)), s.tmpUniforms)
s.graphics.deviceContext.Unmap(unsafe.Pointer(cb), 0)
// Set the render sources.
var srvs [graphics.ShaderImageCount]*_ID3D11ShaderResourceView
var srvs [graphics.ShaderSrcImageCount]*_ID3D11ShaderResourceView
for i, src := range srcs {
if src == nil {
continue
@@ -16,18 +16,67 @@ package directx
import (
"fmt"
"sync"
"unsafe"
"golang.org/x/sync/errgroup"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl"
)
const (
VertexShaderProfile = "vs_4_0"
PixelShaderProfile = "ps_4_0"
VertexShaderEntryPoint = "VSMain"
PixelShaderEntryPoint = "PSMain"
)
type fxcPair struct {
vertex []byte
pixel []byte
}
type precompiledFXCs struct {
binaries map[shaderir.SourceHash]fxcPair
m sync.Mutex
}
func (c *precompiledFXCs) put(hash shaderir.SourceHash, vertex, pixel []byte) {
c.m.Lock()
defer c.m.Unlock()
if c.binaries == nil {
c.binaries = map[shaderir.SourceHash]fxcPair{}
}
if _, ok := c.binaries[hash]; ok {
panic(fmt.Sprintf("directx: the precompiled library for the hash %s is already registered", hash.String()))
}
c.binaries[hash] = fxcPair{
vertex: vertex,
pixel: pixel,
}
}
func (c *precompiledFXCs) get(hash shaderir.SourceHash) ([]byte, []byte) {
c.m.Lock()
defer c.m.Unlock()
f := c.binaries[hash]
return f.vertex, f.pixel
}
var thePrecompiledFXCs precompiledFXCs
func RegisterPrecompiledFXCs(source []byte, vertex, pixel []byte) {
thePrecompiledFXCs.put(shaderir.CalcSourceHash(source), vertex, pixel)
}
var vertexShaderCache = map[string]*_ID3DBlob{}
func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3)
func compileShader(program *shaderir.Program) (vsh, psh *_ID3DBlob, ferr error) {
defer func() {
if ferr == nil {
return
@@ -40,6 +89,22 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
}
}()
if vshBin, pshBin := thePrecompiledFXCs.get(program.SourceHash); vshBin != nil && pshBin != nil {
var err error
if vsh, err = _D3DCreateBlob(uint(len(vshBin))); err != nil {
return nil, nil, err
}
if psh, err = _D3DCreateBlob(uint(len(pshBin))); err != nil {
return nil, nil, err
}
copy(unsafe.Slice((*byte)(vsh.GetBufferPointer()), vsh.GetBufferSize()), vshBin)
copy(unsafe.Slice((*byte)(psh.GetBufferPointer()), psh.GetBufferSize()), pshBin)
return vsh, psh, nil
}
vs, ps, _, _ := hlsl.Compile(program)
var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3)
var wg errgroup.Group
// Vertex shaders are likely the same. If so, reuse the same _ID3DBlob.
@@ -56,7 +121,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
}
}()
wg.Go(func() error {
v, err := _D3DCompile([]byte(vs), "shader", nil, nil, "VSMain", "vs_4_0", flag, 0)
v, err := _D3DCompile([]byte(vs), "shader", nil, nil, VertexShaderEntryPoint, VertexShaderProfile, flag, 0)
if err != nil {
return fmt.Errorf("directx: D3DCompile for VSMain failed, original source: %s, %w", vs, err)
}
@@ -65,7 +130,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
})
}
wg.Go(func() error {
p, err := _D3DCompile([]byte(ps), "shader", nil, nil, "PSMain", "ps_4_0", flag, 0)
p, err := _D3DCompile([]byte(ps), "shader", nil, nil, PixelShaderEntryPoint, PixelShaderProfile, flag, 0)
if err != nil {
return fmt.Errorf("directx: D3DCompile for PSMain failed, original source: %s, %w", ps, err)
}
@@ -77,17 +142,20 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
return nil, nil, err
}
return
return vsh, psh, nil
}
func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int {
var size int
for i, typ := range uniformTypes {
if size < uniformOffsets[i]/4 {
size = uniformOffsets[i] / 4
if size < uniformOffsets[i] {
size = uniformOffsets[i]
}
switch typ.Main {
case shaderir.Bool:
// Bool is 4 bytes in HLSL.
size += 1
case shaderir.Float:
size += 1
case shaderir.Int:
@@ -107,6 +175,8 @@ func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int
case shaderir.Array:
// Each element is aligned to the boundary.
switch typ.Sub[0].Main {
case shaderir.Bool:
size += 4*(typ.Length-1) + 1
case shaderir.Float:
size += 4*(typ.Length-1) + 1
case shaderir.Int:
@@ -133,33 +203,39 @@ func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int
return size
}
func adjustUniforms(uniformTypes []shaderir.Type, uniformOffsets []int, uniforms []uint32) []uint32 {
var fs []uint32
func appendAdjustedUniforms(dst []uint32, uniformTypes []shaderir.Type, uniformOffsets []int, uniforms []uint32) []uint32 {
// Note that HLSL's matrices are row-major, while GLSL and MSL are column-major.
// Transpose matrices so that users can access matrix indices in the same way as GLSL and MSL.
// For packing rule, see https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing
var idx int
for i, typ := range uniformTypes {
if len(fs) < uniformOffsets[i]/4 {
fs = append(fs, make([]uint32, uniformOffsets[i]/4-len(fs))...)
if len(dst) < uniformOffsets[i] {
dst = append(dst, make([]uint32, uniformOffsets[i]-len(dst))...)
}
n := typ.Uint32Count()
n := typ.DwordCount()
switch typ.Main {
case shaderir.Bool:
// Bool is 4 bytes in HLSL.
dst = append(dst, uniforms[idx:idx+1]...)
case shaderir.Float:
fs = append(fs, uniforms[idx:idx+1]...)
dst = append(dst, uniforms[idx:idx+1]...)
case shaderir.Int:
fs = append(fs, uniforms[idx:idx+1]...)
dst = append(dst, uniforms[idx:idx+1]...)
case shaderir.Vec2, shaderir.IVec2:
fs = append(fs, uniforms[idx:idx+2]...)
dst = append(dst, uniforms[idx:idx+2]...)
case shaderir.Vec3, shaderir.IVec3:
fs = append(fs, uniforms[idx:idx+3]...)
dst = append(dst, uniforms[idx:idx+3]...)
case shaderir.Vec4, shaderir.IVec4:
fs = append(fs, uniforms[idx:idx+4]...)
dst = append(dst, uniforms[idx:idx+4]...)
case shaderir.Mat2:
fs = append(fs,
dst = append(dst,
uniforms[idx+0], uniforms[idx+2], 0, 0,
uniforms[idx+1], uniforms[idx+3],
)
case shaderir.Mat3:
fs = append(fs,
dst = append(dst,
uniforms[idx+0], uniforms[idx+3], uniforms[idx+6], 0,
uniforms[idx+1], uniforms[idx+4], uniforms[idx+7], 0,
uniforms[idx+2], uniforms[idx+5], uniforms[idx+8],
@@ -169,14 +245,14 @@ func adjustUniforms(uniformTypes []shaderir.Type, uniformOffsets []int, uniforms
// In DirectX, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
// Invert the sign bits as float32 values.
fs = append(fs,
dst = append(dst,
uniforms[idx+0], uniforms[idx+4], uniforms[idx+8], uniforms[idx+12],
uniforms[idx+1]^(1<<31), uniforms[idx+5]^(1<<31), uniforms[idx+9]^(1<<31), uniforms[idx+13]^(1<<31),
uniforms[idx+2], uniforms[idx+6], uniforms[idx+10], uniforms[idx+14],
uniforms[idx+3], uniforms[idx+7], uniforms[idx+11], uniforms[idx+15],
)
} else {
fs = append(fs,
dst = append(dst,
uniforms[idx+0], uniforms[idx+4], uniforms[idx+8], uniforms[idx+12],
uniforms[idx+1], uniforms[idx+5], uniforms[idx+9], uniforms[idx+13],
uniforms[idx+2], uniforms[idx+6], uniforms[idx+10], uniforms[idx+14],
@@ -186,63 +262,70 @@ func adjustUniforms(uniformTypes []shaderir.Type, uniformOffsets []int, uniforms
case shaderir.Array:
// Each element is aligned to the boundary.
switch typ.Sub[0].Main {
case shaderir.Bool:
for j := 0; j < typ.Length; j++ {
dst = append(dst, uniforms[idx+j])
if j < typ.Length-1 {
dst = append(dst, 0, 0, 0)
}
}
case shaderir.Float:
for j := 0; j < typ.Length; j++ {
fs = append(fs, uniforms[idx+j])
dst = append(dst, uniforms[idx+j])
if j < typ.Length-1 {
fs = append(fs, 0, 0, 0)
dst = append(dst, 0, 0, 0)
}
}
case shaderir.Int:
for j := 0; j < typ.Length; j++ {
fs = append(fs, uniforms[idx+j])
dst = append(dst, uniforms[idx+j])
if j < typ.Length-1 {
fs = append(fs, 0, 0, 0)
dst = append(dst, 0, 0, 0)
}
}
case shaderir.Vec2, shaderir.IVec2:
for j := 0; j < typ.Length; j++ {
fs = append(fs, uniforms[idx+2*j:idx+2*(j+1)]...)
dst = append(dst, uniforms[idx+2*j:idx+2*(j+1)]...)
if j < typ.Length-1 {
fs = append(fs, 0, 0)
dst = append(dst, 0, 0)
}
}
case shaderir.Vec3, shaderir.IVec3:
for j := 0; j < typ.Length; j++ {
fs = append(fs, uniforms[idx+3*j:idx+3*(j+1)]...)
dst = append(dst, uniforms[idx+3*j:idx+3*(j+1)]...)
if j < typ.Length-1 {
fs = append(fs, 0)
dst = append(dst, 0)
}
}
case shaderir.Vec4, shaderir.IVec4:
fs = append(fs, uniforms[idx:idx+4*typ.Length]...)
dst = append(dst, uniforms[idx:idx+4*typ.Length]...)
case shaderir.Mat2:
for j := 0; j < typ.Length; j++ {
u := uniforms[idx+4*j : idx+4*(j+1)]
fs = append(fs,
dst = append(dst,
u[0], u[2], 0, 0,
u[1], u[3], 0, 0,
u[1], u[3],
)
}
if typ.Length > 0 {
fs = fs[:len(fs)-2]
if j < typ.Length-1 {
dst = append(dst, 0, 0)
}
}
case shaderir.Mat3:
for j := 0; j < typ.Length; j++ {
u := uniforms[idx+9*j : idx+9*(j+1)]
fs = append(fs,
dst = append(dst,
u[0], u[3], u[6], 0,
u[1], u[4], u[7], 0,
u[2], u[5], u[8], 0,
u[2], u[5], u[8],
)
}
if typ.Length > 0 {
fs = fs[:len(fs)-1]
if j < typ.Length-1 {
dst = append(dst, 0)
}
}
case shaderir.Mat4:
for j := 0; j < typ.Length; j++ {
u := uniforms[idx+16*j : idx+16*(j+1)]
fs = append(fs,
dst = append(dst,
u[0], u[4], u[8], u[12],
u[1], u[5], u[9], u[13],
u[2], u[6], u[10], u[14],
@@ -258,5 +341,5 @@ func adjustUniforms(uniformTypes []shaderir.Type, uniformOffsets []int, uniforms
idx += n
}
return fs
return dst
}
+18 -10
View File
@@ -30,19 +30,19 @@ type DstRegion struct {
type FillRule int
const (
FillAll FillRule = iota
NonZero
EvenOdd
FillRuleFillAll FillRule = iota
FillRuleNonZero
FillRuleEvenOdd
)
func (f FillRule) String() string {
switch f {
case FillAll:
return "FillAll"
case NonZero:
return "NonZero"
case EvenOdd:
return "EvenOdd"
case FillRuleFillAll:
return "FillRuleFillAll"
case FillRuleNonZero:
return "FillRuleNonZero"
case FillRuleEvenOdd:
return "FillRuleEvenOdd"
default:
return fmt.Sprintf("FillRule(%d)", f)
}
@@ -68,7 +68,7 @@ type Graphics interface {
NewShader(program *shaderir.Program) (Shader, error)
// DrawTriangles draws an image onto another image with the given parameters.
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageCount]ImageID, shader ShaderID, dstRegions []DstRegion, indexOffset int, blend Blend, uniforms []uint32, fillRule FillRule) error
DrawTriangles(dst ImageID, srcs [graphics.ShaderSrcImageCount]ImageID, shader ShaderID, dstRegions []DstRegion, indexOffset int, blend Blend, uniforms []uint32, fillRule FillRule) error
}
type Resetter interface {
@@ -95,3 +95,11 @@ type Shader interface {
}
type ShaderID int
type ColorSpace int
const (
ColorSpaceDefault ColorSpace = iota
ColorSpaceSRGB
ColorSpaceDisplayP3
)
@@ -29,13 +29,46 @@ import (
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
var (
class_CAMetalLayer = objc.GetClass("CAMetalLayer")
class_CAMetalDisplayLink = objc.GetClass("CAMetalDisplayLink")
class_CAMetalDisplayLinkUpdate = objc.GetClass("CAMetalDisplayLinkUpdate")
)
var (
sel_pixelFormat = objc.RegisterName("pixelFormat")
sel_setDevice = objc.RegisterName("setDevice:")
sel_setOpaque = objc.RegisterName("setOpaque:")
sel_setPixelFormat = objc.RegisterName("setPixelFormat:")
sel_new = objc.RegisterName("new")
sel_setColorspace = objc.RegisterName("setColorspace:")
sel_setMaximumDrawableCount = objc.RegisterName("setMaximumDrawableCount:")
sel_setDisplaySyncEnabled = objc.RegisterName("setDisplaySyncEnabled:")
sel_setDrawableSize = objc.RegisterName("setDrawableSize:")
sel_nextDrawable = objc.RegisterName("nextDrawable")
sel_presentsWithTransaction = objc.RegisterName("presentsWithTransaction")
sel_setPresentsWithTransaction = objc.RegisterName("setPresentsWithTransaction:")
sel_setFramebufferOnly = objc.RegisterName("setFramebufferOnly:")
sel_texture = objc.RegisterName("texture")
sel_present = objc.RegisterName("present")
sel_alloc = objc.RegisterName("alloc")
sel_initWithMetalLayer = objc.RegisterName("initWithMetalLayer:")
sel_setDelegate = objc.RegisterName("setDelegate:")
sel_addToOneLoopForMode = objc.RegisterName("addToRunLoop:forMode:")
sel_removeFromRunLoopForMode = objc.RegisterName("removeFromRunLoop:forMode:")
sel_setPaused = objc.RegisterName("setPaused:")
sel_drawable = objc.RegisterName("drawable")
sel_release = objc.RegisterName("release")
)
// 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.
// Reference: https://developer.apple.com/documentation/quartzcore/calayer?language=objc.
type Layer interface {
// Layer returns the underlying CALayer * pointer.
Layer() unsafe.Pointer
@@ -43,15 +76,15 @@ type Layer interface {
// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
type MetalLayer struct {
metalLayer objc.ID
}
// MakeMetalLayer creates a new Core Animation Metal layer.
// NewMetalLayer creates a new Core Animation Metal layer.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
func MakeMetalLayer() (MetalLayer, error) {
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
func NewMetalLayer(colorSpace graphicsdriver.ColorSpace) (MetalLayer, error) {
coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
return MetalLayer{}, err
@@ -67,15 +100,32 @@ func MakeMetalLayer() (MetalLayer, error) {
return MetalLayer{}, err
}
kCGColorSpaceDisplayP3, err := purego.Dlsym(coreGraphics, "kCGColorSpaceDisplayP3")
if err != nil {
return MetalLayer{}, err
var colorSpaceSym uintptr
switch colorSpace {
case graphicsdriver.ColorSpaceSRGB:
kCGColorSpaceSRGB, err := purego.Dlsym(coreGraphics, "kCGColorSpaceSRGB")
if err != nil {
return MetalLayer{}, err
}
colorSpaceSym = kCGColorSpaceSRGB
default:
fallthrough
case graphicsdriver.ColorSpaceDisplayP3:
kCGColorSpaceDisplayP3, err := purego.Dlsym(coreGraphics, "kCGColorSpaceDisplayP3")
if err != nil {
return MetalLayer{}, err
}
colorSpaceSym = kCGColorSpaceDisplayP3
}
layer := objc.ID(objc.GetClass("CAMetalLayer")).Send(objc.RegisterName("new"))
layer := objc.ID(class_CAMetalLayer).Send(sel_new)
// setColorspace: is available from iOS 13.0?
// https://github.com/hajimehoshi/ebiten/commit/3af351a2aa31e30affd433429c42130015b302f3
// TODO: Enable this on iOS as well.
if runtime.GOOS != "ios" {
colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&kCGColorSpaceDisplayP3))) // Dlsym returns pointer to symbol so dereference it
layer.Send(objc.RegisterName("setColorspace:"), colorspace)
// Dlsym returns pointer to symbol so dereference it.
colorspace, _, _ := purego.SyscallN(cgColorSpaceCreateWithName, **(**uintptr)(unsafe.Pointer(&colorSpaceSym)))
layer.Send(sel_setColorspace, colorspace)
purego.SyscallN(cgColorSpaceRelease, colorspace)
}
return MetalLayer{layer}, nil
@@ -88,21 +138,21 @@ func (ml MetalLayer) Layer() unsafe.Pointer {
// PixelFormat returns the pixel format of textures for rendering layer content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat")))
return mtl.PixelFormat(ml.metalLayer.Send(sel_pixelFormat))
}
// SetDevice sets the Metal device responsible for the layer's drawable resources.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device?language=objc.
func (ml MetalLayer) SetDevice(device mtl.Device) {
ml.metalLayer.Send(objc.RegisterName("setDevice:"), uintptr(device.Device()))
ml.metalLayer.Send(sel_setDevice, uintptr(device.Device()))
}
// SetOpaque a Boolean value indicating whether the layer contains completely opaque content.
func (ml MetalLayer) SetOpaque(opaque bool) {
ml.metalLayer.Send(objc.RegisterName("setOpaque:"), opaque)
ml.metalLayer.Send(sel_setOpaque, opaque)
}
// SetPixelFormat controls the pixel format of textures for rendering layer content.
@@ -111,14 +161,14 @@ func (ml MetalLayer) SetOpaque(opaque bool) {
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
// SetPixelFormat panics for other values.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
switch pf {
case mtl.PixelFormatRGBA8UNorm, mtl.PixelFormatRGBA8UNormSRGB, mtl.PixelFormatBGRA8UNorm, mtl.PixelFormatBGRA8UNormSRGB, mtl.PixelFormatStencil8:
default:
panic(errors.New(fmt.Sprintf("invalid pixel format %d", pf)))
panic(fmt.Sprintf("ca: invalid pixel format %d", pf))
}
ml.metalLayer.Send(objc.RegisterName("setPixelFormat:"), uint(pf))
ml.metalLayer.Send(sel_setPixelFormat, uint(pf))
}
// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
@@ -126,44 +176,37 @@ func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
//
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount?language=objc.
func (ml MetalLayer) SetMaximumDrawableCount(count int) {
if count < 2 || count > 3 {
panic(errors.New(fmt.Sprintf("failed trying to set maximumDrawableCount to %d outside of the valid range of [2, 3]", count)))
panic(fmt.Sprintf("ca: failed trying to set maximumDrawableCount to %d outside of the valid range of [2, 3]", count))
}
ml.metalLayer.Send(objc.RegisterName("setMaximumDrawableCount:"), count)
ml.metalLayer.Send(sel_setMaximumDrawableCount, count)
}
// 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.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled?language=objc.
func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
if runtime.GOOS == "ios" {
return
}
ml.metalLayer.Send(objc.RegisterName("setDisplaySyncEnabled:"), enabled)
ml.metalLayer.Send(sel_setDisplaySyncEnabled, enabled)
}
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize?language=objc.
func (ml MetalLayer) SetDrawableSize(width, height int) {
// TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call
var sel_setDrawableSize = objc.RegisterName("setDrawableSize:")
sig := cocoa.NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(objc.GetClass("CAMetalLayer")), sel_setDrawableSize)
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(ml.metalLayer)
inv.SetSelector(sel_setDrawableSize)
inv.SetArgumentAtIndex(unsafe.Pointer(&cocoa.CGSize{Width: cocoa.CGFloat(width), Height: cocoa.CGFloat(height)}), 2)
inv.Invoke()
ml.metalLayer.Send(sel_setDrawableSize, cocoa.CGSize{Width: cocoa.CGFloat(width), Height: cocoa.CGFloat(height)})
}
// NextDrawable returns a Metal drawable.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable?language=objc.
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
md := ml.metalLayer.Send(objc.RegisterName("nextDrawable"))
md := ml.metalLayer.Send(sel_nextDrawable)
if md == 0 {
return MetalDrawable{}, errors.New("nextDrawable returned nil")
}
@@ -172,28 +215,28 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
// PresentsWithTransaction returns a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
func (ml MetalLayer) PresentsWithTransaction() bool {
return ml.metalLayer.Send(objc.RegisterName("presentsWithTransaction")) != 0
return ml.metalLayer.Send(sel_presentsWithTransaction) != 0
}
// SetPresentsWithTransaction sets a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) {
ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction)
ml.metalLayer.Send(sel_setPresentsWithTransaction, presentsWithTransaction)
}
// SetFramebufferOnly sets a Boolean value that determines whether the layers textures are used only for rendering.
//
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly?language=objc
func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) {
ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly)
ml.metalLayer.Send(sel_setFramebufferOnly, framebufferOnly)
}
// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable?language=objc.
type MetalDrawable struct {
metalDrawable objc.ID
}
@@ -205,14 +248,75 @@ func (md MetalDrawable) Drawable() unsafe.Pointer {
// Texture returns a Metal texture object representing the drawable object's content.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture?language=objc.
func (md MetalDrawable) Texture() mtl.Texture {
return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture")))
return mtl.NewTexture(md.metalDrawable.Send(sel_texture))
}
// Present presents the drawable onscreen as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present.
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present?language=objc.
func (md MetalDrawable) Present() {
md.metalDrawable.Send(objc.RegisterName("present"))
md.metalDrawable.Send(sel_present)
}
// MetalDisplayLink is a class your Metal app uses to register for callbacks to synchronize its animations for a display.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink?language=objc
type MetalDisplayLink struct {
objc.ID
}
// SetDelegate sets an instance of a type your app implements that responds to the systems callbacks.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/delegate?language=objc
func (m MetalDisplayLink) SetDelegate(delegate objc.ID) {
m.Send(sel_setDelegate, delegate)
}
// AddToRunLoop registers the display link with a run loop.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/add(to:formode:)?language=objc
func (m MetalDisplayLink) AddToRunLoop(runLoop cocoa.NSRunLoop, mode cocoa.NSRunLoopMode) {
m.Send(sel_addToOneLoopForMode, runLoop, mode)
}
// RemoveFromRunLoop removes a modes display link from a run loop.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/remove(from:formode:)?language=objc
func (m MetalDisplayLink) RemoveFromRunLoop(runLoop cocoa.NSRunLoop, mode cocoa.NSRunLoopMode) {
m.Send(sel_removeFromRunLoopForMode, runLoop, mode)
}
// SetPaused sets a Boolean value that indicates whether the system suspends the display links notifications to the target.
//
// https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/ispaused?language=objc
func (m MetalDisplayLink) SetPaused(paused bool) {
m.Send(sel_setPaused, paused)
}
func (m MetalDisplayLink) Release() {
m.Send(sel_release)
}
// NewMetalDisplayLink creates a display link for Metal from a Core Animation layer.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/init(metallayer:)?language=objc
func NewMetalDisplayLink(metalLayer MetalLayer) MetalDisplayLink {
displayLink := objc.ID(class_CAMetalDisplayLink).Send(sel_alloc).Send(sel_initWithMetalLayer, metalLayer.metalLayer)
return MetalDisplayLink{displayLink}
}
// MetalDisplayLinkUpdate stores information about a single update from a Metal display link instance.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/update?language=objc
type MetalDisplayLinkUpdate struct {
objc.ID
}
// Drawable returns the Metal drawable your app uses to render the next frame.
//
// https://developer.apple.com/documentation/quartzcore/cametaldisplaylink/update/drawable?language=objc
func (m MetalDisplayLinkUpdate) Drawable() MetalDrawable {
return MetalDrawable{m.Send(sel_drawable)}
}
@@ -0,0 +1,208 @@
// Copyright 2025 The Ebitengine 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.
//go:build darwin && !ios
package metal
// #cgo CFLAGS: -x objective-c
//
// #include <Foundation/Foundation.h>
// #include <CoreVideo/CVDisplayLink.h>
// #if __has_include(<QuartzCore/CAMetalLayer.h>)
// #include <QuartzCore/CAMetalLayer.h>
// #endif
//
// #cgo noescape isCAMetalDisplayLinkAvailable
// #cgo nocallback isCAMetalDisplayLinkAvailable
// static bool isCAMetalDisplayLinkAvailable() {
// // TODO: Use PureGo if returning a struct is supported (ebitengine/purego#225).
// // As operatingSystemVersion returns a struct, this cannot be written with PureGo.
// NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
// if (version.majorVersion >= 14) {
// // Also check if the CAMetalDisplayLink class exists
// return NSClassFromString(@"CAMetalDisplayLink") != nil;
// }
// return false;
// }
//
// int ebitengine_DisplayLinkOutputCallback(CVDisplayLinkRef displayLinkRef, CVTimeStamp* inNow, CVTimeStamp* inOutputTime, uint64_t flagsIn, uint64_t* flagsOut, void* displayLinkContext);
import "C"
import (
"log/slog"
"runtime"
"runtime/cgo"
"time"
"unsafe"
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
)
func (v *view) initDisplayLink() error {
if C.isCAMetalDisplayLinkAvailable() {
if err := v.initCAMetalDisplayLink(); err != nil {
return err
}
return nil
}
if err := v.initCADisplayLink(); err != nil {
return err
}
return nil
}
var class_EbitengineCAMetalDisplayLinkDelegate objc.Class
func (v *view) initCAMetalDisplayLink() error {
v.drawableCh = make(chan ca.MetalDrawable)
v.drawableDoneCh = make(chan struct{})
v.metalDisplayLinkRunLoop = createThreadWithRunLoop()
c, err := objc.RegisterClass(
"EbitengineCAMetalDisplayLinkDelegate",
objc.GetClass("NSObject"),
[]*objc.Protocol{objc.GetProtocol("CAMetalDisplayLinkDelegate")},
nil,
[]objc.MethodDef{
{
Cmd: objc.RegisterName("metalDisplayLink:needsUpdate:"),
Fn: func(id objc.ID, cmd objc.SEL, metalDisplayLink objc.ID, needsUpdate objc.ID) {
// There is a case where this callback is invoked from the main run loop (#3353).
// This is very mysterious, but this causes a deadlock.
// As a workaround, return this immediately when the current run loop is the main run loop.
if cocoa.NSRunLoop_currentRunLoop() == cocoa.NSRunLoop_mainRunLoop() {
slog.Debug("metal: metalDisplayLink:needsUpdate: is unexpectedly called from the main run loop")
return
}
drawable := ca.MetalDisplayLinkUpdate{ID: needsUpdate}.Drawable()
if drawable == (ca.MetalDrawable{}) {
return
}
v.drawableCh <- drawable
<-v.drawableDoneCh
},
},
},
)
if err != nil {
return err
}
class_EbitengineCAMetalDisplayLinkDelegate = c
v.createCAMetalDisplayLink()
return nil
}
func (v *view) createCAMetalDisplayLink() {
ch := make(chan uintptr)
v.metalDisplayLinkRunLoop.PerformBlock(objc.NewBlock(func(block objc.Block) {
dl := ca.NewMetalDisplayLink(v.ml)
dl.SetDelegate(objc.ID(class_EbitengineCAMetalDisplayLinkDelegate).Send(objc.RegisterName("new")))
dl.AddToRunLoop(v.metalDisplayLinkRunLoop, cocoa.NSDefaultRunLoopMode)
dl.SetPaused(false)
ch <- uintptr(dl.ID)
close(ch)
}))
v.metalDisplayLink = <-ch
}
func createThreadWithRunLoop() cocoa.NSRunLoop {
ch := make(chan cocoa.NSRunLoop)
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
runLoop := cocoa.NSRunLoop_currentRunLoop()
ch <- runLoop
close(ch)
// Add a dummy mach port to keep alive.
port := cocoa.NSMachPort_port()
runLoop.AddPort(port, cocoa.NSRunLoopCommonModes)
runLoop.Run()
}()
runLoop := <-ch
if runLoop.ID == 0 {
panic("metal: runLoop must be initialized")
}
return runLoop
}
func (v *view) initCADisplayLink() error {
v.fence = newFence()
// TODO: CVDisplayLink APIs are deprecated in macOS 10.15 and later.
// Use new APIs like NSView.displayLink(target:selector:).
var displayLinkRef C.CVDisplayLinkRef
if ret := C.CVDisplayLinkCreateWithActiveCGDisplays(&displayLinkRef); ret != kCVReturnSuccess {
// Failed to get the display link, so proceed without it.
return nil
}
v.handleToSelf = cgo.NewHandle(v)
C.CVDisplayLinkSetOutputCallback(displayLinkRef, C.CVDisplayLinkOutputCallback(C.ebitengine_DisplayLinkOutputCallback), unsafe.Pointer(&v.handleToSelf))
C.CVDisplayLinkStart(displayLinkRef)
v.caDisplayLink = uintptr(displayLinkRef)
return nil
}
//export ebitengine_DisplayLinkOutputCallback
func ebitengine_DisplayLinkOutputCallback(displayLinkRef C.CVDisplayLinkRef, inNow, inOutputTime *C.CVTimeStamp, flagsIn C.uint64_t, flagsOut *C.uint64_t, displayLinkContext unsafe.Pointer) C.int {
cgoHandle := (*cgo.Handle)(displayLinkContext)
view := cgoHandle.Value().(*view)
view.fence.advance()
return 0
}
func (v *view) nextDrawable() ca.MetalDrawable {
if v.metalDisplayLink != 0 {
const wait = 100 * time.Millisecond
if v.drawableTimer == nil {
v.drawableTimer = time.NewTimer(wait)
} else {
v.drawableTimer.Reset(wait)
}
defer v.drawableTimer.Stop()
select {
case d := <-v.drawableCh:
return d
case <-v.drawableTimer.C:
// This happens when the main thread needs to execute the notification observer callback,
// or when the appliation goes to full screen (#3354).
return ca.MetalDrawable{}
}
}
v.waitForDisplayLinkOutputCallback()
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
}
func (v *view) finishDrawableUsage() {
if v.metalDisplayLink != 0 {
v.drawableDoneCh <- struct{}{}
return
}
}
@@ -32,9 +32,13 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
var sel_supportsFamily = objc.RegisterName("supportsFamily:")
type Graphics struct {
view view
colorSpace graphicsdriver.ColorSpace
cq mtl.CommandQueue
cb mtl.CommandBuffer
rce mtl.RenderCommandEncoder
@@ -42,7 +46,15 @@ type Graphics struct {
screenDrawable ca.MetalDrawable
buffers map[mtl.CommandBuffer][]mtl.Buffer
// frame is the current frame number.
// frame is incremented when the screen is presented.
frame int64
// frameToCB maps a frame number to command buffers used in the frame.
// frameToCB keeps command buffers not to be released until the command buffers are completed.
frameToCB map[int64][]mtl.CommandBuffer
buffers map[int64][]mtl.Buffer
unusedBuffers map[mtl.Buffer]struct{}
lastDst *Image
@@ -90,7 +102,7 @@ func init() {
// NewGraphics creates an implementation of graphicsdriver.Graphics for Metal.
// The returned graphics value is nil iff the error is not nil.
func NewGraphics() (graphicsdriver.Graphics, error) {
func NewGraphics(colorSpace graphicsdriver.ColorSpace) (graphicsdriver.Graphics, error) {
// On old mac devices like iMac 2011, Metal is not supported (#779).
// TODO: Is there a better way to check whether Metal is available or not?
// It seems OK to call MTLCreateSystemDefaultDevice multiple times, so this should be fine.
@@ -98,12 +110,14 @@ func NewGraphics() (graphicsdriver.Graphics, error) {
return nil, fmt.Errorf("metal: mtl.CreateSystemDefaultDevice failed: %w", systemDefaultDeviceErr)
}
g := &Graphics{}
g := &Graphics{
colorSpace: colorSpace,
}
if runtime.GOOS != "ios" {
// Initializing a Metal device and a layer must be done in the main thread on macOS.
// Note that this assumes NewGraphics is called on the main thread on desktops.
if err := g.view.initialize(systemDefaultDevice); err != nil {
if err := g.view.initialize(systemDefaultDevice, colorSpace); err != nil {
return nil, err
}
}
@@ -118,10 +132,12 @@ func (g *Graphics) Begin() error {
}
func (g *Graphics) End(present bool) error {
g.flushIfNeeded(present)
g.screenDrawable = ca.MetalDrawable{}
g.flushCommandBufferIfNeeded(present)
g.pool.Release()
g.pool.ID = 0
if present {
g.frame++
}
return nil
}
@@ -149,21 +165,30 @@ func pow2(x uintptr) uintptr {
}
func (g *Graphics) gcBuffers() {
for cb, bs := range g.buffers {
// If the command buffer still lives, the buffer must not be updated.
// TODO: Handle an error?
if cb.Status() != mtl.CommandBufferStatusCompleted {
loop:
for frame, bs := range g.buffers {
if frame == g.frame {
continue
}
// Check if all command buffers for the frame are completed.
for _, cb := range g.frameToCB[frame] {
if cb.Status() != mtl.CommandBufferStatusCompleted {
continue loop
}
}
for _, cb := range g.frameToCB[frame] {
cb.Release()
}
delete(g.frameToCB, frame)
for _, b := range bs {
if g.unusedBuffers == nil {
g.unusedBuffers = map[mtl.Buffer]struct{}{}
}
g.unusedBuffers[b] = struct{}{}
}
delete(g.buffers, cb)
cb.Release()
delete(g.buffers, frame)
}
const maxUnusedBuffers = 10
@@ -182,10 +207,20 @@ func (g *Graphics) gcBuffers() {
}
}
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
func (g *Graphics) ensureCommandBuffer() {
if g.cb != (mtl.CommandBuffer{}) {
return
}
g.cb = g.cq.CommandBuffer()
if g.frameToCB == nil {
g.frameToCB = map[int64][]mtl.CommandBuffer{}
}
g.frameToCB[g.frame] = append(g.frameToCB[g.frame], g.cb)
g.cb.Retain()
}
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
g.ensureCommandBuffer()
var newBuf mtl.Buffer
for b := range g.unusedBuffers {
@@ -197,16 +232,13 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
}
if newBuf == (mtl.Buffer{}) {
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
newBuf = g.view.getMTLDevice().NewBufferWithLength(pow2(length), resourceStorageMode)
}
if g.buffers == nil {
g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{}
g.buffers = map[int64][]mtl.Buffer{}
}
if _, ok := g.buffers[g.cb]; !ok {
g.cb.Retain()
}
g.buffers[g.cb] = append(g.buffers[g.cb], newBuf)
g.buffers[g.frame] = append(g.buffers[g.frame], newBuf)
return newBuf
}
@@ -223,21 +255,21 @@ func (g *Graphics) SetVertices(vertices []float32, indices []uint32) error {
return nil
}
func (g *Graphics) flushIfNeeded(present bool) {
if g.cb == (mtl.CommandBuffer{}) && !present {
func (g *Graphics) flushCommandBufferIfNeeded(present bool) {
if g.cb == (mtl.CommandBuffer{}) {
if g.rce != (mtl.RenderCommandEncoder{}) {
panic("metal: render command encoder must be empty if command buffer is empty")
}
return
}
g.flushRenderCommandEncoderIfNeeded()
if present {
// This check is necessary when skipping to render the screen (SetScreenClearedEveryFrame(false)).
if g.screenDrawable == (ca.MetalDrawable{}) && g.cb != (mtl.CommandBuffer{}) {
g.screenDrawable = g.view.nextDrawable()
}
if g.screenDrawable != (ca.MetalDrawable{}) {
g.cb.PresentDrawable(g.screenDrawable)
}
var presented bool
if present && g.screenDrawable != (ca.MetalDrawable{}) {
g.cb.PresentDrawable(g.screenDrawable)
g.screenDrawable = ca.MetalDrawable{}
presented = true
}
g.cb.Commit()
@@ -248,6 +280,10 @@ func (g *Graphics) flushIfNeeded(present bool) {
g.tmpTextures = g.tmpTextures[:0]
g.cb = mtl.CommandBuffer{}
if presented {
g.view.finishDrawableUsage()
}
}
func (g *Graphics) checkSize(width, height int) {
@@ -286,7 +322,7 @@ func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
}
t := g.view.getMTLDevice().MakeTexture(td)
t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
i := &Image{
id: g.genNextImageID(),
graphics: g,
@@ -388,16 +424,17 @@ func (g *Graphics) Initialize() error {
if runtime.GOOS == "ios" {
// Initializing a Metal device and a layer must be done in the render thread on iOS.
if err := g.view.initialize(systemDefaultDevice); err != nil {
if err := g.view.initialize(systemDefaultDevice, g.colorSpace); err != nil {
return err
}
}
if g.transparent {
g.view.ml.SetOpaque(false)
}
// The default value is false [1], but transparinting doesn't work without calling this.
// To avoid confusion, let's call this explicitly.
// [1] https://developer.apple.com/documentation/quartzcore/calayer/isopaque?language=objc
g.view.ml.SetOpaque(!g.transparent)
// The stencil reference value is always 0 (default).
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[noStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -411,7 +448,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[incrementStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -425,7 +462,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[invertStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -439,7 +476,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[drawWithStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@@ -454,7 +491,7 @@ func (g *Graphics) Initialize() error {
},
})
g.cq = g.view.getMTLDevice().MakeCommandQueue()
g.cq = g.view.getMTLDevice().NewCommandQueue()
return nil
}
@@ -467,11 +504,18 @@ func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
g.lastDst = nil
}
func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs [graphics.ShaderImageCount]*Image, indexOffset int, shader *Shader, uniforms [][]uint32, blend graphicsdriver.Blend, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs [graphics.ShaderSrcImageCount]*Image, indexOffset int, shader *Shader, uniforms []uint32, blend graphicsdriver.Blend, fillRule graphicsdriver.FillRule) error {
// In order to create a separate command buffer for the screen, flush the current command buffer.
// It's because a drawable will not be released as long as the CommandBuffer referencing it is alive,
// it is more efficient to separate CommandBuffers that use the drawable from those that do not.
if (g.lastDst != nil && g.lastDst.screen) != dst.screen {
g.flushCommandBufferIfNeeded(false)
}
// When preparing a stencil buffer, flush the current render command encoder
// to make sure the stencil buffer is cleared when loading.
// TODO: What about clearing the stencil buffer by vertices?
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillAll {
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillRuleFillAll {
g.flushRenderCommandEncoderIfNeeded()
}
g.lastDst = dst
@@ -497,17 +541,15 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
rpd.ColorAttachments[0].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
dst.ensureStencil()
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
rpd.StencilAttachment.Texture = dst.stencil
}
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
}
g.rce = g.cb.MakeRenderCommandEncoder(rpd)
g.ensureCommandBuffer()
g.rce = g.cb.RenderCommandEncoderWithDescriptor(rpd)
}
w, h := dst.internalSize()
@@ -521,12 +563,11 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
})
g.rce.SetVertexBuffer(g.vb, 0, 0)
for i, u := range uniforms {
if u == nil {
continue
}
g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
if len(uniforms) > 0 {
uniforms := adjustUniformVariablesLayout(shader.ir.Uniforms, uniforms)
head := unsafe.SliceData(uniforms)
g.rce.SetVertexBytes(unsafe.Pointer(head), unsafe.Sizeof(uniforms[0])*uintptr(len(uniforms)), 1)
g.rce.SetFragmentBytes(unsafe.Pointer(head), unsafe.Sizeof(uniforms[0])*uintptr(len(uniforms)), 0)
}
for i, src := range srcs {
@@ -544,26 +585,26 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
drawWithStencilRpss mtl.RenderPipelineState
)
switch fillRule {
case graphicsdriver.FillAll:
case graphicsdriver.FillRuleFillAll:
s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen)
if err != nil {
return err
}
noStencilRpss = s
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen)
if err != nil {
return err
}
incrementStencilRpss = s
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen)
if err != nil {
return err
}
invertStencilRpss = s
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
if err != nil {
return err
@@ -580,20 +621,20 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
})
switch fillRule {
case graphicsdriver.FillAll:
case graphicsdriver.FillRuleFillAll:
g.rce.SetDepthStencilState(g.dsss[noStencil])
g.rce.SetRenderPipelineState(noStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
g.rce.SetDepthStencilState(g.dsss[incrementStencil])
g.rce.SetRenderPipelineState(incrementStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
g.rce.SetDepthStencilState(g.dsss[invertStencil])
g.rce.SetRenderPipelineState(invertStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
g.rce.SetDepthStencilState(g.dsss[drawWithStencil])
g.rce.SetRenderPipelineState(drawWithStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
@@ -605,7 +646,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
return nil
}
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("metal: shader ID is invalid")
}
@@ -616,72 +657,12 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
g.view.update()
}
var srcs [graphics.ShaderImageCount]*Image
var srcs [graphics.ShaderSrcImageCount]*Image
for i, srcID := range srcIDs {
srcs[i] = g.images[srcID]
}
uniformVars := make([][]uint32, len(g.shaders[shaderID].ir.Uniforms))
// Set the additional uniform variables.
var idx int
for i, t := range g.shaders[shaderID].ir.Uniforms {
if i == graphics.ProjectionMatrixUniformVariableIndex {
// In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
// Invert the sign bits as float32 values.
uniforms[idx+1] ^= 1 << 31
uniforms[idx+5] ^= 1 << 31
uniforms[idx+9] ^= 1 << 31
uniforms[idx+13] ^= 1 << 31
}
n := t.Uint32Count()
switch t.Main {
case shaderir.Vec3, shaderir.IVec3:
// float3 requires 16-byte alignment (#2463).
v1 := make([]uint32, 4)
copy(v1[0:3], uniforms[idx:idx+3])
uniformVars[i] = v1
case shaderir.Mat3:
// float3x3 requires 16-byte alignment (#2036).
v1 := make([]uint32, 12)
copy(v1[0:3], uniforms[idx:idx+3])
copy(v1[4:7], uniforms[idx+3:idx+6])
copy(v1[8:11], uniforms[idx+6:idx+9])
uniformVars[i] = v1
case shaderir.Array:
switch t.Sub[0].Main {
case shaderir.Vec3, shaderir.IVec3:
v1 := make([]uint32, t.Length*4)
for j := 0; j < t.Length; j++ {
offset0 := j * 3
offset1 := j * 4
copy(v1[offset1:offset1+3], uniforms[idx+offset0:idx+offset0+3])
}
uniformVars[i] = v1
case shaderir.Mat3:
v1 := make([]uint32, t.Length*12)
for j := 0; j < t.Length; j++ {
offset0 := j * 9
offset1 := j * 12
copy(v1[offset1:offset1+3], uniforms[idx+offset0:idx+offset0+3])
copy(v1[offset1+4:offset1+7], uniforms[idx+offset0+3:idx+offset0+6])
copy(v1[offset1+8:offset1+11], uniforms[idx+offset0+6:idx+offset0+9])
}
uniformVars[i] = v1
default:
uniformVars[i] = uniforms[idx : idx+n]
}
default:
uniformVars[i] = uniforms[idx : idx+n]
}
idx += n
}
if err := g.draw(dst, dstRegions, srcs, indexOffset, g.shaders[shaderID], uniformVars, blend, fillRule); err != nil {
if err := g.draw(dst, dstRegions, srcs, indexOffset, g.shaders[shaderID], uniforms, blend, fillRule); err != nil {
return err
}
@@ -705,7 +686,7 @@ func (g *Graphics) MaxImageSize() int {
// supportsFamily is available as of macOS 10.15+ and iOS 13.0+.
// https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily
if d.RespondsToSelector(objc.RegisterName("supportsFamily:")) {
if d.RespondsToSelector(sel_supportsFamily) {
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
g.maxImageSize = 8192
switch {
@@ -802,16 +783,16 @@ func (i *Image) Dispose() {
}
func (i *Image) syncTexture() {
i.graphics.flushRenderCommandEncoderIfNeeded()
i.graphics.flushCommandBufferIfNeeded(false)
// Calling SynchronizeTexture is ignored on iOS (see mtl.m), but it looks like committing BlitCommandEncoder
// is necessary (#1337).
if i.graphics.cb != (mtl.CommandBuffer{}) {
panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?")
panic("metal: command buffer must be empty at syncTexture")
}
cb := i.graphics.cq.MakeCommandBuffer()
bce := cb.MakeBlitCommandEncoder()
cb := i.graphics.cq.CommandBuffer()
bce := cb.BlitCommandEncoder()
bce.SynchronizeTexture(i.texture, 0, 0)
bce.EndEncoding()
@@ -821,7 +802,6 @@ func (i *Image) syncTexture() {
}
func (i *Image) ReadPixels(args []graphicsdriver.PixelsArgs) error {
i.graphics.flushIfNeeded(false)
i.syncTexture()
for _, arg := range args {
@@ -859,7 +839,7 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
}
t := g.view.getMTLDevice().MakeTexture(td)
t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
g.tmpTextures = append(g.tmpTextures, t)
for _, a := range args {
@@ -869,10 +849,8 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
}, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Region.Dx())
}
if g.cb == (mtl.CommandBuffer{}) {
g.cb = i.graphics.cq.MakeCommandBuffer()
}
bce := g.cb.MakeBlitCommandEncoder()
g.ensureCommandBuffer()
bce := g.cb.BlitCommandEncoder()
for _, a := range args {
so := mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0}
ss := mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1}
@@ -896,6 +874,9 @@ func (i *Image) mtlTexture() mtl.Texture {
// After nextDrawable, it is expected some command buffers are completed.
g.gcBuffers()
}
if g.screenDrawable == (ca.MetalDrawable{}) {
return mtl.Texture{}
}
return g.screenDrawable.Texture()
}
return i.texture
@@ -914,5 +895,133 @@ func (i *Image) ensureStencil() {
StorageMode: mtl.StorageModePrivate,
Usage: mtl.TextureUsageRenderTarget,
}
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td)
i.stencil = i.graphics.view.getMTLDevice().NewTextureWithDescriptor(td)
}
// adjustUniformVariablesLayout returns adjusted uniform variables to match the Metal's memory layout.
func adjustUniformVariablesLayout(uniformTypes []shaderir.Type, uniforms []uint32) []uint32 {
// Each type's alignment is defined by the specification.
// See https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
var values []uint32
fillZerosToFitAlignment := func(values []uint32, align int) []uint32 {
if len(values) == 0 {
return values
}
n0 := len(values)
n1 := ((len(values)-1)/align + 1) * align
if n0 == n1 {
return values
}
return append(values, make([]uint32, n1-n0)...)
}
var idx int
var byteAlign int
for i, typ := range uniformTypes {
n := typ.DwordCount()
switch typ.Main {
case shaderir.Bool:
if byteAlign == 0 {
values = append(values, uniforms[idx:idx+1]...)
} else {
values[len(values)-1] |= uniforms[idx] << (8 * byteAlign)
}
case shaderir.Float, shaderir.Int:
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec2, shaderir.IVec2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec3, shaderir.IVec3:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
values = append(values, 0)
case shaderir.Vec4, shaderir.IVec4:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat3:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+3]...)
values = append(values, 0)
values = append(values, uniforms[idx+3:idx+6]...)
values = append(values, 0)
values = append(values, uniforms[idx+6:idx+9]...)
values = append(values, 0)
case shaderir.Mat4:
values = fillZerosToFitAlignment(values, 4)
if i == graphics.ProjectionMatrixUniformVariableIndex {
// In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
// Invert the sign bits as float32 values.
u := uniforms[idx : idx+16]
values = append(values,
u[0], u[1]^uint32(1<<31), u[2], u[3],
u[4], u[5]^uint32(1<<31), u[6], u[7],
u[8], u[9]^uint32(1<<31), u[10], u[11],
u[12], u[13]^uint32(1<<31), u[14], u[15],
)
} else {
values = append(values, uniforms[idx:idx+n]...)
}
case shaderir.Array:
switch typ.Sub[0].Main {
case shaderir.Bool:
for i := range n {
if (i+byteAlign)%4 == 0 {
values = append(values, uniforms[idx+i])
} else {
values[len(values)-1] |= uniforms[idx+i] << (8 * ((i + byteAlign) % 4))
}
}
case shaderir.Float, shaderir.Int:
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec2, shaderir.IVec2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Vec3, shaderir.IVec3:
values = fillZerosToFitAlignment(values, 4)
for j := 0; j < typ.Length; j++ {
values = append(values, uniforms[idx+3*j:idx+3*(j+1)]...)
values = append(values, 0)
}
case shaderir.Vec4, shaderir.IVec4:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat2:
values = fillZerosToFitAlignment(values, 2)
values = append(values, uniforms[idx:idx+n]...)
case shaderir.Mat3:
values = fillZerosToFitAlignment(values, 4)
for j := 0; j < typ.Length; j++ {
values = append(values, uniforms[idx+9*j:idx+9*j+3]...)
values = append(values, 0)
values = append(values, uniforms[idx+9*j+3:idx+9*j+6]...)
values = append(values, 0)
values = append(values, uniforms[idx+9*j+6:idx+9*j+9]...)
values = append(values, 0)
}
case shaderir.Mat4:
values = fillZerosToFitAlignment(values, 4)
values = append(values, uniforms[idx:idx+n]...)
default:
panic(fmt.Sprintf("metal: not implemented type for uniform variables: %s", typ.String()))
}
default:
panic(fmt.Sprintf("metal: not implemented type for uniform variables: %s", typ.String()))
}
idx += n
if typ.Main == shaderir.Bool || (typ.Main == shaderir.Array && typ.Sub[0].Main == shaderir.Bool) {
byteAlign += n
byteAlign %= 4
} else {
byteAlign = 0
}
}
return values
}
@@ -0,0 +1,39 @@
// Copyright 2024 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.
package mtl
import (
"unsafe"
"github.com/ebitengine/purego"
)
var libSystem uintptr
var (
dispatchDataCreate func(buffer unsafe.Pointer, size uint, queue uintptr, destructor uintptr) uintptr
dispatchRelease func(obj uintptr)
)
func init() {
lib, err := purego.Dlopen("/usr/lib/libSystem.B.dylib", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
libSystem = lib
purego.RegisterLibFunc(&dispatchDataCreate, libSystem, "dispatch_data_create")
purego.RegisterLibFunc(&dispatchRelease, libSystem, "dispatch_release")
}
@@ -36,7 +36,7 @@ import (
// GPUFamily represents the functionality for families of GPUs.
//
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc.
type GPUFamily int
const (
@@ -54,7 +54,7 @@ const (
// FeatureSet defines a specific platform, hardware, and software configuration.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset?language=objc.
type FeatureSet uint16
const (
@@ -92,7 +92,7 @@ const (
// TextureType defines The dimension of each image, including whether multiple images are arranged into an array or
// a cube.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype?language=objc.
type TextureType uint16
const (
@@ -102,7 +102,7 @@ const (
// PixelFormat defines data formats that describe the organization
// and characteristics of individual pixels in a texture.
//
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat.
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc.
type PixelFormat uint16
// The data formats that describe the organization and characteristics
@@ -117,7 +117,7 @@ const (
// PrimitiveType defines geometric primitive types for drawing commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype.
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc.
type PrimitiveType uint8
// Geometric primitive types for drawing commands.
@@ -132,7 +132,7 @@ const (
// LoadAction defines actions performed at the start of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction.
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction?language=objc.
type LoadAction uint8
// Actions performed at the start of a rendering pass for a render command encoder.
@@ -145,7 +145,7 @@ const (
// StoreAction defines actions performed at the end of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction.
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc.
type StoreAction uint8
// Actions performed at the end of a rendering pass for a render command encoder.
@@ -160,7 +160,7 @@ const (
// StorageMode defines the memory location and access permissions of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode.
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc.
type StorageMode uint8
const (
@@ -189,7 +189,7 @@ const (
// ResourceOptions defines optional arguments used to create
// and influence behavior of buffer and texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions.
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc.
type ResourceOptions uint16
const (
@@ -237,7 +237,7 @@ const (
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode.
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode?language=objc.
type CPUCacheMode uint8
const (
@@ -252,7 +252,7 @@ const (
// IndexType is the index type for an index buffer that references vertices of geometric primitives.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc
type IndexType uint8
const (
@@ -358,7 +358,7 @@ const (
// Resource represents a memory allocation for storing specialized data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
// Reference: https://developer.apple.com/documentation/metal/mtlresource?language=objc.
type Resource interface {
// resource returns the underlying id<MTLResource> pointer.
resource() unsafe.Pointer
@@ -366,7 +366,7 @@ type Resource interface {
// RenderPipelineDescriptor configures new RenderPipelineState objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor?language=objc.
type RenderPipelineDescriptor struct {
// VertexFunction is a programmable function that processes individual vertices in a rendering pass.
VertexFunction Function
@@ -384,7 +384,7 @@ type RenderPipelineDescriptor struct {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor?language=objc.
type RenderPipelineColorAttachmentDescriptor struct {
// PixelFormat is the pixel format of the color attachment's texture.
PixelFormat PixelFormat
@@ -404,7 +404,7 @@ type RenderPipelineColorAttachmentDescriptor struct {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor?language=objc.
type RenderPassDescriptor struct {
// ColorAttachments is array of state information for attachments that store color data.
ColorAttachments [1]RenderPassColorAttachmentDescriptor
@@ -416,7 +416,7 @@ type RenderPassDescriptor struct {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor?language=objc.
type RenderPassColorAttachmentDescriptor struct {
RenderPassAttachmentDescriptor
ClearColor ClearColor
@@ -425,7 +425,7 @@ type RenderPassColorAttachmentDescriptor struct {
// RenderPassStencilAttachment describes a stencil render target that serves as the output
// destination for stencil pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor?language=objc.
type RenderPassStencilAttachment struct {
RenderPassAttachmentDescriptor
}
@@ -433,7 +433,7 @@ type RenderPassStencilAttachment struct {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor?language=objc.
type RenderPassAttachmentDescriptor struct {
LoadAction LoadAction
StoreAction StoreAction
@@ -442,14 +442,14 @@ type RenderPassAttachmentDescriptor struct {
// ClearColor is an RGBA value used for a color pixel.
//
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor.
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor?language=objc.
type ClearColor struct {
Red, Green, Blue, Alpha float64
}
// TextureDescriptor configures new Texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor?language=objc.
type TextureDescriptor struct {
TextureType TextureType
PixelFormat PixelFormat
@@ -462,7 +462,7 @@ type TextureDescriptor struct {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtldevice?language=objc.
type Device struct {
device objc.ID
@@ -484,7 +484,6 @@ var (
)
var (
sel_class = objc.RegisterName("class")
sel_length = objc.RegisterName("length")
sel_isHeadless = objc.RegisterName("isHeadless")
sel_isLowPower = objc.RegisterName("isLowPower")
@@ -493,6 +492,7 @@ var (
sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:")
sel_newCommandQueue = objc.RegisterName("newCommandQueue")
sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:")
sel_newLibraryWithData_error = objc.RegisterName("newLibraryWithData:error:")
sel_release = objc.RegisterName("release")
sel_retain = objc.RegisterName("retain")
sel_new = objc.RegisterName("new")
@@ -567,7 +567,7 @@ var (
// CreateSystemDefaultDevice returns the preferred system default Metal device.
//
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice.
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice?language=objc.
func CreateSystemDefaultDevice() (Device, error) {
metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
@@ -607,37 +607,36 @@ func (d Device) Device() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Point
// RespondsToSelector returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.
//
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector?language=objc.
func (d Device) RespondsToSelector(sel objc.SEL) bool {
return d.device.Send(sel_respondsToSelector, sel) != 0
}
// SupportsFamily returns a Boolean value that indicates whether the GPU device supports the feature set of a specific GPU family.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily?language=objc.
func (d Device) SupportsFamily(gpuFamily GPUFamily) bool {
return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0
}
// SupportsFeatureSet reports whether device d supports feature set fs.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset?language=objc.
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
return d.device.Send(sel_supportsFeatureSet, uintptr(fs)) != 0
}
// MakeCommandQueue creates a serial command submission queue.
// NewCommandQueue creates a queue you use to submit rendering and computation commands to a GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue.
func (d Device) MakeCommandQueue() CommandQueue {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-newcommandqueue?language=objc.
func (d Device) NewCommandQueue() CommandQueue {
return CommandQueue{d.device.Send(sel_newCommandQueue)}
}
// MakeLibrary creates a new library that contains
// the functions stored in the specified source string.
// NewLibraryWithSource synchronously creates a Metal library instance by compiling the functions in a source string.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary.
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-newlibrarywithsource?language=objc.
func (d Device) NewLibraryWithSource(source string, opt CompileOptions) (Library, error) {
var err cocoa.NSError
l := d.device.Send(
sel_newLibraryWithSource_options_error,
@@ -652,10 +651,31 @@ func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error)
return Library{l}, nil
}
// MakeRenderPipelineState creates a render pipeline state object.
// NewLibraryWithData Creates a Metal library instance that contains the functions in a precompiled Metal library.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate.
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433391-newlibrarywithdata?language=objc.
func (d Device) NewLibraryWithData(buffer []byte) (Library, error) {
defer runtime.KeepAlive(buffer)
data := dispatchDataCreate(unsafe.Pointer(&buffer[0]), uint(len(buffer)), 0, 0)
defer dispatchRelease(data)
var err cocoa.NSError
l := d.device.Send(
sel_newLibraryWithData_error,
data,
unsafe.Pointer(&err),
)
if l == 0 {
return Library{}, errors.New(cocoa.NSString{ID: err.Send(sel_localizedDescription)}.String())
}
return Library{l}, nil
}
// NewRenderPipelineStateWithDescriptor synchronously creates a render pipeline state.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-newrenderpipelinestatewithdescri?language=objc.
func (d Device) NewRenderPipelineStateWithDescriptor(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
renderPipelineDescriptor := objc.ID(class_MTLRenderPipelineDescriptor).Send(sel_new)
renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function)
renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function)
@@ -683,26 +703,24 @@ func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPip
return RenderPipelineState{renderPipelineState}, nil
}
// MakeBufferWithBytes allocates a new buffer of a given length
// and initializes its contents by copying existing data into it.
// NewBufferWithBytes 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) MakeBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes?language=objc.
func (d Device) NewBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithBytes_length_options, bytes, length, uintptr(opt))}
}
// MakeBufferWithLength allocates a new zero-filled buffer of a given length.
// NewBufferWithLength allocates a new zero-filled buffer of a given length.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength
func (d Device) MakeBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength?language=objc.
func (d Device) NewBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))}
}
// MakeTexture creates a texture object with privately owned storage
// that contains texture state.
// NewTextureWithDescriptor creates a new texture instance.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture.
func (d Device) MakeTexture(td TextureDescriptor) Texture {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-newtexturewithdescriptor?language=objc.
func (d Device) NewTextureWithDescriptor(td TextureDescriptor) Texture {
textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new)
textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType))
textureDescriptor.Send(sel_setPixelFormat, uintptr(td.PixelFormat))
@@ -717,10 +735,10 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
}
}
// MakeDepthStencilState creates a new object that contains depth and stencil test state.
// NewDepthStencilStateWithDescriptor creates a depth-stencil state instance.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-makedepthstencilstate
func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilState {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-newdepthstencilstatewithdescript?language=objc.
func (d Device) NewDepthStencilStateWithDescriptor(dsd DepthStencilDescriptor) DepthStencilState {
depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new)
backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil)
backFaceStencil.Send(sel_setStencilFailureOperation, uintptr(dsd.BackFaceStencil.StencilFailureOperation))
@@ -742,14 +760,14 @@ func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilSt
// CompileOptions specifies optional compilation settings for
// the graphics or compute functions within a library.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions?language=objc.
type CompileOptions struct {
// TODO.
}
// Drawable is a displayable resource that can be rendered or written to.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
// Reference: https://developer.apple.com/documentation/metal/mtldrawable?language=objc.
type Drawable interface {
// Drawable returns the underlying id<MTLDrawable> pointer.
Drawable() unsafe.Pointer
@@ -758,7 +776,7 @@ type Drawable interface {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc.
type CommandQueue struct {
commandQueue objc.ID
}
@@ -767,17 +785,17 @@ func (cq CommandQueue) Release() {
cq.commandQueue.Send(sel_release)
}
// MakeCommandBuffer creates a command buffer.
// CommandBuffer returns a command buffer from the command queue that maintains strong references to resources.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer.
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-commandbuffer?language=objc.
func (cq CommandQueue) CommandBuffer() CommandBuffer {
return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)}
}
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc.
type CommandBuffer struct {
commandBuffer objc.ID
}
@@ -792,55 +810,49 @@ func (cb CommandBuffer) Release() {
// Status returns the current stage in the lifetime of the command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status?language=objc.
func (cb CommandBuffer) Status() CommandBufferStatus {
return CommandBufferStatus(cb.commandBuffer.Send(sel_status))
}
// PresentDrawable registers a drawable presentation to occur as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable?language=objc.
func (cb CommandBuffer) PresentDrawable(d Drawable) {
cb.commandBuffer.Send(sel_presentDrawable, d.Drawable())
}
// Commit commits this command buffer for execution as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit?language=objc.
func (cb CommandBuffer) Commit() {
cb.commandBuffer.Send(sel_commit)
}
// WaitUntilCompleted waits for the execution of this command buffer to complete.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted?language=objc.
func (cb CommandBuffer) WaitUntilCompleted() {
cb.commandBuffer.Send(sel_waitUntilCompleted)
}
// WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled?language=objc.
func (cb CommandBuffer) WaitUntilScheduled() {
cb.commandBuffer.Send(sel_waitUntilScheduled)
}
// MakeRenderCommandEncoder creates an encoder object that can
// encode graphics rendering commands into this command buffer.
// RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder.
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc.
func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescriptor) RenderCommandEncoder {
var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new)
var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0)
colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction))
colorAttachments0.Send(sel_setStoreAction, int(rpd.ColorAttachments[0].StoreAction))
colorAttachments0.Send(sel_setTexture, rpd.ColorAttachments[0].Texture.texture)
sig := cocoa.NSMethodSignature_instanceMethodSignatureForSelector(colorAttachments0.Send(sel_class), sel_setClearColor)
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(colorAttachments0)
inv.SetSelector(sel_setClearColor)
inv.SetArgumentAtIndex(unsafe.Pointer(&rpd.ColorAttachments[0].ClearColor), 2)
inv.Invoke()
colorAttachments0.Send(sel_setClearColor, rpd.ColorAttachments[0].ClearColor)
var stencilAttachment = renderPassDescriptor.Send(sel_stencilAttachment)
stencilAttachment.Send(sel_setLoadAction, int(rpd.StencilAttachment.LoadAction))
stencilAttachment.Send(sel_setStoreAction, int(rpd.StencilAttachment.StoreAction))
@@ -850,11 +862,11 @@ func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) Rende
return RenderCommandEncoder{CommandEncoder{rce}}
}
// MakeBlitCommandEncoder creates an encoder object that can encode
// BlitCommandEncoder 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 {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder?language=objc.
func (cb CommandBuffer) BlitCommandEncoder() BlitCommandEncoder {
ce := cb.commandBuffer.Send(sel_blitCommandEncoder)
return BlitCommandEncoder{CommandEncoder{ce}}
}
@@ -862,14 +874,14 @@ func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
// CommandEncoder is an encoder that writes sequential GPU commands
// into a command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-blitcommandencoder?language=objc.
type CommandEncoder struct {
commandEncoder objc.ID
}
// EndEncoding declares that all command generation from this encoder is completed.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding?language=objc.
func (ce CommandEncoder) EndEncoding() {
ce.commandEncoder.Send(sel_endEncoding)
}
@@ -877,7 +889,7 @@ func (ce CommandEncoder) EndEncoding() {
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands
// and executes graphics functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder?language=objc.
type RenderCommandEncoder struct {
CommandEncoder
}
@@ -888,41 +900,33 @@ func (rce RenderCommandEncoder) Release() {
// SetRenderPipelineState sets the current render pipeline state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate?language=objc.
func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
rce.commandEncoder.Send(sel_setRenderPipelineState, rps.renderPipelineState)
}
func (rce RenderCommandEncoder) SetViewport(viewport Viewport) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLViewport=dddddd}"))
inv.SetTarget(rce.commandEncoder)
inv.SetSelector(sel_setViewport)
inv.SetArgumentAtIndex(unsafe.Pointer(&viewport), 2)
inv.Invoke()
rce.commandEncoder.Send(sel_setViewport, viewport)
}
// SetScissorRect sets the scissor rectangle for a fragment scissor test.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect?language=objc.
func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}"))
inv.SetTarget(rce.commandEncoder)
inv.SetSelector(sel_setScissorRect)
inv.SetArgumentAtIndex(unsafe.Pointer(&scissorRect), 2)
inv.Invoke()
rce.commandEncoder.Send(sel_setScissorRect, scissorRect)
}
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer?language=objc.
func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index)
}
// SetVertexBytes sets a block of data for the vertex function.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes?language=objc.
func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
rce.commandEncoder.Send(sel_setVertexBytes_length_atIndex, bytes, length, index)
}
@@ -933,7 +937,7 @@ func (rce RenderCommandEncoder) SetFragmentBytes(bytes unsafe.Pointer, length ui
// 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
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture?language=objc.
func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) {
rce.commandEncoder.Send(sel_setFragmentTexture_atIndex, texture.texture, index)
}
@@ -944,7 +948,7 @@ func (rce RenderCommandEncoder) SetBlendColor(red, green, blue, alpha float32) {
// SetDepthStencilState sets the depth and stencil test state.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate?language=objc.
func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthStencilState) {
rce.commandEncoder.Send(sel_setDepthStencilState, depthStencilState.depthStencilState)
}
@@ -952,7 +956,7 @@ func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthSten
// DrawPrimitives renders one instance of primitives using vertex data
// in contiguous array elements.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives?language=objc.
func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
rce.commandEncoder.Send(sel_drawPrimitives_vertexStart_vertexCount, uintptr(typ), vertexStart, vertexCount)
}
@@ -969,7 +973,7 @@ func (rce RenderCommandEncoder) DrawIndexedPrimitives(typ PrimitiveType, indexCo
// BlitCommandEncoder is an encoder that specifies resource copy
// and resource synchronization commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder?language=objc.
type BlitCommandEncoder struct {
CommandEncoder
}
@@ -977,7 +981,7 @@ type BlitCommandEncoder struct {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize?language=objc.
func (bce BlitCommandEncoder) Synchronize(resource Resource) {
if runtime.GOOS == "ios" {
return
@@ -985,6 +989,9 @@ func (bce BlitCommandEncoder) Synchronize(resource Resource) {
bce.commandEncoder.Send(sel_synchronizeResource, resource.resource())
}
// SynchronizeTexture encodes a command that synchronizes a part of the CPUs copy of a texture so that it matches the GPUs copy.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400757-synchronizetexture?language=objc.
func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) {
if runtime.GOOS == "ios" {
return
@@ -992,7 +999,11 @@ func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, lev
bce.commandEncoder.Send(sel_synchronizeTexture_slice_level, texture.texture, slice, level)
}
// CopyFromTexture encodes a command that copies image data from a textures slice into another slice.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture?language=objc.
func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice int, sourceLevel int, sourceOrigin Origin, sourceSize Size, destinationTexture Texture, destinationSlice int, destinationLevel int, destinationOrigin Origin) {
// copyFromTexture requires so many arguments that Send doesn't work (#3135).
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:@QQ{MTLOrigin=qqq}{MTLSize=qqq}@QQ{MTLOrigin=qqq}"))
inv.SetTarget(bce.commandEncoder)
inv.SetSelector(sel_copyFromTexture_sourceSlice_sourceLevel_sourceOrigin_sourceSize_toTexture_destinationSlice_destinationLevel_destinationOrigin)
@@ -1010,15 +1021,15 @@ func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice
// Library is a collection of compiled graphics or compute functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
// Reference: https://developer.apple.com/documentation/metal/mtllibrary?language=objc.
type Library struct {
library objc.ID
}
// MakeFunction returns a pre-compiled, non-specialized function.
// NewFunctionWithName 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) {
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-newfunctionwithname?language=objc.
func (l Library) NewFunctionWithName(name string) (Function, error) {
f := l.library.Send(sel_newFunctionWithName,
cocoa.NSString_alloc().InitWithUTF8String(name).ID,
)
@@ -1028,10 +1039,14 @@ func (l Library) MakeFunction(name string) (Function, error) {
return Function{f}, nil
}
func (l Library) Release() {
l.library.Send(sel_release)
}
// Texture is a memory allocation for storing formatted
// image data that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
// Reference: https://developer.apple.com/documentation/metal/mtltexture?language=objc.
type Texture struct {
texture objc.ID
}
@@ -1042,7 +1057,9 @@ func NewTexture(texture objc.ID) Texture {
}
// resource implements the Resource interface.
func (t Texture) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture)) }
func (t Texture) resource() unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture))
}
func (t Texture) Release() {
t.texture.Send(sel_release)
@@ -1051,42 +1068,28 @@ func (t Texture) Release() {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes?language=objc.
func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, level int) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:^vQ{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q"))
inv.SetTarget(t.texture)
inv.SetSelector(sel_getBytes_bytesPerRow_fromRegion_mipmapLevel)
inv.SetArgumentAtIndex(unsafe.Pointer(&pixelBytes), 2)
inv.SetArgumentAtIndex(unsafe.Pointer(&bytesPerRow), 3)
inv.SetArgumentAtIndex(unsafe.Pointer(&region), 4)
inv.SetArgumentAtIndex(unsafe.Pointer(&level), 5)
inv.Invoke()
t.texture.Send(sel_getBytes_bytesPerRow_fromRegion_mipmapLevel, pixelBytes, bytesPerRow, region, 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
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion?language=objc.
func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Pointer, bytesPerRow int) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q^vQ"))
inv.SetTarget(t.texture)
inv.SetSelector(sel_replaceRegion_mipmapLevel_withBytes_bytesPerRow)
inv.SetArgumentAtIndex(unsafe.Pointer(&region), 2)
inv.SetArgumentAtIndex(unsafe.Pointer(&level), 3)
inv.SetArgumentAtIndex(unsafe.Pointer(&pixelBytes), 4)
inv.SetArgumentAtIndex(unsafe.Pointer(&bytesPerRow), 5)
inv.Invoke()
t.texture.Send(sel_replaceRegion_mipmapLevel_withBytes_bytesPerRow, region, level, pixelBytes, bytesPerRow)
}
// Width is the width of the texture image for the base level mipmap, in pixels.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width?language=objc.
func (t Texture) Width() int {
return int(t.texture.Send(sel_width))
}
// Height is the height of the texture image for the base level mipmap, in pixels.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height?language=objc.
func (t Texture) Height() int {
return int(t.texture.Send(sel_height))
}
@@ -1094,13 +1097,19 @@ func (t Texture) Height() int {
// Buffer is a memory allocation for storing unformatted data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer.
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer?language=objc.
type Buffer struct {
buffer objc.ID
}
func (b Buffer) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer)) }
// resource implements the Resource interface.
func (b Buffer) resource() unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer))
}
// Length returns the logical size of the buffer, in bytes.
//
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer/1515373-length?language=objc.
func (b Buffer) Length() uintptr {
return uintptr(b.buffer.Send(sel_length))
}
@@ -1121,13 +1130,9 @@ func (b Buffer) Release() {
b.buffer.Send(sel_release)
}
func (b Buffer) Native() unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer))
}
// Function represents a programmable graphics or compute function executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfunction.
// Reference: https://developer.apple.com/documentation/metal/mtlfunction?language=objc.
type Function struct {
function objc.ID
}
@@ -1139,7 +1144,7 @@ func (f Function) Release() {
// RenderPipelineState contains the graphics functions
// and configuration state used in a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate?language=objc.
type RenderPipelineState struct {
renderPipelineState objc.ID
}
@@ -1151,7 +1156,7 @@ func (r RenderPipelineState) Release() {
// 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.
// Reference: https://developer.apple.com/documentation/metal/mtlregion?language=objc.
type Region struct {
Origin Origin // The location of the upper-left corner of the block.
Size Size // The size of the block.
@@ -1160,25 +1165,36 @@ type Region struct {
// 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 }
// Reference: https://developer.apple.com/documentation/metal/mtlorigin?language=objc.
type Origin struct {
X int
Y int
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 }
// Reference: https://developer.apple.com/documentation/metal/mtlsize?language=objc.
type Size struct {
Width int
Height int
Depth int
}
// RegionMake2D returns a 2D, rectangular region for image or texture data.
//
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d?language=objc.
func RegionMake2D(x, y, width, height int) Region {
return Region{
Origin: Origin{x, y, 0},
Size: Size{width, height, 1},
Origin: Origin{X: x, Y: y, Z: 0},
Size: Size{Width: width, Height: height, Depth: 1},
}
}
// Viewport is a 3D rectangular region for the viewport clipping.
//
// Reference: https://developer.apple.com/documentation/metal/mtlviewport?language=objc.
type Viewport struct {
OriginX float64
OriginY float64
@@ -1188,9 +1204,9 @@ type Viewport struct {
ZFar float64
}
// ScissorRect represents a rectangle for the scissor fragment test.
// ScissorRect is a rectangle for the scissor fragment test.
//
// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect
// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect?language=objc.
type ScissorRect struct {
X int
Y int
@@ -1200,7 +1216,7 @@ type ScissorRect struct {
// DepthStencilState is a depth and stencil state object that specifies the depth and stencil configuration and operations used in a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate?language=objc.
type DepthStencilState struct {
depthStencilState objc.ID
}
@@ -1211,7 +1227,7 @@ func (d DepthStencilState) Release() {
// DepthStencilDescriptor is an object that configures new MTLDepthStencilState objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor?language=objc.
type DepthStencilDescriptor struct {
// BackFaceStencil is the stencil descriptor for back-facing primitives.
BackFaceStencil StencilDescriptor
@@ -1222,7 +1238,7 @@ type DepthStencilDescriptor struct {
// StencilDescriptor is an object that defines the front-facing or back-facing stencil operations of a depth and stencil state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor
// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor?language=objc.
type StencilDescriptor struct {
// StencilFailureOperation is the operation that is performed to update the values in the stencil attachment when the stencil test fails.
StencilFailureOperation StencilOperation
@@ -16,6 +16,7 @@ package metal
import (
"fmt"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
@@ -23,6 +24,37 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl"
)
type precompiledLibraries struct {
binaries map[shaderir.SourceHash][]byte
m sync.Mutex
}
func (c *precompiledLibraries) put(hash shaderir.SourceHash, bin []byte) {
c.m.Lock()
defer c.m.Unlock()
if c.binaries == nil {
c.binaries = map[shaderir.SourceHash][]byte{}
}
if _, ok := c.binaries[hash]; ok {
panic(fmt.Sprintf("metal: the precompiled library for the hash %s is already registered", hash.String()))
}
c.binaries[hash] = bin
}
func (c *precompiledLibraries) get(hash shaderir.SourceHash) []byte {
c.m.Lock()
defer c.m.Unlock()
return c.binaries[hash]
}
var thePrecompiledLibraries precompiledLibraries
func RegisterPrecompiledLibrary(source []byte, bin []byte) {
thePrecompiledLibraries.put(shaderir.CalcSourceHash(source), bin)
}
type shaderRpsKey struct {
blend graphicsdriver.Blend
stencilMode stencilMode
@@ -33,9 +65,12 @@ type Shader struct {
id graphicsdriver.ShaderID
ir *shaderir.Program
lib mtl.Library
fs mtl.Function
vs mtl.Function
rpss map[shaderRpsKey]mtl.RenderPipelineState
libraryPrecompiled bool
}
func newShader(device mtl.Device, id graphicsdriver.ShaderID, program *shaderir.Program) (*Shader, error) {
@@ -60,21 +95,42 @@ func (s *Shader) Dispose() {
}
s.vs.Release()
s.fs.Release()
// Do not release s.lib if this is precompiled. This is a shared precompiled library.
if !s.libraryPrecompiled {
s.lib.Release()
}
}
func (s *Shader) init(device mtl.Device) error {
src := msl.Compile(s.ir)
lib, err := device.MakeLibrary(src, mtl.CompileOptions{})
if err != nil {
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src)
var src string
if libBin := thePrecompiledLibraries.get(s.ir.SourceHash); len(libBin) > 0 {
lib, err := device.NewLibraryWithData(libBin)
if err != nil {
return err
}
s.lib = lib
} else {
src = msl.Compile(s.ir)
lib, err := device.NewLibraryWithSource(src, mtl.CompileOptions{})
if err != nil {
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src)
}
s.lib = lib
}
vs, err := lib.MakeFunction(msl.VertexName)
vs, err := s.lib.NewFunctionWithName(msl.VertexName)
if err != nil {
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
if src != "" {
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
}
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w", err)
}
fs, err := lib.MakeFunction(msl.FragmentName)
fs, err := s.lib.NewFunctionWithName(msl.FragmentName)
if err != nil {
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
if src != "" {
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
}
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w", err)
}
s.fs = fs
s.vs = vs
@@ -120,7 +176,7 @@ func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, ste
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
}
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)
rps, err := view.getMTLDevice().NewRenderPipelineStateWithDescriptor(rpld)
if err != nil {
return mtl.RenderPipelineState{}, err
}
@@ -15,12 +15,22 @@
package metal
import (
"runtime/cgo"
"sync"
"time"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
// maximumDrawableCount is the maximum number of drawable objects.
//
// Always use 3 for macOS (#2880, #2883, #3278).
// At least, this should work with MacBook Pro 2020 (Intel) and MacBook Pro 2023 (M3).
const maximumDrawableCount = 3
type view struct {
window uintptr
uiview uintptr
@@ -32,6 +42,19 @@ type view struct {
ml ca.MetalLayer
once sync.Once
caDisplayLink uintptr
metalDisplayLink uintptr
// The following members are used only with CAMetalDisplayLink.
drawableCh chan ca.MetalDrawable
drawableDoneCh chan struct{}
drawableTimer *time.Timer
metalDisplayLinkRunLoop cocoa.NSRunLoop
// The following members are used only with CADisplayLink.
handleToSelf cgo.Handle
fence *fence
}
func (v *view) setDrawableSize(width, height int) {
@@ -58,10 +81,10 @@ func (v *view) colorPixelFormat() mtl.PixelFormat {
return v.ml.PixelFormat()
}
func (v *view) initialize(device mtl.Device) error {
func (v *view) initialize(device mtl.Device, colorSpace graphicsdriver.ColorSpace) error {
v.device = device
ml, err := ca.MakeMetalLayer()
ml, err := ca.NewMetalLayer(colorSpace)
if err != nil {
return err
}
@@ -83,16 +106,39 @@ func (v *view) initialize(device mtl.Device) error {
// nextDrawable took more than one second if the window has other controls like NSTextView (#1029).
v.ml.SetPresentsWithTransaction(false)
v.ml.SetMaximumDrawableCount(v.maximumDrawableCount())
v.ml.SetMaximumDrawableCount(maximumDrawableCount)
if err := v.initializeOS(); err != nil {
return err
}
return nil
}
func (v *view) nextDrawable() ca.MetalDrawable {
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
type fence struct {
value uint64
lastValue uint64
cond *sync.Cond
}
func newFence() *fence {
return &fence{
cond: sync.NewCond(&sync.Mutex{}),
}
}
func (f *fence) wait() {
f.cond.L.Lock()
defer f.cond.L.Unlock()
for f.lastValue >= f.value {
f.cond.Wait()
}
f.lastValue = f.value
}
func (f *fence) advance() {
f.cond.L.Lock()
defer f.cond.L.Unlock()
f.value++
f.cond.Broadcast()
}
@@ -22,11 +22,15 @@ package metal
//
// #import <UIKit/UIKit.h>
//
// #cgo noescape addSublayer
// #cgo nocallback addSublayer
// static void addSublayer(void* view, void* sublayer) {
// CALayer* layer = ((UIView*)view).layer;
// [layer addSublayer:(CALayer*)sublayer];
// }
//
// #cgo noescape setFrame
// #cgo nocallback setFrame
// static void setFrame(void* cametal, void* uiview) {
// __block CGSize size;
// dispatch_sync(dispatch_get_main_queue(), ^{
@@ -39,6 +43,7 @@ import "C"
import (
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
@@ -65,7 +70,20 @@ const (
resourceStorageMode = mtl.ResourceStorageModeShared
)
func (v *view) maximumDrawableCount() int {
// TODO: Is 2 available for iOS?
return 3
func (v *view) nextDrawable() ca.MetalDrawable {
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
}
func (v *view) finishDrawableUsage() {
// Do nothing.
}
func (v *view) initializeOS() error {
// Do nothing.
return nil
}
@@ -17,14 +17,14 @@
package metal
import (
"runtime"
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
)
const kCVReturnSuccess = 0
func (v *view) setWindow(window uintptr) {
// NSView can be updated e.g., fullscreen-state is switched.
v.window = window
@@ -36,8 +36,6 @@ func (v *view) setUIView(uiview uintptr) {
}
func (v *view) update() {
v.ml.SetMaximumDrawableCount(v.maximumDrawableCount())
if !v.windowChanged {
return
}
@@ -55,25 +53,20 @@ const (
resourceStorageMode = mtl.ResourceStorageModeManaged
)
func (v *view) maximumDrawableCount() int {
// Note that the architecture might not be the true reason of the issues (#2880, #2883).
// Hajime tested only MacBook Pro 2020 (Intel) and MacBook Pro 2023 (M3).
// Use 3 for Intel Mac and iOS. With 2, There are some situations that the FPS becomes half, or the FPS becomes too low (#2880).
if runtime.GOARCH == "amd64" {
return 3
func (v *view) initializeOS() error {
if err := v.initDisplayLink(); err != nil {
return err
}
// Use 3 in fullscren.
// Though this might degrade FPS, this is necessary to avoid mysterious rendering delays.
if v.isFullscreen() {
return 3
}
// Use 2 for a Wnidow to avoid mysterious blinking (#2883).
return 2
return nil
}
func (v *view) isFullscreen() bool {
return cocoa.NSWindow{ID: objc.ID(v.window)}.StyleMask()&cocoa.NSWindowStyleMaskFullScreen != 0
func (v *view) waitForDisplayLinkOutputCallback() {
if v.caDisplayLink == 0 && v.metalDisplayLink == 0 {
return
}
if v.caDisplayLink == 0 && v.vsyncDisabled {
// TODO: nextDrawable still waits for the next drawable available, so this should be fixed not to wait.
return
}
v.fence.wait()
}
@@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"image"
"runtime"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
@@ -89,7 +90,6 @@ type (
type (
uniformLocation int32
attribLocation int32
)
const (
@@ -100,19 +100,19 @@ const (
type context struct {
ctx gl.Context
locationCache *locationCache
screenFramebuffer framebufferNative // This might not be the default frame buffer '0' (e.g. iOS).
lastFramebuffer framebufferNative
lastTexture textureNative
lastRenderbuffer renderbufferNative
lastViewportWidth int
lastViewportHeight int
lastBlend graphicsdriver.Blend
maxTextureSize int
maxTextureSizeOnce sync.Once
highp bool
highpOnce sync.Once
initOnce sync.Once
locationCache *locationCache
screenFramebuffer framebufferNative // This might not be the default frame buffer '0' (e.g. iOS).
lastFramebuffer framebufferNative
lastTexture textureNative
lastRenderbuffer renderbufferNative
lastViewportWidth int
lastViewportHeight int
lastBlend graphicsdriver.Blend
maxTextureSize int
maxTextureSizeOnce sync.Once
initOnce sync.Once
hasKHRParallelShaderCompile bool
hasKHRParallelShaderCompileOnce sync.Once
}
func (c *context) bindTexture(t textureNative) {
@@ -141,14 +141,14 @@ func (c *context) bindFramebuffer(f framebufferNative) {
func (c *context) setViewport(f *framebuffer) {
c.bindFramebuffer(f.native)
if c.lastViewportWidth == f.width && c.lastViewportHeight == f.height {
if c.lastViewportWidth == f.viewportWidth && c.lastViewportHeight == f.viewportHeight {
return
}
// On some environments, viewport size must be within the framebuffer size.
// e.g. Edge (#71), Chrome on GPD Pocket (#420), macOS Mojave (#691).
// Use the same size of the framebuffer here.
c.ctx.Viewport(0, 0, int32(f.width), int32(f.height))
c.ctx.Viewport(0, 0, int32(f.viewportWidth), int32(f.viewportHeight))
// glViewport must be called at least at every frame on iOS.
// As the screen framebuffer is the last render target, next SetViewport should be
@@ -157,16 +157,16 @@ func (c *context) setViewport(f *framebuffer) {
c.lastViewportWidth = 0
c.lastViewportHeight = 0
} else {
c.lastViewportWidth = f.width
c.lastViewportHeight = f.height
c.lastViewportWidth = f.viewportWidth
c.lastViewportHeight = f.viewportHeight
}
}
func (c *context) newScreenFramebuffer(width, height int) *framebuffer {
return &framebuffer{
native: c.screenFramebuffer,
width: width,
height: height,
native: c.screenFramebuffer,
viewportWidth: width,
viewportHeight: height,
}
}
@@ -319,9 +319,6 @@ func (c *context) newRenderbuffer(width, height int) (renderbufferNative, error)
}
func (c *context) deleteRenderbuffer(r renderbufferNative) {
if !c.ctx.IsRenderbuffer(uint32(r)) {
return
}
if c.lastRenderbuffer == r {
c.lastRenderbuffer = 0
}
@@ -336,19 +333,23 @@ func (c *context) newFramebuffer(texture textureNative, width, height int) (*fra
c.bindFramebuffer(framebufferNative(f))
c.ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, uint32(texture), 0)
if s := c.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
if s != 0 {
return nil, fmt.Errorf("opengl: creating framebuffer failed: %v", s)
if shouldCheckFramebufferStatus() {
if s := c.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
if s != 0 {
return nil, fmt.Errorf("opengl: creating framebuffer failed: %v", s)
}
if e := c.ctx.GetError(); e != gl.NO_ERROR {
return nil, fmt.Errorf("opengl: creating framebuffer failed: (glGetError) %d", e)
}
return nil, fmt.Errorf("opengl: creating framebuffer failed: unknown error")
}
if e := c.ctx.GetError(); e != gl.NO_ERROR {
return nil, fmt.Errorf("opengl: creating framebuffer failed: (glGetError) %d", e)
}
return nil, fmt.Errorf("opengl: creating framebuffer failed: unknown error")
}
return &framebuffer{
native: framebufferNative(f),
width: width,
height: height,
native: framebufferNative(f),
viewportWidth: width,
viewportHeight: height,
}, nil
}
@@ -356,9 +357,13 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e
c.bindFramebuffer(f)
c.ctx.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, uint32(r))
if s := c.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s))
if shouldCheckFramebufferStatus() {
if s := c.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("opengl: glFramebufferRenderbuffer failed: %d", s)
}
}
return nil
}
@@ -366,9 +371,6 @@ func (c *context) deleteFramebuffer(f framebufferNative) {
if f == c.screenFramebuffer {
return
}
if !c.ctx.IsFramebuffer(uint32(f)) {
return
}
// If a framebuffer to be deleted is bound, a newly bound framebuffer
// will be a default framebuffer.
// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteFramebuffers.xml
@@ -389,10 +391,6 @@ func (c *context) newShader(shaderType uint32, source string) (shader, error) {
c.ctx.ShaderSource(s, source)
c.ctx.CompileShader(s)
if c.ctx.GetShaderi(s, gl.COMPILE_STATUS) == gl.FALSE {
log := c.ctx.GetShaderInfoLog(s)
return 0, fmt.Errorf("opengl: shader compile failed: %s", log)
}
return shader(s), nil
}
@@ -411,10 +409,6 @@ func (c *context) newProgram(shaders []shader, attributes []string) (program, er
}
c.ctx.LinkProgram(p)
if c.ctx.GetProgrami(p, gl.LINK_STATUS) == gl.FALSE {
info := c.ctx.GetProgramInfoLog(p)
return 0, fmt.Errorf("opengl: program error: %s", info)
}
return program(p), nil
}
@@ -448,6 +442,8 @@ func (c *context) uniforms(p program, location string, v []uint32, typ shaderir.
}
switch base {
case shaderir.Bool:
c.ctx.Uniform1iv(int32(l), uint32sToInt32s(v))
case shaderir.Float:
c.ctx.Uniform1fv(int32(l), uint32sToFloat32s(v))
case shaderir.Int:
@@ -496,3 +492,22 @@ func (c *context) glslVersion() glsl.GLSLVersion {
}
return glsl.GLSLVersionDefault
}
func shouldCheckFramebufferStatus() bool {
// CheckFramebufferStatus is slow and should be avoided especially in browsers.
// See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#avoid_blocking_api_calls_in_production
//
// TODO: Should this be avoided in all environments?
return runtime.GOOS != "js"
}
func (c *context) hasParallelShaderCompile() bool {
c.hasKHRParallelShaderCompileOnce.Do(func() {
if runtime.GOOS != "js" {
return
}
ext := c.ctx.GetExtension("KHR_parallel_shader_compile")
c.hasKHRParallelShaderCompile = ext != nil
})
return c.hasKHRParallelShaderCompile
}
@@ -0,0 +1,103 @@
// Copyright 2019 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.
package gl
import (
"syscall/js"
)
var (
object = js.Global().Get("Object")
arrayBuffer = js.Global().Get("ArrayBuffer")
uint8Array = js.Global().Get("Uint8Array")
float32Array = js.Global().Get("Float32Array")
int32Array = js.Global().Get("Int32Array")
)
var (
tmpArrayBufferByteLength = 16
// tmpArrayBuffer is a temporary buffer used at gl.readPixels or gl.texSubImage2D.
// The read data is converted to Go's byte slice as soon as possible.
// To avoid often allocating ArrayBuffer, reuse the buffer whenever possible.
tmpArrayBuffer = arrayBuffer.New(tmpArrayBufferByteLength)
// tmpUint8Array is a Uint8ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
tmpUint8Array = uint8Array.New(tmpArrayBuffer)
// tmpFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
tmpFloat32Array = float32Array.New(tmpArrayBuffer)
// tmpInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
tmpInt32Array = int32Array.New(tmpArrayBuffer)
)
func ensureTemporaryArrayBufferSize(byteLength int) {
if bufl := tmpArrayBufferByteLength; bufl < byteLength {
for bufl < byteLength {
bufl *= 2
}
tmpArrayBufferByteLength = bufl
tmpArrayBuffer = arrayBuffer.New(bufl)
tmpUint8Array = uint8Array.New(tmpArrayBuffer)
tmpFloat32Array = float32Array.New(tmpArrayBuffer)
tmpInt32Array = int32Array.New(tmpArrayBuffer)
}
}
// tmpUint8ArrayFromUint8Slice returns a Uint8Array whose length is at least minLength from an uint8 slice.
// Be careful that the length can exceed the given minLength.
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
func tmpUint8ArrayFromUint8Slice(minLength int, data []uint8) js.Value {
ensureTemporaryArrayBufferSize(minLength)
copyUint8SliceToTemporaryArrayBuffer(data)
return tmpUint8Array
}
// tmpUint8ArrayFromUint16Slice returns a Uint8Array whose length is at least minLength from an uint16 slice.
// Be careful that the length can exceed the given minLength.
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
func tmpUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value {
ensureTemporaryArrayBufferSize(minLength * 2)
copySliceToTemporaryArrayBuffer(data)
return tmpUint8Array
}
// tmpUint8ArrayFromFloat32Slice returns a Uint8Array whose length is at least minLength from a float32 slice.
// Be careful that the length can exceed the given minLength.
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
func tmpUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
ensureTemporaryArrayBufferSize(minLength * 4)
copySliceToTemporaryArrayBuffer(data)
return tmpUint8Array
}
// tmpFloat32ArrayFromFloat32Slice returns a Float32Array whose length is at least minLength.
// Be careful that the length can exceed the given minLength.
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
func tmpFloat32ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
ensureTemporaryArrayBufferSize(minLength * 4)
copySliceToTemporaryArrayBuffer(data)
return tmpFloat32Array
}
// tmpInt32ArrayFromInt32Slice returns a Int32Array whose length is at least minLength.
// Be careful that the length can exceed the given minLength.
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
func tmpInt32ArrayFromInt32Slice(minLength int, data []int32) js.Value {
ensureTemporaryArrayBufferSize(minLength * 4)
copySliceToTemporaryArrayBuffer(data)
return tmpInt32Array
}
@@ -23,6 +23,7 @@ const (
BLEND = 0x0BE2
CLAMP_TO_EDGE = 0x812F
COLOR_ATTACHMENT0 = 0x8CE0
COMPLETION_STATUS_KHR = 0x91B1
COMPILE_STATUS = 0x8B81
DECR_WRAP = 0x8508
DEPTH24_STENCIL8 = 0x88F0
@@ -347,6 +347,15 @@ func (d *DebugContext) GetError() uint32 {
return out0
}
func (d *DebugContext) GetExtension(arg0 string) any {
out0 := d.Context.GetExtension(arg0)
fmt.Fprintln(os.Stderr, "GetExtension")
if e := d.Context.GetError(); e != NO_ERROR {
panic(fmt.Sprintf("gl: GetError() returned %d at GetExtension", e))
}
return out0
}
func (d *DebugContext) GetInteger(arg0 uint32) int {
out0 := d.Context.GetInteger(arg0)
fmt.Fprintln(os.Stderr, "GetInteger")
@@ -406,15 +415,6 @@ func (d *DebugContext) IsES() bool {
return out0
}
func (d *DebugContext) IsFramebuffer(arg0 uint32) bool {
out0 := d.Context.IsFramebuffer(arg0)
fmt.Fprintln(os.Stderr, "IsFramebuffer")
if e := d.Context.GetError(); e != NO_ERROR {
panic(fmt.Sprintf("gl: GetError() returned %d at IsFramebuffer", e))
}
return out0
}
func (d *DebugContext) IsProgram(arg0 uint32) bool {
out0 := d.Context.IsProgram(arg0)
fmt.Fprintln(os.Stderr, "IsProgram")
@@ -424,15 +424,6 @@ func (d *DebugContext) IsProgram(arg0 uint32) bool {
return out0
}
func (d *DebugContext) IsRenderbuffer(arg0 uint32) bool {
out0 := d.Context.IsRenderbuffer(arg0)
fmt.Fprintln(os.Stderr, "IsRenderbuffer")
if e := d.Context.GetError(); e != NO_ERROR {
panic(fmt.Sprintf("gl: GetError() returned %d at IsRenderbuffer", e))
}
return out0
}
func (d *DebugContext) LinkProgram(arg0 uint32) {
d.Context.LinkProgram(arg0)
fmt.Fprintln(os.Stderr, "LinkProgram")
@@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: 2014 Eric Woroshow
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !darwin && !js && !windows && !playstation5
//go:build nintendosdk
package gl
@@ -20,298 +20,505 @@ package gl
// typedef ptrdiff_t GLintptr;
// typedef ptrdiff_t GLsizeiptr;
//
// #cgo noescape glowActiveTexture
// #cgo nocallback glowActiveTexture
// static void glowActiveTexture(uintptr_t fnptr, GLenum texture) {
// typedef void (*fn)(GLenum texture);
// ((fn)(fnptr))(texture);
// }
//
// #cgo noescape glowAttachShader
// #cgo nocallback glowAttachShader
// static void glowAttachShader(uintptr_t fnptr, GLuint program, GLuint shader) {
// typedef void (*fn)(GLuint program, GLuint shader);
// ((fn)(fnptr))(program, shader);
// }
//
// #cgo noescape glowBindAttribLocation
// #cgo nocallback glowBindAttribLocation
// static void glowBindAttribLocation(uintptr_t fnptr, GLuint program, GLuint index, const GLchar* name) {
// typedef void (*fn)(GLuint program, GLuint index, const GLchar* name);
// ((fn)(fnptr))(program, index, name);
// }
//
// #cgo noescape glowBindBuffer
// #cgo nocallback glowBindBuffer
// static void glowBindBuffer(uintptr_t fnptr, GLenum target, GLuint buffer) {
// typedef void (*fn)(GLenum target, GLuint buffer);
// ((fn)(fnptr))(target, buffer);
// }
//
// #cgo noescape glowBindFramebuffer
// #cgo nocallback glowBindFramebuffer
// static void glowBindFramebuffer(uintptr_t fnptr, GLenum target, GLuint framebuffer) {
// typedef void (*fn)(GLenum target, GLuint framebuffer);
// ((fn)(fnptr))(target, framebuffer);
// }
//
// #cgo noescape glowBindRenderbuffer
// #cgo nocallback glowBindRenderbuffer
// static void glowBindRenderbuffer(uintptr_t fnptr, GLenum target, GLuint renderbuffer) {
// typedef void (*fn)(GLenum target, GLuint renderbuffer);
// ((fn)(fnptr))(target, renderbuffer);
// }
//
// #cgo noescape glowBindTexture
// #cgo nocallback glowBindTexture
// static void glowBindTexture(uintptr_t fnptr, GLenum target, GLuint texture) {
// typedef void (*fn)(GLenum target, GLuint texture);
// ((fn)(fnptr))(target, texture);
// }
//
// #cgo noescape glowBindVertexArray
// #cgo nocallback glowBindVertexArray
// static void glowBindVertexArray(uintptr_t fnptr, GLuint array) {
// typedef void (*fn)(GLuint array);
// ((fn)(fnptr))(array);
// }
//
// #cgo noescape glowBlendEquationSeparate
// #cgo nocallback glowBlendEquationSeparate
// static void glowBlendEquationSeparate(uintptr_t fnptr, GLenum modeRGB, GLenum modeAlpha) {
// typedef void (*fn)(GLenum modeRGB, GLenum modeAlpha);
// ((fn)(fnptr))(modeRGB, modeAlpha);
// }
//
// #cgo noescape glowBlendFuncSeparate
// #cgo nocallback glowBlendFuncSeparate
// static void glowBlendFuncSeparate(uintptr_t fnptr, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) {
// typedef void (*fn)(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
// ((fn)(fnptr))(srcRGB, dstRGB, srcAlpha, dstAlpha);
// }
//
// #cgo noescape glowBufferData
// #cgo nocallback glowBufferData
// static void glowBufferData(uintptr_t fnptr, GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
// typedef void (*fn)(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
// ((fn)(fnptr))(target, size, data, usage);
// }
//
// #cgo noescape glowBufferSubData
// #cgo nocallback glowBufferSubData
// static void glowBufferSubData(uintptr_t fnptr, GLenum target, GLintptr offset, GLsizeiptr size, const void* data) {
// typedef void (*fn)(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
// ((fn)(fnptr))(target, offset, size, data);
// }
//
// #cgo noescape glowCheckFramebufferStatus
// #cgo nocallback glowCheckFramebufferStatus
// static GLenum glowCheckFramebufferStatus(uintptr_t fnptr, GLenum target) {
// typedef GLenum (*fn)(GLenum target);
// return ((fn)(fnptr))(target);
// }
//
// #cgo noescape glowClear
// #cgo nocallback glowClear
// static void glowClear(uintptr_t fnptr, GLbitfield mask) {
// typedef void (*fn)(GLbitfield mask);
// ((fn)(fnptr))(mask);
// }
//
// #cgo noescape glowColorMask
// #cgo nocallback glowColorMask
// static void glowColorMask(uintptr_t fnptr, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
// typedef void (*fn)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
// ((fn)(fnptr))(red, green, blue, alpha);
// }
//
// #cgo noescape glowCompileShader
// #cgo nocallback glowCompileShader
// static void glowCompileShader(uintptr_t fnptr, GLuint shader) {
// typedef void (*fn)(GLuint shader);
// ((fn)(fnptr))(shader);
// }
//
// #cgo noescape glowCreateProgram
// #cgo nocallback glowCreateProgram
// static GLuint glowCreateProgram(uintptr_t fnptr) {
// typedef GLuint (*fn)();
// return ((fn)(fnptr))();
// }
//
// #cgo noescape glowCreateShader
// #cgo nocallback glowCreateShader
// static GLuint glowCreateShader(uintptr_t fnptr, GLenum type) {
// typedef GLuint (*fn)(GLenum type);
// return ((fn)(fnptr))(type);
// }
//
// #cgo noescape glowDeleteBuffers
// #cgo nocallback glowDeleteBuffers
// static void glowDeleteBuffers(uintptr_t fnptr, GLsizei n, const GLuint* buffers) {
// typedef void (*fn)(GLsizei n, const GLuint* buffers);
// ((fn)(fnptr))(n, buffers);
// }
//
// #cgo noescape glowDeleteFramebuffers
// #cgo nocallback glowDeleteFramebuffers
// static void glowDeleteFramebuffers(uintptr_t fnptr, GLsizei n, const GLuint* framebuffers) {
// typedef void (*fn)(GLsizei n, const GLuint* framebuffers);
// ((fn)(fnptr))(n, framebuffers);
// }
//
// #cgo noescape glowDeleteProgram
// #cgo nocallback glowDeleteProgram
// static void glowDeleteProgram(uintptr_t fnptr, GLuint program) {
// typedef void (*fn)(GLuint program);
// ((fn)(fnptr))(program);
// }
//
// #cgo noescape glowDeleteRenderbuffers
// #cgo nocallback glowDeleteRenderbuffers
// static void glowDeleteRenderbuffers(uintptr_t fnptr, GLsizei n, const GLuint* renderbuffers) {
// typedef void (*fn)(GLsizei n, const GLuint* renderbuffers);
// ((fn)(fnptr))(n, renderbuffers);
// }
//
// #cgo noescape glowDeleteShader
// #cgo nocallback glowDeleteShader
// static void glowDeleteShader(uintptr_t fnptr, GLuint shader) {
// typedef void (*fn)(GLuint shader);
// ((fn)(fnptr))(shader);
// }
//
// #cgo noescape glowDeleteTextures
// #cgo nocallback glowDeleteTextures
// static void glowDeleteTextures(uintptr_t fnptr, GLsizei n, const GLuint* textures) {
// typedef void (*fn)(GLsizei n, const GLuint* textures);
// ((fn)(fnptr))(n, textures);
// }
//
// #cgo noescape glowDeleteVertexArrays
// #cgo nocallback glowDeleteVertexArrays
// static void glowDeleteVertexArrays(uintptr_t fnptr, GLsizei n, const GLuint* arrays) {
// typedef void (*fn)(GLsizei n, const GLuint* arrays);
// ((fn)(fnptr))(n, arrays);
// }
//
// #cgo noescape glowDisable
// #cgo nocallback glowDisable
// static void glowDisable(uintptr_t fnptr, GLenum cap) {
// typedef void (*fn)(GLenum cap);
// ((fn)(fnptr))(cap);
// }
//
// #cgo noescape glowDisableVertexAttribArray
// #cgo nocallback glowDisableVertexAttribArray
// static void glowDisableVertexAttribArray(uintptr_t fnptr, GLuint index) {
// typedef void (*fn)(GLuint index);
// ((fn)(fnptr))(index);
// }
//
// #cgo noescape glowDrawElements
// #cgo nocallback glowDrawElements
// static void glowDrawElements(uintptr_t fnptr, GLenum mode, GLsizei count, GLenum type, const uintptr_t indices) {
// typedef void (*fn)(GLenum mode, GLsizei count, GLenum type, const uintptr_t indices);
// ((fn)(fnptr))(mode, count, type, indices);
// }
//
// #cgo noescape glowEnable
// #cgo nocallback glowEnable
// static void glowEnable(uintptr_t fnptr, GLenum cap) {
// typedef void (*fn)(GLenum cap);
// ((fn)(fnptr))(cap);
// }
//
// #cgo noescape glowEnableVertexAttribArray
// #cgo nocallback glowEnableVertexAttribArray
// static void glowEnableVertexAttribArray(uintptr_t fnptr, GLuint index) {
// typedef void (*fn)(GLuint index);
// ((fn)(fnptr))(index);
// }
//
// #cgo noescape glowFlush
// #cgo nocallback glowFlush
// static void glowFlush(uintptr_t fnptr) {
// typedef void (*fn)();
// ((fn)(fnptr))();
// }
//
// #cgo noescape glowFramebufferRenderbuffer
// #cgo nocallback glowFramebufferRenderbuffer
// static void glowFramebufferRenderbuffer(uintptr_t fnptr, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) {
// typedef void (*fn)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
// ((fn)(fnptr))(target, attachment, renderbuffertarget, renderbuffer);
// }
//
// #cgo noescape glowFramebufferTexture2D
// #cgo nocallback glowFramebufferTexture2D
// static void glowFramebufferTexture2D(uintptr_t fnptr, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {
// typedef void (*fn)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
// ((fn)(fnptr))(target, attachment, textarget, texture, level);
// }
//
// #cgo noescape glowGenBuffers
// #cgo nocallback glowGenBuffers
// static void glowGenBuffers(uintptr_t fnptr, GLsizei n, GLuint* buffers) {
// typedef void (*fn)(GLsizei n, GLuint* buffers);
// ((fn)(fnptr))(n, buffers);
// }
//
// #cgo noescape glowGenFramebuffers
// #cgo nocallback glowGenFramebuffers
// static void glowGenFramebuffers(uintptr_t fnptr, GLsizei n, GLuint* framebuffers) {
// typedef void (*fn)(GLsizei n, GLuint* framebuffers);
// ((fn)(fnptr))(n, framebuffers);
// }
//
// #cgo noescape glowGenRenderbuffers
// #cgo nocallback glowGenRenderbuffers
// static void glowGenRenderbuffers(uintptr_t fnptr, GLsizei n, GLuint* renderbuffers) {
// typedef void (*fn)(GLsizei n, GLuint* renderbuffers);
// ((fn)(fnptr))(n, renderbuffers);
// }
//
// #cgo noescape glowGenTextures
// #cgo nocallback glowGenTextures
// static void glowGenTextures(uintptr_t fnptr, GLsizei n, GLuint* textures) {
// typedef void (*fn)(GLsizei n, GLuint* textures);
// ((fn)(fnptr))(n, textures);
// }
//
// #cgo noescape glowGenVertexArrays
// #cgo nocallback glowGenVertexArrays
// static void glowGenVertexArrays(uintptr_t fnptr, GLsizei n, GLuint* arrays) {
// typedef void (*fn)(GLsizei n, GLuint* arrays);
// ((fn)(fnptr))(n, arrays);
// }
//
// #cgo noescape glowGetError
// #cgo nocallback glowGetError
// static GLenum glowGetError(uintptr_t fnptr) {
// typedef GLenum (*fn)();
// return ((fn)(fnptr))();
// }
//
// #cgo noescape glowGetIntegerv
// #cgo nocallback glowGetIntegerv
// static void glowGetIntegerv(uintptr_t fnptr, GLenum pname, GLint* data) {
// typedef void (*fn)(GLenum pname, GLint* data);
// ((fn)(fnptr))(pname, data);
// }
//
// #cgo noescape glowGetProgramInfoLog
// #cgo nocallback glowGetProgramInfoLog
// static void glowGetProgramInfoLog(uintptr_t fnptr, GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog) {
// typedef void (*fn)(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
// ((fn)(fnptr))(program, bufSize, length, infoLog);
// }
//
// #cgo noescape glowGetProgramiv
// #cgo nocallback glowGetProgramiv
// static void glowGetProgramiv(uintptr_t fnptr, GLuint program, GLenum pname, GLint* params) {
// typedef void (*fn)(GLuint program, GLenum pname, GLint* params);
// ((fn)(fnptr))(program, pname, params);
// }
//
// #cgo noescape glowGetShaderInfoLog
// #cgo nocallback glowGetShaderInfoLog
// static void glowGetShaderInfoLog(uintptr_t fnptr, GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog) {
// typedef void (*fn)(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
// ((fn)(fnptr))(shader, bufSize, length, infoLog);
// }
//
// #cgo noescape glowGetShaderiv
// #cgo nocallback glowGetShaderiv
// static void glowGetShaderiv(uintptr_t fnptr, GLuint shader, GLenum pname, GLint* params) {
// typedef void (*fn)(GLuint shader, GLenum pname, GLint* params);
// ((fn)(fnptr))(shader, pname, params);
// }
//
// #cgo noescape glowGetUniformLocation
// #cgo nocallback glowGetUniformLocation
// static GLint glowGetUniformLocation(uintptr_t fnptr, GLuint program, const GLchar* name) {
// typedef GLint (*fn)(GLuint program, const GLchar* name);
// return ((fn)(fnptr))(program, name);
// }
// static GLboolean glowIsFramebuffer(uintptr_t fnptr, GLuint framebuffer) {
// typedef GLboolean (*fn)(GLuint framebuffer);
// return ((fn)(fnptr))(framebuffer);
// }
//
// #cgo noescape glowIsProgram
// #cgo nocallback glowIsProgram
// static GLboolean glowIsProgram(uintptr_t fnptr, GLuint program) {
// typedef GLboolean (*fn)(GLuint program);
// return ((fn)(fnptr))(program);
// }
// static GLboolean glowIsRenderbuffer(uintptr_t fnptr, GLuint renderbuffer) {
// typedef GLboolean (*fn)(GLuint renderbuffer);
// return ((fn)(fnptr))(renderbuffer);
// }
//
// #cgo noescape glowLinkProgram
// #cgo nocallback glowLinkProgram
// static void glowLinkProgram(uintptr_t fnptr, GLuint program) {
// typedef void (*fn)(GLuint program);
// ((fn)(fnptr))(program);
// }
//
// #cgo noescape glowPixelStorei
// #cgo nocallback glowPixelStorei
// static void glowPixelStorei(uintptr_t fnptr, GLenum pname, GLint param) {
// typedef void (*fn)(GLenum pname, GLint param);
// ((fn)(fnptr))(pname, param);
// }
//
// #cgo noescape glowReadPixels
// #cgo nocallback glowReadPixels
// static void glowReadPixels(uintptr_t fnptr, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
// typedef void (*fn)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
// ((fn)(fnptr))(x, y, width, height, format, type, pixels);
// }
//
// #cgo noescape glowRenderbufferStorage
// #cgo nocallback glowRenderbufferStorage
// static void glowRenderbufferStorage(uintptr_t fnptr, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) {
// typedef void (*fn)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
// ((fn)(fnptr))(target, internalformat, width, height);
// }
//
// #cgo noescape glowScissor
// #cgo nocallback glowScissor
// static void glowScissor(uintptr_t fnptr, GLint x, GLint y, GLsizei width, GLsizei height) {
// typedef void (*fn)(GLint x, GLint y, GLsizei width, GLsizei height);
// ((fn)(fnptr))(x, y, width, height);
// }
//
// #cgo noescape glowShaderSource
// #cgo nocallback glowShaderSource
// static void glowShaderSource(uintptr_t fnptr, GLuint shader, GLsizei count, const GLchar*const* string, const GLint* length) {
// typedef void (*fn)(GLuint shader, GLsizei count, const GLchar*const* string, const GLint* length);
// ((fn)(fnptr))(shader, count, string, length);
// }
//
// #cgo noescape glowStencilFunc
// #cgo nocallback glowStencilFunc
// static void glowStencilFunc(uintptr_t fnptr, GLenum func, GLint ref, GLuint mask) {
// typedef void (*fn)(GLenum func, GLint ref, GLuint mask);
// ((fn)(fnptr))(func, ref, mask);
// }
//
// #cgo noescape glowStencilOpSeparate
// #cgo nocallback glowStencilOpSeparate
// static void glowStencilOpSeparate(uintptr_t fnptr, GLenum face, GLenum fail, GLenum zfail, GLenum zpass) {
// typedef void (*fn)(GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
// ((fn)(fnptr))(face, fail, zfail, zpass);
// }
//
// #cgo noescape glowTexImage2D
// #cgo nocallback glowTexImage2D
// static void glowTexImage2D(uintptr_t fnptr, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) {
// typedef void (*fn)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels);
// ((fn)(fnptr))(target, level, internalformat, width, height, border, format, type, pixels);
// }
//
// #cgo noescape glowTexParameteri
// #cgo nocallback glowTexParameteri
// static void glowTexParameteri(uintptr_t fnptr, GLenum target, GLenum pname, GLint param) {
// typedef void (*fn)(GLenum target, GLenum pname, GLint param);
// ((fn)(fnptr))(target, pname, param);
// }
//
// #cgo noescape glowTexSubImage2D
// #cgo nocallback glowTexSubImage2D
// static void glowTexSubImage2D(uintptr_t fnptr, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) {
// typedef void (*fn)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels);
// ((fn)(fnptr))(target, level, xoffset, yoffset, width, height, format, type, pixels);
// }
//
// #cgo noescape glowUniform1fv
// #cgo nocallback glowUniform1fv
// static void glowUniform1fv(uintptr_t fnptr, GLint location, GLsizei count, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLfloat* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform1i
// #cgo nocallback glowUniform1i
// static void glowUniform1i(uintptr_t fnptr, GLint location, GLint v0) {
// typedef void (*fn)(GLint location, GLint v0);
// ((fn)(fnptr))(location, v0);
// }
//
// #cgo noescape glowUniform1iv
// #cgo nocallback glowUniform1iv
// static void glowUniform1iv(uintptr_t fnptr, GLint location, GLsizei count, const GLint* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLint* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform2fv
// #cgo nocallback glowUniform2fv
// static void glowUniform2fv(uintptr_t fnptr, GLint location, GLsizei count, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLfloat* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform2iv
// #cgo nocallback glowUniform2iv
// static void glowUniform2iv(uintptr_t fnptr, GLint location, GLsizei count, const GLint* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLint* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform3fv
// #cgo nocallback glowUniform3fv
// static void glowUniform3fv(uintptr_t fnptr, GLint location, GLsizei count, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLfloat* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform3iv
// #cgo nocallback glowUniform3iv
// static void glowUniform3iv(uintptr_t fnptr, GLint location, GLsizei count, const GLint* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLint* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform4fv
// #cgo nocallback glowUniform4fv
// static void glowUniform4fv(uintptr_t fnptr, GLint location, GLsizei count, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLfloat* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniform4iv
// #cgo nocallback glowUniform4iv
// static void glowUniform4iv(uintptr_t fnptr, GLint location, GLsizei count, const GLint* value) {
// typedef void (*fn)(GLint location, GLsizei count, const GLint* value);
// ((fn)(fnptr))(location, count, value);
// }
//
// #cgo noescape glowUniformMatrix2fv
// #cgo nocallback glowUniformMatrix2fv
// static void glowUniformMatrix2fv(uintptr_t fnptr, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
// ((fn)(fnptr))(location, count, transpose, value);
// }
//
// #cgo noescape glowUniformMatrix3fv
// #cgo nocallback glowUniformMatrix3fv
// static void glowUniformMatrix3fv(uintptr_t fnptr, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
// ((fn)(fnptr))(location, count, transpose, value);
// }
//
// #cgo noescape glowUniformMatrix4fv
// #cgo nocallback glowUniformMatrix4fv
// static void glowUniformMatrix4fv(uintptr_t fnptr, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
// typedef void (*fn)(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
// ((fn)(fnptr))(location, count, transpose, value);
// }
//
// #cgo noescape glowUseProgram
// #cgo nocallback glowUseProgram
// static void glowUseProgram(uintptr_t fnptr, GLuint program) {
// typedef void (*fn)(GLuint program);
// ((fn)(fnptr))(program);
// }
//
// #cgo noescape glowVertexAttribPointer
// #cgo nocallback glowVertexAttribPointer
// static void glowVertexAttribPointer(uintptr_t fnptr, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const uintptr_t pointer) {
// typedef void (*fn)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const uintptr_t pointer);
// ((fn)(fnptr))(index, size, type, normalized, stride, pointer);
// }
//
// #cgo noescape glowViewport
// #cgo nocallback glowViewport
// static void glowViewport(uintptr_t fnptr, GLint x, GLint y, GLsizei width, GLsizei height) {
// typedef void (*fn)(GLint x, GLint y, GLsizei width, GLsizei height);
// ((fn)(fnptr))(x, y, width, height);
@@ -369,9 +576,7 @@ type defaultContext struct {
gpGetShaderInfoLog C.uintptr_t
gpGetShaderiv C.uintptr_t
gpGetUniformLocation C.uintptr_t
gpIsFramebuffer C.uintptr_t
gpIsProgram C.uintptr_t
gpIsRenderbuffer C.uintptr_t
gpLinkProgram C.uintptr_t
gpPixelStorei C.uintptr_t
gpReadPixels C.uintptr_t
@@ -594,6 +799,10 @@ func (c *defaultContext) GetError() uint32 {
return uint32(ret)
}
func (c *defaultContext) GetExtension(name string) any {
return nil
}
func (c *defaultContext) GetInteger(pname uint32) int {
var dst int32
C.glowGetIntegerv(c.gpGetIntegerv, C.GLenum(pname), (*C.GLint)(unsafe.Pointer(&dst)))
@@ -602,6 +811,9 @@ func (c *defaultContext) GetInteger(pname uint32) int {
func (c *defaultContext) GetProgramInfoLog(program uint32) string {
bufSize := c.GetProgrami(program, INFO_LOG_LENGTH)
if bufSize == 0 {
return ""
}
infoLog := make([]byte, bufSize)
C.glowGetProgramInfoLog(c.gpGetProgramInfoLog, C.GLuint(program), C.GLsizei(bufSize), nil, (*C.GLchar)(unsafe.Pointer(&infoLog[0])))
return string(infoLog)
@@ -615,6 +827,9 @@ func (c *defaultContext) GetProgrami(program uint32, pname uint32) int {
func (c *defaultContext) GetShaderInfoLog(shader uint32) string {
bufSize := c.GetShaderi(shader, INFO_LOG_LENGTH)
if bufSize == 0 {
return ""
}
infoLog := make([]byte, bufSize)
C.glowGetShaderInfoLog(c.gpGetShaderInfoLog, C.GLuint(shader), C.GLsizei(bufSize), nil, (*C.GLchar)(unsafe.Pointer(&infoLog[0])))
return string(infoLog)
@@ -633,21 +848,11 @@ func (c *defaultContext) GetUniformLocation(program uint32, name string) int32 {
return int32(ret)
}
func (c *defaultContext) IsFramebuffer(framebuffer uint32) bool {
ret := C.glowIsFramebuffer(c.gpIsFramebuffer, C.GLuint(framebuffer))
return ret == TRUE
}
func (c *defaultContext) IsProgram(program uint32) bool {
ret := C.glowIsProgram(c.gpIsProgram, C.GLuint(program))
return ret == TRUE
}
func (c *defaultContext) IsRenderbuffer(renderbuffer uint32) bool {
ret := C.glowIsRenderbuffer(c.gpIsRenderbuffer, C.GLuint(renderbuffer))
return ret == TRUE
}
func (c *defaultContext) LinkProgram(program uint32) {
C.glowLinkProgram(c.gpLinkProgram, C.GLuint(program))
}
@@ -819,9 +1024,7 @@ func (c *defaultContext) LoadFunctions() error {
c.gpGetShaderInfoLog = C.uintptr_t(g.get("glGetShaderInfoLog"))
c.gpGetShaderiv = C.uintptr_t(g.get("glGetShaderiv"))
c.gpGetUniformLocation = C.uintptr_t(g.get("glGetUniformLocation"))
c.gpIsFramebuffer = C.uintptr_t(g.get("glIsFramebuffer"))
c.gpIsProgram = C.uintptr_t(g.get("glIsProgram"))
c.gpIsRenderbuffer = C.uintptr_t(g.get("glIsRenderbuffer"))
c.gpLinkProgram = C.uintptr_t(g.get("glLinkProgram"))
c.gpPixelStorei = C.uintptr_t(g.get("glPixelStorei"))
c.gpReadPixels = C.uintptr_t(g.get("glReadPixels"))
@@ -17,8 +17,6 @@ package gl
import (
"fmt"
"syscall/js"
"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
)
type defaultContext struct {
@@ -61,15 +59,14 @@ type defaultContext struct {
fnFramebufferTexture2D js.Value
fnFlush js.Value
fnGetError js.Value
fnGetExtension js.Value
fnGetParameter js.Value
fnGetProgramInfoLog js.Value
fnGetProgramParameter js.Value
fnGetShaderInfoLog js.Value
fnGetShaderParameter js.Value
fnGetUniformLocation js.Value
fnIsFramebuffer js.Value
fnIsProgram js.Value
fnIsRenderbuffer js.Value
fnLinkProgram js.Value
fnPixelStorei js.Value
fnReadPixels js.Value
@@ -191,15 +188,14 @@ func NewDefaultContext(v js.Value) (Context, error) {
fnFramebufferTexture2D: v.Get("framebufferTexture2D").Call("bind", v),
fnFlush: v.Get("flush").Call("bind", v),
fnGetError: v.Get("getError").Call("bind", v),
fnGetExtension: v.Get("getExtension").Call("bind", v),
fnGetParameter: v.Get("getParameter").Call("bind", v),
fnGetProgramInfoLog: v.Get("getProgramInfoLog").Call("bind", v),
fnGetProgramParameter: v.Get("getProgramParameter").Call("bind", v),
fnGetShaderInfoLog: v.Get("getShaderInfoLog").Call("bind", v),
fnGetShaderParameter: v.Get("getShaderParameter").Call("bind", v),
fnGetUniformLocation: v.Get("getUniformLocation").Call("bind", v),
fnIsFramebuffer: v.Get("isFramebuffer").Call("bind", v),
fnIsProgram: v.Get("isProgram").Call("bind", v),
fnIsRenderbuffer: v.Get("isRenderbuffer").Call("bind", v),
fnLinkProgram: v.Get("linkProgram").Call("bind", v),
fnPixelStorei: v.Get("pixelStorei").Call("bind", v),
fnReadPixels: v.Get("readPixels").Call("bind", v),
@@ -292,7 +288,7 @@ func (c *defaultContext) BufferInit(target uint32, size int, usage uint32) {
func (c *defaultContext) BufferSubData(target uint32, offset int, data []byte) {
l := len(data)
arr := jsutil.TemporaryUint8ArrayFromUint8Slice(l, data)
arr := tmpUint8ArrayFromUint8Slice(l, data)
c.fnBufferSubData.Invoke(target, offset, arr, 0, l)
}
@@ -373,7 +369,7 @@ func (c *defaultContext) DeleteTexture(texture uint32) {
func (c *defaultContext) DeleteVertexArray(array uint32) {
c.fnDeleteVertexArray.Invoke(c.vertexArrays.get(array))
c.textures.delete(array)
c.vertexArrays.delete(array)
}
func (c *defaultContext) Disable(cap uint32) {
@@ -412,6 +408,14 @@ func (c *defaultContext) GetError() uint32 {
return uint32(c.fnGetError.Invoke().Int())
}
func (c *defaultContext) GetExtension(name string) any {
ext := c.fnGetExtension.Invoke(name)
if ext.IsNull() || ext.IsUndefined() {
return nil
}
return ext
}
func (c *defaultContext) GetInteger(pname uint32) int {
ret := c.fnGetParameter.Invoke(pname)
switch pname {
@@ -481,18 +485,10 @@ func (c *defaultContext) GetUniformLocation(program uint32, name string) int32 {
return int32((program << 5) | idx)
}
func (c *defaultContext) IsFramebuffer(framebuffer uint32) bool {
return c.fnIsFramebuffer.Invoke(c.framebuffers.get(framebuffer)).Bool()
}
func (c *defaultContext) IsProgram(program uint32) bool {
return c.fnIsProgram.Invoke(c.programs.get(program)).Bool()
}
func (c *defaultContext) IsRenderbuffer(renderbuffer uint32) bool {
return c.fnIsRenderbuffer.Invoke(c.renderbuffers.get(renderbuffer)).Bool()
}
func (c *defaultContext) LinkProgram(program uint32) {
c.fnLinkProgram.Invoke(c.programs.get(program))
}
@@ -506,7 +502,7 @@ func (c *defaultContext) ReadPixels(dst []byte, x int32, y int32, width int32, h
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, 0)
return
}
p := jsutil.TemporaryUint8ArrayFromUint8Slice(len(dst), nil)
p := tmpUint8ArrayFromUint8Slice(len(dst), nil)
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, p)
js.CopyBytesToGo(dst, p)
}
@@ -543,7 +539,7 @@ func (c *defaultContext) TexParameteri(target uint32, pname uint32, param int32)
}
func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {
arr := jsutil.TemporaryUint8ArrayFromUint8Slice(len(pixels), pixels)
arr := tmpUint8ArrayFromUint8Slice(len(pixels), pixels)
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
// GLsizei width, GLsizei height,
// GLenum format, GLenum type, ArrayBufferView pixels, srcOffset);
@@ -552,7 +548,7 @@ func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32
func (c *defaultContext) Uniform1fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniform1fv.Invoke(l, arr, 0, len(value))
}
@@ -563,61 +559,61 @@ func (c *defaultContext) Uniform1i(location int32, v0 int32) {
func (c *defaultContext) Uniform1iv(location int32, value []int32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryInt32Array(len(value), value)
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
c.fnUniform1iv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) Uniform2fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniform2fv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) Uniform2iv(location int32, value []int32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryInt32Array(len(value), value)
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
c.fnUniform2iv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) Uniform3fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniform3fv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) Uniform3iv(location int32, value []int32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryInt32Array(len(value), value)
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
c.fnUniform3iv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) Uniform4fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniform4fv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) Uniform4iv(location int32, value []int32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryInt32Array(len(value), value)
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
c.fnUniform4iv.Invoke(l, arr, 0, len(value))
}
func (c *defaultContext) UniformMatrix2fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniformMatrix2fv.Invoke(l, false, arr, 0, len(value))
}
func (c *defaultContext) UniformMatrix3fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniformMatrix3fv.Invoke(l, false, arr, 0, len(value))
}
func (c *defaultContext) UniformMatrix4fv(location int32, value []float32) {
l := c.getUniformLocation(location)
arr := jsutil.TemporaryFloat32Array(len(value), value)
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
c.fnUniformMatrix4fv.Invoke(l, false, arr, 0, len(value))
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin || windows
//go:build (darwin || freebsd || linux || netbsd || openbsd || windows) && !nintendosdk && !playstation5
package gl
@@ -69,9 +69,7 @@ type defaultContext struct {
gpGetShaderInfoLog uintptr
gpGetShaderiv uintptr
gpGetUniformLocation uintptr
gpIsFramebuffer uintptr
gpIsProgram uintptr
gpIsRenderbuffer uintptr
gpLinkProgram uintptr
gpPixelStorei uintptr
gpReadPixels uintptr
@@ -294,6 +292,10 @@ func (c *defaultContext) GetError() uint32 {
return uint32(ret)
}
func (c *defaultContext) GetExtension(name string) any {
return nil
}
func (c *defaultContext) GetInteger(pname uint32) int {
var dst int32
purego.SyscallN(c.gpGetIntegerv, uintptr(pname), uintptr(unsafe.Pointer(&dst)))
@@ -302,6 +304,9 @@ func (c *defaultContext) GetInteger(pname uint32) int {
func (c *defaultContext) GetProgramInfoLog(program uint32) string {
bufSize := c.GetProgrami(program, INFO_LOG_LENGTH)
if bufSize == 0 {
return ""
}
infoLog := make([]byte, bufSize)
purego.SyscallN(c.gpGetProgramInfoLog, uintptr(program), uintptr(bufSize), 0, uintptr(unsafe.Pointer(&infoLog[0])))
return string(infoLog)
@@ -315,6 +320,9 @@ func (c *defaultContext) GetProgrami(program uint32, pname uint32) int {
func (c *defaultContext) GetShaderInfoLog(shader uint32) string {
bufSize := c.GetShaderi(shader, INFO_LOG_LENGTH)
if bufSize == 0 {
return ""
}
infoLog := make([]byte, bufSize)
purego.SyscallN(c.gpGetShaderInfoLog, uintptr(shader), uintptr(bufSize), 0, uintptr(unsafe.Pointer(&infoLog[0])))
return string(infoLog)
@@ -333,21 +341,11 @@ func (c *defaultContext) GetUniformLocation(program uint32, name string) int32 {
return int32(ret)
}
func (c *defaultContext) IsFramebuffer(framebuffer uint32) bool {
ret, _, _ := purego.SyscallN(c.gpIsFramebuffer, uintptr(framebuffer))
return byte(ret) != 0
}
func (c *defaultContext) IsProgram(program uint32) bool {
ret, _, _ := purego.SyscallN(c.gpIsProgram, uintptr(program))
return byte(ret) != 0
}
func (c *defaultContext) IsRenderbuffer(renderbuffer uint32) bool {
ret, _, _ := purego.SyscallN(c.gpIsRenderbuffer, uintptr(renderbuffer))
return byte(ret) != 0
}
func (c *defaultContext) LinkProgram(program uint32) {
purego.SyscallN(c.gpLinkProgram, uintptr(program))
}
@@ -519,9 +517,7 @@ func (c *defaultContext) LoadFunctions() error {
c.gpGetShaderInfoLog = g.get("glGetShaderInfoLog")
c.gpGetShaderiv = g.get("glGetShaderiv")
c.gpGetUniformLocation = g.get("glGetUniformLocation")
c.gpIsFramebuffer = g.get("glIsFramebuffer")
c.gpIsProgram = g.get("glIsProgram")
c.gpIsRenderbuffer = g.get("glIsRenderbuffer")
c.gpLinkProgram = g.get("glLinkProgram")
c.gpPixelStorei = g.get("glPixelStorei")
c.gpReadPixels = g.get("glReadPixels")
@@ -66,15 +66,14 @@ type Context interface {
FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32)
FramebufferTexture2D(target uint32, attachment uint32, textarget uint32, texture uint32, level int32)
GetError() uint32
GetExtension(name string) any
GetInteger(pname uint32) int
GetProgramInfoLog(program uint32) string
GetProgrami(program uint32, pname uint32) int
GetShaderInfoLog(shader uint32) string
GetShaderi(shader uint32, pname uint32) int
GetUniformLocation(program uint32, name string) int32
IsFramebuffer(framebuffer uint32) bool
IsProgram(program uint32) bool
IsRenderbuffer(renderbuffer uint32) bool
LinkProgram(program uint32)
PixelStorei(pname uint32, param int32)
ReadPixels(dst []byte, x int32, y int32, width int32, height int32, format uint32, xtype uint32)
@@ -38,8 +38,7 @@ func (c *defaultContext) init() error {
return nil
}
// TODO: Use multiple %w-s as of Go 1.20
return fmt.Errorf("gl: failed to load: OpenGL.framework: %v, OpenGLES.framework: %v", errGL, errGLES)
return fmt.Errorf("gl: failed to load: OpenGL.framework: %w, OpenGLES.framework: %w", errGL, errGLES)
}
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
@@ -16,99 +16,84 @@
package gl
// #cgo LDFLAGS: -ldl
//
// #include <dlfcn.h>
// #include <stdlib.h>
//
// static void* getProcAddressGL(void* libGL, const char* name) {
// static void*(*glXGetProcAddress)(const char*);
// if (!glXGetProcAddress) {
// glXGetProcAddress = dlsym(libGL, "glXGetProcAddress");
// if (!glXGetProcAddress) {
// glXGetProcAddress = dlsym(libGL, "glXGetProcAddressARB");
// }
// }
// return glXGetProcAddress(name);
// }
//
// static void* getProcAddressGLES(void* libGLES, const char* name) {
// return dlsym(libGLES, name);
// }
import "C"
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"unsafe"
"github.com/ebitengine/purego"
)
var (
libGL unsafe.Pointer
libGLES unsafe.Pointer
libGL uintptr
libGLES uintptr
)
func (c *defaultContext) init() error {
var preferES bool
if runtime.GOOS == "android" {
preferES = true
}
if !preferES {
for _, t := range strings.Split(os.Getenv("EBITENGINE_OPENGL"), ",") {
switch strings.TrimSpace(t) {
case "es":
preferES = true
break
}
}
}
var errs []error
// Try OpenGL first. OpenGL is preferable as this doesn't cause context losses.
if !preferES {
// Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD.
for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} {
cname := C.CString(name)
lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL)
C.free(unsafe.Pointer(cname))
if lib != nil {
libGL = lib
// Try OpenGL ES first. Some machines like Android and Raspberry Pi might work only with OpenGL ES.
//
// Do not use OpenGL ES for Steam, as overlays might not work properly (#3338).
// With Steam, OpenGL (not ES) should be available anyway.
if os.Getenv("SteamEnv") != "1" {
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err == nil {
libGLES = lib
c.isES = true
return nil
}
errs = append(errs, fmt.Errorf("gl: Dlopen failed: name: %s: %w", name, err))
}
}
// Try OpenGL ES.
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
cname := C.CString(name)
lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL)
C.free(unsafe.Pointer(cname))
if lib != nil {
libGLES = lib
c.isES = true
// Try OpenGL next.
// Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD.
// TODO: Should "libOpenGL.so.0" [1] and "libGLX.so.0" [2] be added? These were added as of GLFW 3.3.9.
// [1] https://github.com/glfw/glfw/commit/55aad3c37b67f17279378db52da0a3ab81bbf26d
// [2] https://github.com/glfw/glfw/commit/c18851f52ec9704eb06464058a600845ec1eada1
for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} {
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err == nil {
libGL = lib
return nil
}
errs = append(errs, fmt.Errorf("gl: Dlopen failed: name: %s: %w", name, err))
}
return fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so")
errs = append([]error{fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so: ")}, errs...)
return errors.Join(errs...)
}
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
if c.isES {
return getProcAddressGLES(name), nil
return getProcAddressGLES(name)
}
return getProcAddressGL(name), nil
return getProcAddressGL(name)
}
func getProcAddressGL(name string) uintptr {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return uintptr(C.getProcAddressGL(libGL, cname))
var glXGetProcAddress func(name string) uintptr
func getProcAddressGL(name string) (uintptr, error) {
if glXGetProcAddress == nil {
if _, err := purego.Dlsym(libGL, "glXGetProcAddress"); err == nil {
purego.RegisterLibFunc(&glXGetProcAddress, libGL, "glXGetProcAddress")
} else if _, err := purego.Dlsym(libGL, "glXGetProcAddressARB"); err == nil {
purego.RegisterLibFunc(&glXGetProcAddress, libGL, "glXGetProcAddressARB")
}
}
if glXGetProcAddress == nil {
return 0, fmt.Errorf("gl: failed to find glXGetProcAddress or glXGetProcAddressARB in libGL.so")
}
return glXGetProcAddress(name), nil
}
func getProcAddressGLES(name string) uintptr {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return uintptr(C.getProcAddressGLES(libGLES, cname))
func getProcAddressGLES(name string) (uintptr, error) {
proc, err := purego.Dlsym(libGLES, name)
if err != nil {
return 0, err
}
return proc, nil
}
@@ -21,6 +21,8 @@ package gl
// #include <stdlib.h>
// #include <EGL/egl.h>
//
// #cgo noescape getProcAddress
// #cgo nocallback getProcAddress
// static void* getProcAddress(const char* name) {
// return eglGetProcAddress(name);
// }
@@ -0,0 +1,40 @@
// Copyright 2019 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.
package gl
import (
"runtime"
"syscall/js"
"unsafe"
)
func copyUint8SliceToTemporaryArrayBuffer(src []uint8) {
if len(src) == 0 {
return
}
js.CopyBytesToJS(tmpUint8Array, src)
}
type numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64
}
func copySliceToTemporaryArrayBuffer[T numeric](src []T) {
if len(src) == 0 {
return
}
js.CopyBytesToJS(tmpUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*int(unsafe.Sizeof(T(0)))))
runtime.KeepAlive(src)
}
@@ -198,7 +198,7 @@ func (g *Graphics) uniformVariableName(idx int) string {
return name
}
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("opengl: shader ID is invalid")
}
@@ -224,7 +224,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
var idx int
for i, typ := range shader.ir.Uniforms {
n := typ.Uint32Count()
n := typ.DwordCount()
g.uniformVars[i].name = g.uniformVariableName(i)
g.uniformVars[i].value = uniforms[idx : idx+n]
g.uniformVars[i].typ = typ
@@ -241,7 +241,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
g.uniformVars[idx].value[13] ^= 1 << 31
}
var imgs [graphics.ShaderImageCount]textureVariable
var imgs [graphics.ShaderSrcImageCount]textureVariable
for i, srcID := range srcIDs {
if srcID == graphicsdriver.InvalidImageID {
continue
@@ -259,7 +259,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
}
g.uniformVars = g.uniformVars[:0]
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
if err := destination.ensureStencilBuffer(); err != nil {
return err
}
@@ -274,14 +274,14 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
int32(dstRegion.Region.Dy()),
)
switch fillRule {
case graphicsdriver.NonZero:
case graphicsdriver.FillRuleNonZero:
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP)
g.context.ctx.StencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP)
g.context.ctx.ColorMask(false, false, false, false)
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.EvenOdd:
case graphicsdriver.FillRuleEvenOdd:
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.INVERT)
@@ -289,7 +289,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
g.context.ctx.StencilFunc(gl.NOTEQUAL, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.KEEP)
g.context.ctx.ColorMask(true, true, true, true)
@@ -298,7 +298,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
indexOffset += dstRegion.IndexCount
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
g.context.ctx.Disable(gl.STENCIL_TEST)
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ebitenginegldebug
//go:build !playstation5 && ebitenginegldebug
package opengl
@@ -27,7 +27,7 @@ type graphicsPlatform struct {
// NewGraphics creates an implementation of graphicsdriver.Graphics for OpenGL.
// The returned graphics value is nil iff the error is not nil.
func NewGraphics(canvas js.Value) (graphicsdriver.Graphics, error) {
func NewGraphics(canvas js.Value, colorSpace graphicsdriver.ColorSpace) (graphicsdriver.Graphics, error) {
var glContext js.Value
attr := js.Global().Get("Object").New()
@@ -41,6 +41,13 @@ func NewGraphics(canvas js.Value) (graphicsdriver.Graphics, error) {
return nil, fmt.Errorf("opengl: getContext for webgl2 failed")
}
switch colorSpace {
case graphicsdriver.ColorSpaceSRGB:
glContext.Set("drawingBufferColorSpace", "srgb")
case graphicsdriver.ColorSpaceDisplayP3:
glContext.Set("drawingBufferColorSpace", "display-p3")
}
ctx, err := gl.NewDefaultContext(glContext)
if err != nil {
return nil, err
@@ -12,20 +12,61 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !android && !ios && !js && !nintendosdk && !playstation5
//go:build (freebsd || linux || netbsd || openbsd) && !android && !nintendosdk && !playstation5
package opengl
import (
"fmt"
"runtime"
"bufio"
"bytes"
"os/exec"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl/gl"
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
)
func isGLXExtensionForGL2Available() bool {
var buf bytes.Buffer
cmd := exec.Command("glxinfo")
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return false
}
const (
indent = " "
ext = "GLX_EXT_create_context_es2_profile"
)
var listingExtensions bool
s := bufio.NewScanner(&buf)
for s.Scan() {
line := s.Text()
if !listingExtensions {
if line == "GLX extensions:" {
listingExtensions = true
}
continue
}
if !strings.HasPrefix(line, indent) {
listingExtensions = false
break
}
for len(line) > 0 {
head, tail, _ := strings.Cut(line, ",")
if strings.TrimSpace(head) == ext {
return true
}
line = tail
}
}
return false
}
type graphicsPlatform struct {
window *glfw.Window
}
@@ -33,10 +74,6 @@ type graphicsPlatform struct {
// NewGraphics creates an implementation of graphicsdriver.Graphics for OpenGL.
// The returned graphics value is nil iff the error is not nil.
func NewGraphics() (graphicsdriver.Graphics, error) {
if microsoftgdk.IsXbox() {
return nil, fmt.Errorf("opengl: OpenGL is not supported on Xbox")
}
ctx, err := gl.NewDefaultContext()
if err != nil {
return nil, err
@@ -60,8 +97,12 @@ func setGLFWClientAPI(isES bool) error {
if err := glfw.WindowHint(glfw.ContextVersionMinor, 0); err != nil {
return err
}
if err := glfw.WindowHint(glfw.ContextCreationAPI, glfw.EGLContextAPI); err != nil {
return err
// Use GLX if the extension allows, or use EGL otherwise.
// Prefer GLX since EGL might not work well on Wayland (#3152).
if !isGLXExtensionForGL2Available() {
if err := glfw.WindowHint(glfw.ContextCreationAPI, glfw.EGLContextAPI); err != nil {
return err
}
}
return nil
}
@@ -75,15 +116,6 @@ func setGLFWClientAPI(isES bool) error {
if err := glfw.WindowHint(glfw.ContextVersionMinor, 2); err != nil {
return err
}
// macOS requires forward-compatible and a core profile.
if runtime.GOOS == "darwin" {
if err := glfw.WindowHint(glfw.OpenGLForwardCompat, glfw.True); err != nil {
return err
}
if err := glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile); err != nil {
return err
}
}
return nil
}
@@ -103,11 +135,11 @@ func (g *Graphics) swapBuffers() error {
// This needs to be called at least after SetMonitor.
// Without SwapInterval after SetMonitor, vsynch doesn't work (#375).
if g.vsync {
if err := glfw.SwapInterval(1); err != nil {
if err := g.window.SwapInterval(1); err != nil {
return err
}
} else {
if err := glfw.SwapInterval(0); err != nil {
if err := g.window.SwapInterval(0); err != nil {
return err
}
}
@@ -0,0 +1,86 @@
// Copyright 2024 The Ebitengine 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.
//go:build darwin && !ios
package opengl
import (
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl/gl"
)
type graphicsPlatform struct {
window *glfw.Window
}
// NewGraphics creates an implementation of graphicsdriver.Graphics for OpenGL.
// The returned graphics value is nil iff the error is not nil.
func NewGraphics() (graphicsdriver.Graphics, error) {
ctx, err := gl.NewDefaultContext()
if err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI); err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.ContextVersionMajor, 3); err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.ContextVersionMinor, 2); err != nil {
return nil, err
}
// macOS requires forward-compatible and a core profile.
if err := glfw.WindowHint(glfw.OpenGLForwardCompat, glfw.True); err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile); err != nil {
return nil, err
}
return newGraphics(ctx), nil
}
func (g *Graphics) SetGLFWWindow(window *glfw.Window) {
g.window = window
}
func (g *Graphics) makeContextCurrent() error {
return g.window.MakeContextCurrent()
}
func (g *Graphics) swapBuffers() error {
// Call SwapIntervals even though vsync is not changed.
// When toggling to fullscreen, vsync state might be reset unexpectedly (#1787).
// 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).
if g.vsync {
if err := g.window.SwapInterval(1); err != nil {
return err
}
} else {
if err := g.window.SwapInterval(0); err != nil {
return err
}
}
if err := g.window.SwapBuffers(); err != nil {
return err
}
return nil
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !ebitenginegldebug
//go:build !playstation5 && !ebitenginegldebug
package opengl
@@ -0,0 +1,84 @@
// Copyright 2024 The Ebitengine 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.
package opengl
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl/gl"
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
)
type graphicsPlatform struct {
window *glfw.Window
}
// NewGraphics creates an implementation of graphicsdriver.Graphics for OpenGL.
// The returned graphics value is nil iff the error is not nil.
func NewGraphics() (graphicsdriver.Graphics, error) {
if microsoftgdk.IsXbox() {
return nil, fmt.Errorf("opengl: OpenGL is not supported on Xbox")
}
ctx, err := gl.NewDefaultContext()
if err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI); err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.ContextVersionMajor, 3); err != nil {
return nil, err
}
if err := glfw.WindowHint(glfw.ContextVersionMinor, 2); err != nil {
return nil, err
}
return newGraphics(ctx), nil
}
func (g *Graphics) SetGLFWWindow(window *glfw.Window) {
g.window = window
}
func (g *Graphics) makeContextCurrent() error {
return g.window.MakeContextCurrent()
}
func (g *Graphics) swapBuffers() error {
// Call SwapIntervals even though vsync is not changed.
// When toggling to fullscreen, vsync state might be reset unexpectedly (#1787).
// 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).
if g.vsync {
if err := g.window.SwapInterval(1); err != nil {
return err
}
} else {
if err := g.window.SwapInterval(0); err != nil {
return err
}
}
if err := g.window.SwapBuffers(); err != nil {
return err
}
return nil
}
@@ -37,10 +37,9 @@ type Image struct {
// framebuffer is a wrapper of OpenGL's framebuffer.
type framebuffer struct {
graphics *Graphics
native framebufferNative
width int
height int
native framebufferNative
viewportWidth int
viewportHeight int
}
func (i *Image) ID() graphicsdriver.ImageID {
@@ -81,7 +80,7 @@ func (i *Image) ReadPixels(args []graphicsdriver.PixelsArgs) error {
return nil
}
func (i *Image) framebufferSize() (int, int) {
func (i *Image) viewportSize() (int, int) {
if i.screen {
// The (default) framebuffer size can't be converted to a power of 2.
// On browsers, i.width and i.height are used as viewport size and
@@ -96,11 +95,12 @@ func (i *Image) ensureFramebuffer() error {
return nil
}
w, h := i.framebufferSize()
w, h := i.viewportSize()
if i.screen {
i.framebuffer = i.graphics.context.newScreenFramebuffer(w, h)
return nil
}
f, err := i.graphics.context.newFramebuffer(i.texture, w, h)
if err != nil {
return err
@@ -118,7 +118,7 @@ func (i *Image) ensureStencilBuffer() error {
return err
}
r, err := i.graphics.context.newRenderbuffer(i.framebufferSize())
r, err := i.graphics.context.newRenderbuffer(i.viewportSize())
if err != nil {
return err
}
@@ -53,28 +53,33 @@ func (a *arrayBufferLayout) names() []string {
return ns
}
// totalBytes returns the size in bytes for one element of the array buffer.
func (a *arrayBufferLayout) totalBytes() int {
// float32Count returns the total float32 count for one element of the array buffer.
func (a *arrayBufferLayout) float32Count() int {
if a.total != 0 {
return a.total
}
t := 0
for _, p := range a.parts {
t += floatSizeInBytes * p.num
t += p.num
}
a.total = t
return a.total
}
func (a *arrayBufferLayout) addPart(part arrayBufferLayoutPart) {
a.parts = append(a.parts, part)
a.total = 0
}
// enable starts using the array buffer.
func (a *arrayBufferLayout) enable(context *context) {
for i := range a.parts {
context.ctx.EnableVertexAttribArray(uint32(i))
}
total := a.totalBytes()
total := a.float32Count()
offset := 0
for i, p := range a.parts {
context.ctx.VertexAttribPointer(uint32(i), int32(p.num), gl.FLOAT, false, int32(total), offset)
context.ctx.VertexAttribPointer(uint32(i), int32(p.num), gl.FLOAT, false, int32(floatSizeInBytes*total), offset)
offset += floatSizeInBytes * p.num
}
}
@@ -88,28 +93,39 @@ func (a *arrayBufferLayout) disable(context *context) {
}
// theArrayBufferLayout is the array buffer layout for Ebitengine.
var theArrayBufferLayout = arrayBufferLayout{
// Note that GL_MAX_VERTEX_ATTRIBS is at least 16.
parts: []arrayBufferLayoutPart{
{
name: "A0",
num: 2,
},
{
name: "A1",
num: 2,
},
{
name: "A2",
num: 4,
},
},
}
var theArrayBufferLayout arrayBufferLayout
func init() {
vertexFloatCount := theArrayBufferLayout.totalBytes() / floatSizeInBytes
if graphics.VertexFloatCount != vertexFloatCount {
panic(fmt.Sprintf("vertex float num must be %d but %d", graphics.VertexFloatCount, vertexFloatCount))
theArrayBufferLayout = arrayBufferLayout{
// Note that GL_MAX_VERTEX_ATTRIBS is at least 16.
parts: []arrayBufferLayoutPart{
{
name: "A0",
num: 2,
},
{
name: "A1",
num: 2,
},
{
name: "A2",
num: 4,
},
},
}
n := theArrayBufferLayout.float32Count()
diff := graphics.VertexFloatCount - n
if diff == 0 {
return
}
if diff%4 != 0 {
panic("opengl: unexpected attribute layout")
}
for i := 0; i < diff/4; i++ {
theArrayBufferLayout.addPart(arrayBufferLayoutPart{
name: fmt.Sprintf("A%d", i+3),
num: 4,
})
}
}
@@ -259,7 +275,7 @@ func (g *Graphics) textureVariableName(idx int) string {
}
// useProgram uses the program (programTexture).
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderImageCount]textureVariable) error {
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderSrcImageCount]textureVariable) error {
if g.state.lastProgram != program {
g.context.ctx.UseProgram(uint32(program))
@@ -276,7 +292,7 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu
if u.value == nil {
continue
}
if got, expected := len(u.value), u.typ.Uint32Count(); got != expected {
if got, expected := len(u.value), u.typ.DwordCount(); got != expected {
// Copy a shaderir.Type value once. Do not pass u.typ directly to fmt.Errorf arguments, or
// the value u would be allocated on heap.
typ := u.typ
@@ -18,6 +18,7 @@ package opengl
import (
"fmt"
"runtime"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl/gl"
@@ -59,13 +60,13 @@ func (s *Shader) compile() error {
vs, err := s.graphics.context.newShader(gl.VERTEX_SHADER, vssrc)
if err != nil {
return fmt.Errorf("opengl: vertex shader compile error: %v, source:\n%s", err, vssrc)
return err
}
defer s.graphics.context.ctx.DeleteShader(uint32(vs))
fs, err := s.graphics.context.newShader(gl.FRAGMENT_SHADER, fssrc)
if err != nil {
return fmt.Errorf("opengl: fragment shader compile error: %v, source:\n%s", err, fssrc)
return err
}
defer s.graphics.context.ctx.DeleteShader(uint32(fs))
@@ -74,6 +75,26 @@ func (s *Shader) compile() error {
return err
}
// Check the shader compile status asynchronously if possible.
// The function 'compile' itself is still blocking, but at least this gives a chance to other goroutines to run
// while waiting for the shader compilation.
if s.graphics.context.hasParallelShaderCompile() {
for s.graphics.context.ctx.GetShaderi(uint32(vs), gl.COMPLETION_STATUS_KHR) != gl.TRUE ||
s.graphics.context.ctx.GetShaderi(uint32(fs), gl.COMPLETION_STATUS_KHR) != gl.TRUE {
runtime.Gosched()
}
}
// Check errors only after linking fails.
// See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#dont_check_shader_compile_status_unless_linking_fails
if s.graphics.context.ctx.GetProgrami(uint32(p), gl.LINK_STATUS) == gl.FALSE {
programInfo := s.graphics.context.ctx.GetProgramInfoLog(uint32(p))
vertexShaderInfo := s.graphics.context.ctx.GetShaderInfoLog(uint32(vs))
fragmentShaderInfo := s.graphics.context.ctx.GetShaderInfoLog(uint32(fs))
return fmt.Errorf("opengl: program error: %s\nvertex shader error: %s\nvertex shader source: %s\nfragment shader error: %s\nfragment shader source: %s",
programInfo, vertexShaderInfo, vssrc, fragmentShaderInfo, fssrc)
}
s.p = p
return nil
}
@@ -14,28 +14,58 @@
//go:build playstation5
// The actual implementation will be provided by -overlay.
// The actual implementation will be provided by github.com/hajimehoshi/uwagaki.
#include "graphics_playstation5.h"
extern "C" ebitengine_Error ebitengine_InitializeGraphics(void) {
extern "C" ebitengine_Error ebitengine_InitializeGraphics(void) { return {}; }
extern "C" ebitengine_Error ebitengine_NewImage(int *image, int width,
int height) {
return {};
}
extern "C" ebitengine_Error ebitengine_NewImage(int* image, int width, int height) {
extern "C" void ebitengine_ReadPixels(int image, uint8_t *pixels,
ebitengine_Region region) {}
extern "C" ebitengine_Error ebitengine_FlushReadPixels(int image) { return {}; }
extern "C" void ebitengine_WritePixels(int image, const uint8_t *pixels,
ebitengine_Region region) {}
extern "C" ebitengine_Error ebitengine_FlushWritePixels(int image) {
return {};
}
extern "C" ebitengine_Error ebitengine_NewScreenFramebufferImage(int* image, int width, int height) {
extern "C" ebitengine_Error
ebitengine_NewScreenFramebufferImage(int *image, int width, int height) {
return {};
}
extern "C" void ebitengine_DisposeImage(int id) {
}
extern "C" void ebitengine_DisposeImage(int id) {}
extern "C" ebitengine_Error ebitengine_NewShader(int* shader, const char* source) {
extern "C" void ebitengine_Begin() {}
extern "C" void ebitengine_End(int present) {}
extern "C" void ebitengine_SetVertices(const float *vertices, int vertex_count,
const uint32_t *indices,
int index_count) {}
extern "C" ebitengine_Error
ebitengine_DrawTriangles(int dst, const int *srcs, int src_count, int shader,
const ebitengine_DstRegion *dst_regions,
int dst_region_count, int index_offset,
ebitengine_Blend blend, const uint32_t *uniforms,
int uniform_count, int fill_rule) {
return {};
}
extern "C" void ebitengine_DisposeShader(int id) {
extern "C" ebitengine_Error ebitengine_NewShader(
int *shader, const char *vertex_header, int vertex_header_size,
const char *vertex_text, int vertex_text_size, const char *pixel_header,
int pixel_header_size, const char *pixel_text, int pixel_text_size) {
return {};
}
extern "C" void ebitengine_DisposeShader(int id) {}
@@ -17,16 +17,24 @@
package playstation5
// #include "graphics_playstation5.h"
// #include <stdlib.h>
import "C"
import (
"fmt"
"runtime"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
//export ebitengine_ProjectionMatrixUniformDwordIndex
func ebitengine_ProjectionMatrixUniformDwordIndex() C.int {
return C.int(graphics.ProjectionMatrixUniformDwordIndex)
}
type playstation5Error struct {
name string
code int
@@ -60,10 +68,16 @@ func (g *Graphics) Initialize() error {
}
func (g *Graphics) Begin() error {
C.ebitengine_Begin()
return nil
}
func (g *Graphics) End(present bool) error {
var cPresent C.int
if present {
cPresent = 1
}
C.ebitengine_End(cPresent)
return nil
}
@@ -71,11 +85,16 @@ func (g *Graphics) SetTransparent(transparent bool) {
}
func (g *Graphics) SetVertices(vertices []float32, indices []uint32) error {
defer runtime.KeepAlive(vertices)
defer runtime.KeepAlive(indices)
C.ebitengine_SetVertices((*C.float)(unsafe.SliceData(vertices)), C.int(len(vertices)), (*C.uint32_t)(unsafe.SliceData(indices)), C.int(len(indices)))
return nil
}
func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
var id C.int
width = graphics.InternalImageSize(width)
height = graphics.InternalImageSize(height)
if err := C.ebitengine_NewImage(&id, C.int(width), C.int(height)); !C.ebitengine_IsErrorNil(&err) {
return nil, newPlaystation5Error("(*playstation5.Graphics).NewImage", err)
}
@@ -98,7 +117,7 @@ func (g *Graphics) SetVsyncEnabled(enabled bool) {
}
func (g *Graphics) NeedsClearingScreen() bool {
return false
return true
}
func (g *Graphics) MaxImageSize() int {
@@ -106,9 +125,15 @@ func (g *Graphics) MaxImageSize() int {
}
func (g *Graphics) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
s := precompiledShaders[program.SourceHash]
defer runtime.KeepAlive(s)
var id C.int
// TODO: Give a source code.
if err := C.ebitengine_NewShader(&id, nil); !C.ebitengine_IsErrorNil(&err) {
if err := C.ebitengine_NewShader(&id,
(*C.char)(unsafe.Pointer(unsafe.SliceData(s.vertexHeader))), C.int(len(s.vertexHeader)),
(*C.char)(unsafe.Pointer(unsafe.SliceData(s.vertexText))), C.int(len(s.vertexText)),
(*C.char)(unsafe.Pointer(unsafe.SliceData(s.pixelHeader))), C.int(len(s.pixelHeader)),
(*C.char)(unsafe.Pointer(unsafe.SliceData(s.pixelText))), C.int(len(s.pixelText))); !C.ebitengine_IsErrorNil(&err) {
return nil, newPlaystation5Error("(*playstation5.Graphics).NewShader", err)
}
return &Shader{
@@ -116,7 +141,43 @@ func (g *Graphics) NewShader(program *shaderir.Program) (graphicsdriver.Shader,
}, nil
}
func (g *Graphics) DrawTriangles(dst graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, shader graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dst graphicsdriver.ImageID, srcs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shader graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
cSrcs := make([]C.int, len(srcs))
for i, src := range srcs {
cSrcs[i] = C.int(src)
}
defer runtime.KeepAlive(cSrcs)
cDstRegions := make([]C.ebitengine_DstRegion, len(dstRegions))
defer runtime.KeepAlive(cDstRegions)
for i, r := range dstRegions {
cDstRegions[i] = C.ebitengine_DstRegion{
min_x: C.int(r.Region.Min.X),
min_y: C.int(r.Region.Min.Y),
max_x: C.int(r.Region.Max.X),
max_y: C.int(r.Region.Max.Y),
index_count: C.int(r.IndexCount),
}
}
cBlend := C.ebitengine_Blend{
factor_src_rgb: C.uint8_t(blend.BlendFactorSourceRGB),
factor_src_alpha: C.uint8_t(blend.BlendFactorSourceAlpha),
factor_dst_rgb: C.uint8_t(blend.BlendFactorDestinationRGB),
factor_dst_alpha: C.uint8_t(blend.BlendFactorDestinationAlpha),
operation_rgb: C.uint8_t(blend.BlendOperationRGB),
operation_alpha: C.uint8_t(blend.BlendOperationAlpha),
}
cUniforms := make([]C.uint32_t, len(uniforms))
defer runtime.KeepAlive(cUniforms)
for i, u := range uniforms {
cUniforms[i] = C.uint32_t(u)
}
if err := C.ebitengine_DrawTriangles(C.int(dst), unsafe.SliceData(cSrcs), C.int(len(cSrcs)), C.int(shader), unsafe.SliceData(cDstRegions), C.int(len(cDstRegions)), C.int(indexOffset), cBlend, unsafe.SliceData(cUniforms), C.int(len(cUniforms)), C.int(fillRule)); !C.ebitengine_IsErrorNil(&err) {
return newPlaystation5Error("(*playstation5.Graphics).DrawTriangles", err)
}
return nil
}
@@ -133,12 +194,34 @@ func (i *Image) Dispose() {
}
func (i *Image) ReadPixels(args []graphicsdriver.PixelsArgs) error {
// TODO: Implement this
for _, a := range args {
region := C.ebitengine_Region{
min_x: C.int(a.Region.Min.X),
min_y: C.int(a.Region.Min.Y),
max_x: C.int(a.Region.Max.X),
max_y: C.int(a.Region.Max.Y),
}
C.ebitengine_ReadPixels(C.int(i.id), (*C.uint8_t)(unsafe.Pointer(unsafe.SliceData(a.Pixels))), region)
}
if err := C.ebitengine_FlushReadPixels(C.int(i.id)); !C.ebitengine_IsErrorNil(&err) {
return newPlaystation5Error("(*playstation5.Image).ReadPixels", err)
}
return nil
}
func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
// TODO: Implement this
for _, a := range args {
region := C.ebitengine_Region{
min_x: C.int(a.Region.Min.X),
min_y: C.int(a.Region.Min.Y),
max_x: C.int(a.Region.Max.X),
max_y: C.int(a.Region.Max.Y),
}
C.ebitengine_WritePixels(C.int(i.id), (*C.uint8_t)(unsafe.Pointer(unsafe.SliceData(a.Pixels))), region)
}
if err := C.ebitengine_FlushWritePixels(C.int(i.id)); !C.ebitengine_IsErrorNil(&err) {
return newPlaystation5Error("(*playstation5.Image).WritePixels", err)
}
return nil
}
@@ -19,26 +19,100 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int ebitengine_ProjectionMatrixUniformDwordIndex();
typedef struct ebitengine_Error {
const char* message;
int code;
const char *message;
int code;
} ebitengine_Error;
static bool ebitengine_IsErrorNil(ebitengine_Error* err) {
static bool ebitengine_IsErrorNil(ebitengine_Error *err) {
return err->message == NULL && err->code == 0;
}
typedef struct ebitengine_Region {
int min_x;
int min_y;
int max_x;
int max_y;
} ebitengine_Region;
typedef struct ebitengine_DstRegion {
int min_x;
int min_y;
int max_x;
int max_y;
int index_count;
} ebitengine_DstRegion;
// kBlendFactor* and kBlendOperation* must be synced with
// internal/graphicsdriver/blend.go.
enum {
kBlendFactorZero = 0,
kBlendFactorOne = 1,
kBlendFactorSourceColor = 2,
kBlendFactorOneMinusSourceColor = 3,
kBlendFactorSourceAlpha = 4,
kBlendFactorOneMinusSourceAlpha = 5,
kBlendFactorDestinationColor = 6,
kBlendFactorOneMinusDestinationColor = 7,
kBlendFactorDestinationAlpha = 8,
kBlendFactorOneMinusDestinationAlpha = 9,
kBlendFactorSourceAlphaSaturated = 10,
};
enum {
kBlendOperationAdd = 0,
kBlendOperationSubtract = 1,
kBlendOperationReverseSubtract = 2,
kBlendOperationMin = 3,
kBlendOperationMax = 4,
};
typedef struct ebitengine_Blend {
uint8_t factor_src_rgb;
uint8_t factor_src_alpha;
uint8_t factor_dst_rgb;
uint8_t factor_dst_alpha;
uint8_t operation_rgb;
uint8_t operation_alpha;
} ebitengine_Blend;
ebitengine_Error ebitengine_InitializeGraphics(void);
ebitengine_Error ebitengine_NewImage(int* image, int width, int height);
ebitengine_Error ebitengine_NewScreenFramebufferImage(int* image, int width, int height);
ebitengine_Error ebitengine_NewImage(int *image, int width, int height);
ebitengine_Error ebitengine_NewScreenFramebufferImage(int *image, int width,
int height);
void ebitengine_ReadPixels(int image, uint8_t *pixels,
ebitengine_Region region);
ebitengine_Error ebitengine_FlushReadPixels(int image);
void ebitengine_WritePixels(int image, const uint8_t *pixels,
ebitengine_Region region);
ebitengine_Error ebitengine_FlushWritePixels(int image);
void ebitengine_DisposeImage(int id);
ebitengine_Error ebitengine_NewShader(int* shader, const char* source);
void ebitengine_Begin();
void ebitengine_End(int present);
void ebitengine_SetVertices(const float *vertices, int vertex_count,
const uint32_t *indices, int index_count);
ebitengine_Error
ebitengine_DrawTriangles(int dst, const int *srcs, int src_count, int shader,
const ebitengine_DstRegion *dst_regions,
int dst_region_count, int indexOffset,
ebitengine_Blend blend, const uint32_t *uniforms,
int uniform_count, int fill_rule);
ebitengine_Error ebitengine_NewShader(
int *shader, const char *vertex_header, int vertex_header_size,
const char *vertex_text, int vertex_text_size, const char *pixel_header,
int pixel_header_size, const char *pixel_text, int pixel_text_size);
void ebitengine_DisposeShader(int id);
#ifdef __cplusplus
@@ -0,0 +1,34 @@
// Copyright 2024 The Ebitengine 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.
//go:build playstation5
package playstation5
import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
var (
// precompiledShaders is a map to store precompiled shaders.
// precompiledShaders is initialized by a separate tool.
precompiledShaders map[shaderir.SourceHash]*shaderSource
)
type shaderSource struct {
vertexHeader []byte
vertexText []byte
pixelHeader []byte
pixelText []byte
}