Add GPU acceleration for most of ApiHawk's GuiApi (gui.* lua APIs), refactor ApiHawk surfaces
This commit is contained in:
parent
e1fe18be36
commit
476ac94d80
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,6 +3,7 @@
|
|||
<PackageVersion Include="Cyotek.Drawing.BitmapFont" Version="2.0.4" />
|
||||
<PackageVersion Include="DotNetAnalyzers.DocumentationAnalyzers" Version="1.0.0-beta.59" />
|
||||
<PackageVersion Include="Google.FlatBuffers" Version="23.5.26" /> <!-- last version with .NET Standard 2.0 support -->
|
||||
<PackageVersion Include="ImGui.NET" Version="1.90.6.1" />
|
||||
<PackageVersion Include="JunitXml.TestLogger" Version="3.0.124" />
|
||||
<PackageVersion Include="Magick.NET-Q8-AnyCPU" Version="13.6.0" />
|
||||
<PackageVersion Include="Menees.Analyzers" Version="3.0.10" />
|
||||
|
|
|
@ -9,7 +9,7 @@ project(SDL2-BizHawk C CXX)
|
|||
# set(SDL_ATOMIC ON) # Used in HIDAPI (and disabling it just makes it emulate atomics rather than use intrinsics)
|
||||
set(SDL_AUDIO OFF)
|
||||
set(SDL_VIDEO ON) # Used in mupen64plus + OpenGL/D3D9 display methods
|
||||
set(SDL_RENDER OFF)
|
||||
set(SDL_RENDER ON) # We use the software renderer for GDI+ImGui 2D rendering
|
||||
set(SDL_EVENTS ON) # Implied by SDL_VIDEO and SDL_JOYSTICK
|
||||
set(SDL_JOYSTICK ON) # Used for our SDL2 input adapter
|
||||
set(SDL_HAPTIC OFF)
|
||||
|
@ -69,6 +69,7 @@ set(SDL_DIRECTFB OFF)
|
|||
set(SDL_DUMMYVIDEO OFF)
|
||||
set(SDL_RPI OFF)
|
||||
set(SDL_RENDER_D3D OFF)
|
||||
set(SDL_RENDER_METAL OFF)
|
||||
set(SDL_VIVANTE OFF)
|
||||
set(SDL_VULKAN OFF)
|
||||
set(SDL_KMSDRM OFF)
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace BizHawk.Bizware.Graphics.Controls
|
|||
protected override void OnHandleCreated(EventArgs e)
|
||||
{
|
||||
base.OnHandleCreated(e);
|
||||
Context = new(Handle, 3, 0, true, false);
|
||||
Context = new(Handle, 3, 2, true, false);
|
||||
}
|
||||
|
||||
protected override void OnHandleDestroyed(EventArgs e)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cyotek.Drawing.BitmapFont" />
|
||||
<PackageReference Include="ImGui.NET" ExcludeAssets="native" />
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="Silk.NET.OpenGL" />
|
||||
<PackageReference Include="Silk.NET.WGL.Extensions.NV" />
|
||||
|
|
|
@ -31,6 +31,9 @@ namespace BizHawk.Bizware.Graphics
|
|||
public ID3D11Buffer VertexBuffer;
|
||||
public int VertexBufferCount;
|
||||
|
||||
public ID3D11Buffer IndexBuffer;
|
||||
public int IndexBufferCount;
|
||||
|
||||
private readonly record struct D3D11Uniform(IntPtr VariablePointer, int VariableSize, D3D11PendingBuffer PB);
|
||||
|
||||
private readonly Dictionary<string, D3D11Uniform> _vsUniforms = new();
|
||||
|
@ -99,7 +102,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
1 => Format.R32_Float,
|
||||
2 => Format.R32G32_Float,
|
||||
3 => Format.R32G32B32_Float,
|
||||
4 => Format.R32G32B32A32_Float,
|
||||
4 => item.Integer ? Format.B8G8R8A8_UNorm : Format.R32G32B32A32_Float,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
|
@ -253,6 +256,9 @@ namespace BizHawk.Bizware.Graphics
|
|||
VertexBuffer?.Dispose();
|
||||
VertexBuffer = null;
|
||||
VertexBufferCount = 0;
|
||||
IndexBuffer?.Dispose();
|
||||
IndexBuffer = null;
|
||||
IndexBufferCount = 0;
|
||||
|
||||
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
|
||||
{
|
||||
|
@ -283,6 +289,62 @@ namespace BizHawk.Bizware.Graphics
|
|||
}
|
||||
}
|
||||
|
||||
public void SetVertexData(IntPtr data, int count)
|
||||
{
|
||||
if (VertexBufferCount < count)
|
||||
{
|
||||
VertexBuffer?.Dispose();
|
||||
var bd = new BufferDescription( count * VertexStride, BindFlags.VertexBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
|
||||
VertexBuffer = Device.CreateBuffer(in bd, data);
|
||||
VertexBufferCount = count;
|
||||
Context.IASetVertexBuffer(0, VertexBuffer, VertexStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
var mappedVb = Context.Map(VertexBuffer, MapMode.WriteDiscard);
|
||||
try
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)data, (void*)mappedVb.DataPointer,
|
||||
VertexBufferCount * VertexStride, count * VertexStride);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Context.Unmap(VertexBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetIndexData(IntPtr data, int count)
|
||||
{
|
||||
if (IndexBufferCount < count)
|
||||
{
|
||||
IndexBuffer?.Dispose();
|
||||
var bd = new BufferDescription(count * 2, BindFlags.IndexBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
|
||||
IndexBuffer = Device.CreateBuffer(in bd, data);
|
||||
IndexBufferCount = count;
|
||||
Context.IASetIndexBuffer(IndexBuffer, Format.R16_UInt, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var mappedIb = Context.Map(IndexBuffer, MapMode.WriteDiscard);
|
||||
try
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)data, (void*)mappedIb.DataPointer,
|
||||
IndexBufferCount * 2, count * 2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Context.Unmap(IndexBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasUniformSampler(string name)
|
||||
{
|
||||
return _vsSamplers.ContainsKey(name)
|
||||
|
@ -299,6 +361,11 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
public void SetUniformSampler(string name, ITexture2D tex)
|
||||
{
|
||||
if (tex == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var d3d11Tex = (D3D11Texture2D)tex;
|
||||
var sampler = d3d11Tex.LinearFiltering ? LinearSamplerState : PointSamplerState;
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
bd.RenderTarget[0].DestinationBlend = Blend.InverseSourceAlpha;
|
||||
bd.RenderTarget[0].BlendOperation = BlendOperation.Add;
|
||||
bd.RenderTarget[0].SourceBlendAlpha = Blend.One;
|
||||
bd.RenderTarget[0].DestinationBlendAlpha = Blend.Zero;
|
||||
bd.RenderTarget[0].DestinationBlendAlpha = Blend.InverseSourceAlpha;
|
||||
bd.RenderTarget[0].BlendOperationAlpha = BlendOperation.Add;
|
||||
bd.RenderTarget[0].RenderTargetWriteMask = ColorWriteEnable.All;
|
||||
BlendEnableState = Device.CreateBlendState(bd);
|
||||
|
@ -106,8 +106,6 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
RasterizerState = Device.CreateRasterizerState(rd);
|
||||
|
||||
Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||
|
||||
foreach (var tex2d in Textures)
|
||||
{
|
||||
tex2d.CreateTexture();
|
||||
|
|
|
@ -2,11 +2,12 @@ using System;
|
|||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
using Vortice.Direct3D;
|
||||
using Vortice.Direct3D11;
|
||||
using Vortice.DXGI;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -216,6 +217,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
Context.IASetInputLayout(d3d11Pipeline.VertexInputLayout);
|
||||
Context.IASetVertexBuffer(0, d3d11Pipeline.VertexBuffer, d3d11Pipeline.VertexStride);
|
||||
Context.IASetIndexBuffer(d3d11Pipeline.IndexBuffer, Format.R16_UInt, 0);
|
||||
|
||||
// not sure if this applies to the current pipeline or all pipelines
|
||||
// just set it every time to be safe
|
||||
|
@ -262,63 +264,46 @@ namespace BizHawk.Bizware.Graphics
|
|||
Context.OMSetRenderTargets(_controlSwapChain.RTV);
|
||||
}
|
||||
|
||||
public void Draw(IntPtr data, int count)
|
||||
private unsafe void UpdateConstantBuffers()
|
||||
{
|
||||
var pipeline = CurPipeline;
|
||||
var stride = pipeline.VertexStride;
|
||||
|
||||
if (pipeline.VertexBufferCount < count)
|
||||
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
|
||||
{
|
||||
pipeline.VertexBuffer?.Dispose();
|
||||
var bd = new BufferDescription(stride * count, BindFlags.VertexBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
|
||||
pipeline.VertexBuffer = Device.CreateBuffer(in bd, data);
|
||||
pipeline.VertexBufferCount = count;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mappedVb = Context.Map(pipeline.VertexBuffer, MapMode.WriteDiscard);
|
||||
try
|
||||
var pb = CurPipeline.PendingBuffers[i];
|
||||
if (pb == null)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)data, (void*)mappedVb.DataPointer, stride * pipeline.VertexBufferCount, stride * count);
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
|
||||
if (pb.VSBufferDirty)
|
||||
{
|
||||
Context.Unmap(pipeline.VertexBuffer);
|
||||
var vsCb = Context.Map(CurPipeline.VSConstantBuffers[i], MapMode.WriteDiscard);
|
||||
Buffer.MemoryCopy((void*)pb.VSPendingBuffer, (void*)vsCb.DataPointer, pb.VSBufferSize, pb.VSBufferSize);
|
||||
Context.Unmap(CurPipeline.VSConstantBuffers[i]);
|
||||
pb.VSBufferDirty = false;
|
||||
}
|
||||
|
||||
if (pb.PSBufferDirty)
|
||||
{
|
||||
var psCb = Context.Map(CurPipeline.PSConstantBuffers[i], MapMode.WriteDiscard);
|
||||
Buffer.MemoryCopy((void*)pb.PSPendingBuffer, (void*)psCb.DataPointer, pb.PSBufferSize, pb.PSBufferSize);
|
||||
Context.Unmap(CurPipeline.PSConstantBuffers[i]);
|
||||
pb.PSBufferDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
|
||||
{
|
||||
var pb = pipeline.PendingBuffers[i];
|
||||
if (pb == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
public void Draw(int vertexCount)
|
||||
{
|
||||
UpdateConstantBuffers();
|
||||
Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||
Context.Draw(vertexCount, 0);
|
||||
}
|
||||
|
||||
if (pb.VSBufferDirty)
|
||||
{
|
||||
var vsCb = Context.Map(pipeline.VSConstantBuffers[i], MapMode.WriteDiscard);
|
||||
Buffer.MemoryCopy((void*)pb.VSPendingBuffer, (void*)vsCb.DataPointer, pb.VSBufferSize, pb.VSBufferSize);
|
||||
Context.Unmap(pipeline.VSConstantBuffers[i]);
|
||||
pb.VSBufferDirty = false;
|
||||
}
|
||||
|
||||
if (pb.PSBufferDirty)
|
||||
{
|
||||
var psCb = Context.Map(pipeline.PSConstantBuffers[i], MapMode.WriteDiscard);
|
||||
Buffer.MemoryCopy((void*)pb.PSPendingBuffer, (void*)psCb.DataPointer, pb.PSBufferSize, pb.PSBufferSize);
|
||||
Context.Unmap(pipeline.PSConstantBuffers[i]);
|
||||
pb.PSBufferDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context.Draw(count, 0);
|
||||
public void DrawIndexed(int indexCount, int indexStart, int vertexStart)
|
||||
{
|
||||
UpdateConstantBuffers();
|
||||
Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
|
||||
Context.DrawIndexed(indexCount, indexStart, vertexStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
|
@ -9,5 +10,8 @@ namespace BizHawk.Bizware.Graphics
|
|||
width = size.Width;
|
||||
height = size.Height;
|
||||
}
|
||||
|
||||
public static Vector2 ToVector(this Point point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,11 @@ namespace BizHawk.Bizware.Graphics
|
|||
? new GDIPlusGuiRenderer(gdipImpl)
|
||||
: new GuiRenderer(gl);
|
||||
|
||||
public static I2DRenderer Create2DRenderer(this IGL gl, ImGuiResourceCache resourceCache)
|
||||
=> gl is IGL_GDIPlus gdipImpl
|
||||
? new SDLImGui2DRenderer(gdipImpl, resourceCache)
|
||||
: new ImGui2DRenderer(gl, resourceCache);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a texture from disk
|
||||
/// </summary>
|
||||
|
|
|
@ -46,7 +46,11 @@ namespace BizHawk.Bizware.Graphics
|
|||
{
|
||||
}
|
||||
|
||||
public void Draw(IntPtr data, int count)
|
||||
public void Draw(int vertexCount)
|
||||
{
|
||||
}
|
||||
|
||||
public void DrawIndexed(int indexCount, int indexStart, int vertexStart)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared resource cache for the ImGui 2D renderer
|
||||
/// This allows multiple ImGui renderers to share the same cache
|
||||
/// </summary>
|
||||
public sealed class ImGuiResourceCache
|
||||
{
|
||||
private readonly IGL _igl;
|
||||
private ITexture2D _lastTexture;
|
||||
|
||||
internal readonly IPipeline Pipeline;
|
||||
internal readonly Dictionary<Bitmap, ITexture2D> TextureCache = new();
|
||||
internal readonly Dictionary<Color, SolidBrush> BrushCache = new();
|
||||
|
||||
public ImGuiResourceCache(IGL igl)
|
||||
{
|
||||
_igl = igl;
|
||||
if (igl.DispMethodEnum is EDispMethod.OpenGL or EDispMethod.D3D11)
|
||||
{
|
||||
string psProgram, vsProgram;
|
||||
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (igl.DispMethodEnum)
|
||||
{
|
||||
case EDispMethod.D3D11:
|
||||
vsProgram = ImGuiVertexShader_d3d11;
|
||||
psProgram = ImGuiPixelShader_d3d11;
|
||||
break;
|
||||
case EDispMethod.OpenGL:
|
||||
vsProgram = ImGuiVertexShader_gl;
|
||||
psProgram = ImGuiPixelShader_gl;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var vertexLayoutItems = new PipelineCompileArgs.VertexLayoutItem[3];
|
||||
vertexLayoutItems[0] = new("aPosition", 2, 0, AttribUsage.Position);
|
||||
vertexLayoutItems[1] = new("aTexcoord", 2, 8, AttribUsage.Texcoord0);
|
||||
vertexLayoutItems[2] = new("aColor", 4, 16, AttribUsage.Color0, Integer: true);
|
||||
|
||||
var compileArgs = new PipelineCompileArgs(
|
||||
vertexLayoutItems,
|
||||
vertexShaderArgs: new(vsProgram, "vsmain"),
|
||||
fragmentShaderArgs: new(psProgram, "psmain"),
|
||||
fragmentOutputName: "oColor");
|
||||
Pipeline = igl.CreatePipeline(compileArgs);
|
||||
|
||||
igl.BindPipeline(Pipeline);
|
||||
Pipeline.SetUniform("uSamplerEnable", false);
|
||||
Pipeline.SetUniformSampler("uSampler0", null);
|
||||
igl.BindPipeline(null);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetProjection(int width, int height)
|
||||
{
|
||||
var projection = _igl.CreateGuiViewMatrix(width, height) * _igl.CreateGuiProjectionMatrix(width, height);
|
||||
Pipeline.SetUniformMatrix("um44Projection", projection);
|
||||
}
|
||||
|
||||
internal void SetTexture(ITexture2D texture2D)
|
||||
{
|
||||
if (_lastTexture != texture2D)
|
||||
{
|
||||
Pipeline.SetUniform("uSamplerEnable", texture2D != null);
|
||||
Pipeline.SetUniformSampler("uSampler0", texture2D);
|
||||
_lastTexture = texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cachedTexture in TextureCache.Values)
|
||||
{
|
||||
cachedTexture.Dispose();
|
||||
}
|
||||
|
||||
foreach (var cachedBrush in BrushCache.Values)
|
||||
{
|
||||
cachedBrush.Dispose();
|
||||
}
|
||||
|
||||
TextureCache.Clear();
|
||||
BrushCache.Clear();
|
||||
Pipeline?.Dispose();
|
||||
}
|
||||
|
||||
public const string ImGuiVertexShader_d3d11 = @"
|
||||
//vertex shader uniforms
|
||||
float4x4 um44Projection;
|
||||
|
||||
struct VS_INPUT
|
||||
{
|
||||
float2 aPosition : POSITION;
|
||||
float2 aTexcoord : TEXCOORD0;
|
||||
float4 aColor : COLOR0;
|
||||
};
|
||||
|
||||
struct VS_OUTPUT
|
||||
{
|
||||
float4 vPosition : SV_POSITION;
|
||||
float2 vTexcoord0 : TEXCOORD0;
|
||||
float4 vColor0 : COLOR0;
|
||||
};
|
||||
|
||||
VS_OUTPUT vsmain(VS_INPUT src)
|
||||
{
|
||||
VS_OUTPUT dst;
|
||||
float4 temp = float4(src.aPosition,0,1);
|
||||
dst.vPosition = mul(um44Projection, temp);
|
||||
dst.vTexcoord0 = src.aTexcoord;
|
||||
dst.vColor0 = src.aColor;
|
||||
return dst;
|
||||
}
|
||||
";
|
||||
|
||||
public const string ImGuiPixelShader_d3d11 = @"
|
||||
//pixel shader uniforms
|
||||
bool uSamplerEnable;
|
||||
Texture2D uTexture0;
|
||||
sampler uSampler0;
|
||||
|
||||
struct PS_INPUT
|
||||
{
|
||||
float4 vPosition : SV_POSITION;
|
||||
float2 vTexcoord0 : TEXCOORD0;
|
||||
float4 vColor0 : COLOR0;
|
||||
};
|
||||
|
||||
float4 psmain(PS_INPUT src) : SV_Target
|
||||
{
|
||||
float4 temp = src.vColor0;
|
||||
if(uSamplerEnable) temp *= uTexture0.Sample(uSampler0,src.vTexcoord0);
|
||||
return temp;
|
||||
}
|
||||
";
|
||||
|
||||
public const string ImGuiVertexShader_gl = @"
|
||||
//opengl 3.0
|
||||
#version 130
|
||||
uniform mat4 um44Projection;
|
||||
|
||||
in vec2 aPosition;
|
||||
in vec2 aTexcoord;
|
||||
in vec4 aColor;
|
||||
|
||||
out vec2 vTexcoord0;
|
||||
out vec4 vColor0;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 temp = vec4(aPosition,0,1);
|
||||
gl_Position = um44Projection * temp;
|
||||
vTexcoord0 = aTexcoord;
|
||||
vColor0 = aColor;
|
||||
}";
|
||||
|
||||
public const string ImGuiPixelShader_gl = @"
|
||||
//opengl 3.0
|
||||
#version 130
|
||||
bool uSamplerEnable;
|
||||
uniform sampler2D uSampler0;
|
||||
|
||||
in vec2 vTexcoord0;
|
||||
in vec4 vColor0;
|
||||
|
||||
out vec4 oColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 temp = vColor0;
|
||||
if(uSamplerEnable) temp *= texture2D(uSampler0, vTexcoord0);
|
||||
oColor = temp;
|
||||
}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Text;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for 2D rendering, similar to System.Drawing's Graphics
|
||||
/// Semantically, this must be able to be called at any point
|
||||
/// This means batching MUST occur
|
||||
/// As in this case IGL resources can only be used in the ctor, Dispose(), and Render()
|
||||
/// </summary>
|
||||
public interface I2DRenderer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders any pending draw calls.
|
||||
/// Returns the result as an ITexture2D.
|
||||
/// Internally, this may change the bound render target, pipeline, viewport/scissor, and blending state.
|
||||
/// If this is important, rebind them after calling this.
|
||||
/// Any rendering occurs after clearing the target texture.
|
||||
/// If nothing needs to be rendered and the size does not change, the contents are preserved
|
||||
/// </summary>
|
||||
ITexture2D Render(int width, int height);
|
||||
|
||||
/// <summary>
|
||||
/// Clears any pending draw calls.
|
||||
/// This will also insert a command to clear the target texture.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
CompositingMode CompositingMode { set; }
|
||||
|
||||
void DrawBezier(Color color, Point pt1, Point pt2, Point pt3, Point pt4);
|
||||
|
||||
void DrawBeziers(Color color, Point[] points);
|
||||
|
||||
void DrawRectangle(Color color, int x, int y, int width, int height);
|
||||
|
||||
void FillRectangle(Color color, int x, int y, int width, int height);
|
||||
|
||||
void DrawEllipse(Color color, int x, int y, int width, int height);
|
||||
|
||||
void FillEllipse(Color color, int x, int y, int width, int height);
|
||||
|
||||
void DrawImage(Bitmap bitmap, int x, int y);
|
||||
|
||||
void DrawImage(Bitmap bitmap, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, bool cache);
|
||||
|
||||
void DrawLine(Color color, int x1, int y1, int x2, int y2);
|
||||
|
||||
void DrawPie(Color color, int x, int y, int width, int height, int startAngle, int sweepAngle);
|
||||
|
||||
void FillPie(Color color, int x, int y, int width, int height, int startAngle, int sweepAngle);
|
||||
|
||||
void DrawPolygon(Color color, Point[] points);
|
||||
|
||||
void FillPolygon(Color color, Point[] points);
|
||||
|
||||
void DrawString(string s, Font font, Color color, float x, float y, StringFormat format = null, TextRenderingHint textRenderingHint = TextRenderingHint.SystemDefault);
|
||||
}
|
||||
}
|
|
@ -70,12 +70,17 @@ namespace BizHawk.Bizware.Graphics
|
|||
void SetViewport(int x, int y, int width, int height);
|
||||
|
||||
/// <summary>
|
||||
/// Draws based on the currently set pipeline
|
||||
/// Data contains vertexes based on the pipeline's VertexLayout
|
||||
/// Count is the vertex count
|
||||
/// Non-indexed drawing for the currently set pipeline/render target
|
||||
/// Vertexes must form triangle strips
|
||||
/// </summary>
|
||||
void Draw(IntPtr data, int count);
|
||||
void Draw(int vertexCount);
|
||||
|
||||
/// <summary>
|
||||
/// Indexed drawing for the currently set pipeline/render target
|
||||
/// Indexes must be 16 bits each
|
||||
/// Vertexes must form triangle lists
|
||||
/// </summary>
|
||||
void DrawIndexed(int indexCount, int indexStart, int vertexStart);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a proper 2D othographic projection for the given destination size, suitable for use in a GUI
|
||||
|
|
|
@ -5,6 +5,16 @@ namespace BizHawk.Bizware.Graphics
|
|||
{
|
||||
public interface IPipeline : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets vertex data for this pipeline
|
||||
/// </summary>
|
||||
void SetVertexData(IntPtr data, int count);
|
||||
|
||||
/// <summary>
|
||||
/// Sets index data for this pipeline
|
||||
/// </summary>
|
||||
void SetIndexData(IntPtr data, int count);
|
||||
|
||||
bool HasUniformSampler(string name);
|
||||
|
||||
string GetUniformSamplerName(int index);
|
||||
|
|
|
@ -19,8 +19,8 @@ namespace BizHawk.Bizware.Graphics
|
|||
private OpenGLPipeline _curPipeline;
|
||||
internal bool DefaultRenderTargetBound;
|
||||
|
||||
// this IGL either requires at least OpenGL 3.0
|
||||
public static bool Available => OpenGLVersion.SupportsVersion(3, 0);
|
||||
// this IGL either requires at least OpenGL 3.2
|
||||
public static bool Available => OpenGLVersion.SupportsVersion(3, 2);
|
||||
|
||||
public IGL_OpenGL()
|
||||
{
|
||||
|
@ -45,7 +45,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
{
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendEquation(GLEnum.FuncAdd);
|
||||
GL.BlendFuncSeparate(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha, BlendingFactor.One, BlendingFactor.Zero);
|
||||
GL.BlendFuncSeparate(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha, BlendingFactor.One, BlendingFactor.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
public void DisableBlending()
|
||||
|
@ -71,6 +71,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
{
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, 0);
|
||||
GL.BindBuffer(GLEnum.ElementArrayBuffer, 0);
|
||||
GL.UseProgram(0);
|
||||
_curPipeline = null;
|
||||
return;
|
||||
|
@ -78,34 +79,15 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
GL.BindVertexArray(_curPipeline.VAO);
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, _curPipeline.VBO);
|
||||
GL.BindBuffer(GLEnum.ElementArrayBuffer, _curPipeline.IBO);
|
||||
GL.UseProgram(_curPipeline.PID);
|
||||
}
|
||||
|
||||
public void Draw(IntPtr data, int count)
|
||||
{
|
||||
if (_curPipeline == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Tried to {nameof(Draw)} without pipeline!");
|
||||
}
|
||||
public void Draw(int vertexCount)
|
||||
=> GL.DrawArrays(PrimitiveType.TriangleStrip, 0, (uint)vertexCount);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var vertexes = new ReadOnlySpan<byte>((void*)data, count * _curPipeline.VertexStride);
|
||||
|
||||
// BufferData reallocs and BufferSubData doesn't, so only use the former if we need to grow the buffer
|
||||
if (vertexes.Length > _curPipeline.VertexBufferLen)
|
||||
{
|
||||
GL.BufferData(GLEnum.ArrayBuffer, vertexes, GLEnum.DynamicDraw);
|
||||
_curPipeline.VertexBufferLen = vertexes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.BufferSubData(GLEnum.ArrayBuffer, 0, vertexes);
|
||||
}
|
||||
}
|
||||
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, (uint)count);
|
||||
}
|
||||
public unsafe void DrawIndexed(int indexCount, int indexStart, int vertexStart)
|
||||
=> GL.DrawElementsBaseVertex(PrimitiveType.Triangles, (uint)indexCount, DrawElementsType.UnsignedShort, (void*)indexStart, vertexStart);
|
||||
|
||||
public ITexture2D CreateTexture(int width, int height)
|
||||
=> new OpenGLTexture2D(GL, width, height);
|
||||
|
|
|
@ -19,6 +19,9 @@ namespace BizHawk.Bizware.Graphics
|
|||
public readonly int VertexStride;
|
||||
public int VertexBufferLen;
|
||||
|
||||
public readonly uint IBO;
|
||||
public int IndexBufferLen;
|
||||
|
||||
public readonly uint VertexSID;
|
||||
public readonly uint FragmentSID;
|
||||
|
||||
|
@ -81,6 +84,8 @@ namespace BizHawk.Bizware.Graphics
|
|||
VBO = GL.GenBuffer();
|
||||
VertexStride = compileArgs.VertexLayoutStride;
|
||||
|
||||
IBO = GL.GenBuffer();
|
||||
|
||||
_ = GL.GetError();
|
||||
VertexSID = CompileShader(compileArgs.VertexShaderArgs.Source, ShaderType.VertexShader);
|
||||
FragmentSID = CompileShader(compileArgs.FragmentShaderArgs.Source, ShaderType.FragmentShader);
|
||||
|
@ -101,9 +106,9 @@ namespace BizHawk.Bizware.Graphics
|
|||
GL.EnableVertexAttribArray(attribIndex);
|
||||
GL.VertexAttribPointer(
|
||||
attribIndex,
|
||||
item.Components,
|
||||
VertexAttribPointerType.Float,
|
||||
normalized: false,
|
||||
item.Integer ? (int)GLEnum.Bgra : item.Components,
|
||||
item.Integer ? VertexAttribPointerType.UnsignedByte : VertexAttribPointerType.Float,
|
||||
normalized: item.Integer,
|
||||
(uint)VertexStride,
|
||||
(void*)item.Offset);
|
||||
}
|
||||
|
@ -205,6 +210,37 @@ namespace BizHawk.Bizware.Graphics
|
|||
GL.DeleteBuffer(VBO);
|
||||
}
|
||||
|
||||
public unsafe void SetVertexData(IntPtr data, int count)
|
||||
{
|
||||
var vertexes = new ReadOnlySpan<byte>((void*)data, count * VertexStride);
|
||||
|
||||
// BufferData reallocs and BufferSubData doesn't, so only use the former if we need to grow the buffer
|
||||
if (vertexes.Length > VertexBufferLen)
|
||||
{
|
||||
GL.BufferData(GLEnum.ArrayBuffer, vertexes, GLEnum.DynamicDraw);
|
||||
VertexBufferLen = vertexes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.BufferSubData(GLEnum.ArrayBuffer, 0, vertexes);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetIndexData(IntPtr data, int count)
|
||||
{
|
||||
var indexes = new ReadOnlySpan<byte>((void*)data, count * 2);
|
||||
|
||||
if (indexes.Length > IndexBufferLen)
|
||||
{
|
||||
GL.BufferData(GLEnum.ElementArrayBuffer, indexes, GLEnum.DynamicDraw);
|
||||
IndexBufferLen = indexes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.BufferSubData(GLEnum.ElementArrayBuffer, 0, indexes);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasUniformSampler(string name)
|
||||
=> _samplers.ContainsKey(name);
|
||||
|
||||
|
@ -216,6 +252,11 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
public void SetUniformSampler(string name, ITexture2D tex)
|
||||
{
|
||||
if (tex == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_samplers.TryGetValue(name, out var sampler))
|
||||
{
|
||||
var oglTex = (OpenGLTexture2D)tex;
|
||||
|
|
|
@ -10,12 +10,15 @@ namespace BizHawk.Bizware.Graphics
|
|||
{
|
||||
/// <summary>
|
||||
/// Defines an item within a vertex layout
|
||||
/// This is currently restricted to float types
|
||||
/// This is currently restricted to to either 4 byte floats, or a 32 bit unsigned normalized integer type
|
||||
/// If this is a 32 bit unsigned normalized type, it must have 4 components
|
||||
/// </summary>
|
||||
/// <param name="Components">Number of components in the item (e.g. float2 has 2 components). Only 1-4 components is valid</param>
|
||||
/// <param name="Name">Name of the item</param>
|
||||
/// <param name="Components">Number of components in the item (e.g. float2 has 2 components). Only 1-4 components is valid (unless Integer is true, then only 4 is valid)</param>
|
||||
/// <param name="Offset">Byte offset within the vertex buffer to the item</param>
|
||||
/// <param name="Usage">Semantic usage</param>
|
||||
public readonly record struct VertexLayoutItem(string Name, int Components, int Offset, AttribUsage Usage);
|
||||
/// <param name="Integer">Indicates if this uses an integer rather than a float</param>
|
||||
public readonly record struct VertexLayoutItem(string Name, int Components, int Offset, AttribUsage Usage, bool Integer = false);
|
||||
|
||||
/// <summary>
|
||||
/// Defines arguments for compiling a shader
|
||||
|
@ -44,7 +47,12 @@ namespace BizHawk.Bizware.Graphics
|
|||
throw new InvalidOperationException("A vertex layout item must have 1-4 components");
|
||||
}
|
||||
|
||||
VertexLayoutStride += item.Components * 4;
|
||||
if (item.Integer && item.Components != 4)
|
||||
{
|
||||
throw new InvalidOperationException("A vertex layout integer item must have 4 components");
|
||||
}
|
||||
|
||||
VertexLayoutStride += item.Integer ? 4 : item.Components * 4;
|
||||
}
|
||||
|
||||
VertexShaderArgs = vertexShaderArgs;
|
||||
|
|
|
@ -258,7 +258,8 @@ namespace BizHawk.Bizware.Graphics
|
|||
pData[30] = CornerColors[3].Z;
|
||||
pData[31] = CornerColors[3].W;
|
||||
|
||||
Owner.Draw(new(pData), 4);
|
||||
CurrPipeline.SetVertexData(new(pData), 4);
|
||||
Owner.Draw(4);
|
||||
}
|
||||
|
||||
public bool IsActive { get; private set; }
|
||||
|
|
|
@ -0,0 +1,490 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Text;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
using SDGraphics = System.Drawing.Graphics;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps ImGui to create a simple 2D renderer
|
||||
/// </summary>
|
||||
internal class ImGui2DRenderer : I2DRenderer
|
||||
{
|
||||
// ImDrawListSharedData is defined in imgui_internal.h, and therefore it is not exposed in ImGuiNET
|
||||
// we want to use it directly however, and cimgui does give exports for it
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ImDrawListSharedData
|
||||
{
|
||||
public Vector2 TexUvWhitePixel;
|
||||
public IntPtr Font;
|
||||
public float FontSize;
|
||||
public float CurveTessellationTol;
|
||||
// other fields are present, but we don't care about them
|
||||
}
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
private static extern unsafe ImDrawListSharedData* ImDrawListSharedData_ImDrawListSharedData();
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
private static extern unsafe void ImDrawListSharedData_SetCircleTessellationMaxError(ImDrawListSharedData* self, float max_error);
|
||||
|
||||
private static readonly unsafe ImDrawListSharedData* _drawListSharedData;
|
||||
|
||||
static ImGui2DRenderer()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_drawListSharedData = ImDrawListSharedData_ImDrawListSharedData();
|
||||
// values taken from default ImGuiStyle
|
||||
_drawListSharedData->CurveTessellationTol = 1.25f;
|
||||
ImDrawListSharedData_SetCircleTessellationMaxError(_drawListSharedData, 0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<GCHandle> _gcHandles = new();
|
||||
|
||||
protected virtual float RenderThickness => 1;
|
||||
|
||||
protected readonly IGL _igl;
|
||||
protected readonly ImGuiResourceCache _resourceCache;
|
||||
protected ImDrawListPtr _imGuiDrawList;
|
||||
protected bool _hasDrawStringCommand;
|
||||
protected bool _hasClearPending;
|
||||
protected Bitmap _stringOutput;
|
||||
protected SDGraphics _stringGraphics;
|
||||
protected IRenderTarget _renderTarget;
|
||||
|
||||
public ImGui2DRenderer(IGL igl, ImGuiResourceCache resourceCache)
|
||||
{
|
||||
_igl = igl;
|
||||
_resourceCache = resourceCache;
|
||||
|
||||
unsafe
|
||||
{
|
||||
_imGuiDrawList = ImGuiNative.ImDrawList_ImDrawList((IntPtr)_drawListSharedData);
|
||||
}
|
||||
|
||||
_pendingBlendEnable = true;
|
||||
ResetDrawList();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearGCHandles();
|
||||
_renderTarget?.Dispose();
|
||||
_renderTarget = null;
|
||||
_stringGraphics?.Dispose();
|
||||
_stringGraphics = null;
|
||||
_stringOutput?.Dispose();
|
||||
_stringOutput = null;
|
||||
_imGuiDrawList.Destroy();
|
||||
_imGuiDrawList = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private unsafe void ClearStringOutput()
|
||||
{
|
||||
var bmpData = _stringOutput.LockBits(new(0, 0, _stringOutput.Width, _stringOutput.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
new Span<byte>((void*)bmpData.Scan0, bmpData.Stride * bmpData.Height).Clear();
|
||||
_stringOutput.UnlockBits(bmpData);
|
||||
}
|
||||
|
||||
private void ClearGCHandles()
|
||||
{
|
||||
foreach (var gcHandle in _gcHandles)
|
||||
{
|
||||
switch (gcHandle.Target)
|
||||
{
|
||||
case ImGuiUserTexture userTexture:
|
||||
// avoid disposing our string output bitmap here
|
||||
if (userTexture.Bitmap != _stringOutput)
|
||||
{
|
||||
userTexture.Bitmap.Dispose();
|
||||
}
|
||||
break;
|
||||
case DrawStringArgs args:
|
||||
args.Font.Dispose();
|
||||
break;
|
||||
}
|
||||
|
||||
gcHandle.Free();
|
||||
}
|
||||
|
||||
_gcHandles.Clear();
|
||||
}
|
||||
|
||||
private void ResetDrawList()
|
||||
{
|
||||
ClearGCHandles();
|
||||
_imGuiDrawList._ResetForNewFrame();
|
||||
_imGuiDrawList.Flags |= ImDrawListFlags.AllowVtxOffset;
|
||||
_hasDrawStringCommand = false;
|
||||
EnableBlending = _pendingBlendEnable;
|
||||
}
|
||||
|
||||
protected class ImGuiUserTexture
|
||||
{
|
||||
public Bitmap Bitmap;
|
||||
public ITexture2D CachedTexture;
|
||||
public bool WantCache;
|
||||
}
|
||||
|
||||
protected class DrawStringArgs
|
||||
{
|
||||
public string Str;
|
||||
public Font Font;
|
||||
public Color Color;
|
||||
public float X, Y;
|
||||
public StringFormat Format;
|
||||
public TextRenderingHint TextRenderingHint;
|
||||
}
|
||||
|
||||
protected enum DrawCallbackId
|
||||
{
|
||||
None,
|
||||
DisableBlending,
|
||||
EnableBlending,
|
||||
DrawString,
|
||||
}
|
||||
|
||||
protected virtual void RenderInternal(int width, int height)
|
||||
{
|
||||
if (EnableBlending)
|
||||
{
|
||||
_igl.EnableBlending();
|
||||
}
|
||||
else
|
||||
{
|
||||
_igl.DisableBlending();
|
||||
}
|
||||
|
||||
_igl.BindPipeline(_resourceCache.Pipeline);
|
||||
_resourceCache.SetProjection(width, height);
|
||||
|
||||
if (_imGuiDrawList.VtxBuffer.Size > 0)
|
||||
{
|
||||
_resourceCache.Pipeline.SetVertexData(_imGuiDrawList.VtxBuffer.Data, _imGuiDrawList.VtxBuffer.Size);
|
||||
}
|
||||
|
||||
if (_imGuiDrawList.IdxBuffer.Size > 0)
|
||||
{
|
||||
_resourceCache.Pipeline.SetIndexData(_imGuiDrawList.IdxBuffer.Data, _imGuiDrawList.IdxBuffer.Size);
|
||||
}
|
||||
|
||||
var cmdBuffer = _imGuiDrawList.CmdBuffer;
|
||||
for (var i = 0; i < cmdBuffer.Size; i++)
|
||||
{
|
||||
var cmd = cmdBuffer[i];
|
||||
var callbackId = (DrawCallbackId)cmd.UserCallback;
|
||||
switch (callbackId)
|
||||
{
|
||||
case DrawCallbackId.None:
|
||||
{
|
||||
var texId = cmd.GetTexID();
|
||||
ITexture2D tempTex = null;
|
||||
if (texId != IntPtr.Zero)
|
||||
{
|
||||
var userTex = (ImGuiUserTexture)GCHandle.FromIntPtr(texId).Target!;
|
||||
if (userTex.CachedTexture != null)
|
||||
{
|
||||
_resourceCache.SetTexture(userTex.CachedTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempTex = _igl.LoadTexture(userTex.Bitmap);
|
||||
_resourceCache.SetTexture(tempTex);
|
||||
if (userTex.WantCache)
|
||||
{
|
||||
_resourceCache.TextureCache.Add(userTex.Bitmap, tempTex);
|
||||
userTex.CachedTexture = tempTex;
|
||||
tempTex = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_resourceCache.SetTexture(null);
|
||||
}
|
||||
|
||||
_igl.DrawIndexed((int)cmd.ElemCount, (int)cmd.IdxOffset, (int)cmd.VtxOffset);
|
||||
tempTex?.Dispose();
|
||||
break;
|
||||
}
|
||||
case DrawCallbackId.DisableBlending:
|
||||
_igl.DisableBlending();
|
||||
break;
|
||||
case DrawCallbackId.EnableBlending:
|
||||
_igl.EnableBlending();
|
||||
break;
|
||||
case DrawCallbackId.DrawString:
|
||||
{
|
||||
var stringArgs = (DrawStringArgs)GCHandle.FromIntPtr(cmd.UserCallbackData).Target!;
|
||||
var brush = _resourceCache.BrushCache.GetValueOrPutNew1(stringArgs.Color);
|
||||
_stringGraphics.TextRenderingHint = stringArgs.TextRenderingHint;
|
||||
_stringGraphics.DrawString(stringArgs.Str, stringArgs.Font, brush, stringArgs.X, stringArgs.Y, stringArgs.Format);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ITexture2D Render(int width, int height)
|
||||
{
|
||||
var needsRender = _imGuiDrawList.VtxBuffer.Size > 0 || _imGuiDrawList.IdxBuffer.Size > 0 || _hasDrawStringCommand;
|
||||
var needsClear = needsRender || _hasClearPending;
|
||||
if (_renderTarget == null || _renderTarget.Width != width || _renderTarget.Height != height)
|
||||
{
|
||||
_renderTarget?.Dispose();
|
||||
_renderTarget = _igl.CreateRenderTarget(width, height);
|
||||
needsClear = true;
|
||||
}
|
||||
|
||||
if (_hasDrawStringCommand
|
||||
&& (_stringOutput == null
|
||||
|| _stringOutput.Width != width
|
||||
|| _stringOutput.Height != height))
|
||||
{
|
||||
_stringGraphics?.Dispose();
|
||||
_stringOutput?.Dispose();
|
||||
_stringOutput = new(width, height, PixelFormat.Format32bppArgb);
|
||||
_stringGraphics = SDGraphics.FromImage(_stringOutput);
|
||||
}
|
||||
|
||||
_renderTarget.Bind();
|
||||
_igl.SetViewport(width, height);
|
||||
|
||||
if (needsClear)
|
||||
{
|
||||
_igl.ClearColor(Color.FromArgb(0));
|
||||
_hasClearPending = false;
|
||||
}
|
||||
|
||||
if (needsRender)
|
||||
{
|
||||
if (_hasDrawStringCommand)
|
||||
{
|
||||
ClearStringOutput();
|
||||
// synthesize an add image command for our string bitmap
|
||||
_imGuiDrawList.AddCallback((IntPtr)DrawCallbackId.EnableBlending, IntPtr.Zero);
|
||||
DrawImage(_stringOutput, 0, 0);
|
||||
}
|
||||
|
||||
RenderInternal(width, height);
|
||||
ResetDrawList();
|
||||
}
|
||||
|
||||
return _renderTarget;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ResetDrawList();
|
||||
_hasClearPending = true;
|
||||
}
|
||||
|
||||
protected bool EnableBlending { get; private set; }
|
||||
private bool _pendingBlendEnable;
|
||||
|
||||
public CompositingMode CompositingMode
|
||||
{
|
||||
set
|
||||
{
|
||||
switch (_pendingBlendEnable)
|
||||
{
|
||||
// CompositingMode.SourceCopy means disable blending
|
||||
case true when value == CompositingMode.SourceCopy:
|
||||
_imGuiDrawList.AddCallback((IntPtr)DrawCallbackId.DisableBlending, IntPtr.Zero);
|
||||
_pendingBlendEnable = false;
|
||||
break;
|
||||
// CompositingMode.SourceOver means enable blending
|
||||
case false when value == CompositingMode.SourceOver:
|
||||
_imGuiDrawList.AddCallback((IntPtr)DrawCallbackId.EnableBlending, IntPtr.Zero);
|
||||
_pendingBlendEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawBezier(Color color, Point pt1, Point pt2, Point pt3, Point pt4)
|
||||
{
|
||||
_imGuiDrawList.AddBezierCubic(
|
||||
p1: pt1.ToVector(),
|
||||
p2: pt2.ToVector(),
|
||||
p3: pt3.ToVector(),
|
||||
p4: pt4.ToVector(),
|
||||
col: (uint)color.ToArgb(),
|
||||
thickness: RenderThickness);
|
||||
}
|
||||
|
||||
public void DrawBeziers(Color color, Point[] points)
|
||||
{
|
||||
if (points.Length < 4 || (points.Length - 1) % 3 != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid number of points");
|
||||
}
|
||||
|
||||
var startPt = points[0];
|
||||
var col = (uint)color.ToArgb();
|
||||
for (var i = 1; i < points.Length; i += 3)
|
||||
{
|
||||
_imGuiDrawList.AddBezierCubic(
|
||||
p1: startPt.ToVector(),
|
||||
p2: points[i + 0].ToVector(),
|
||||
p3: points[i + 1].ToVector(),
|
||||
p4: points[i + 2].ToVector(),
|
||||
col: col,
|
||||
thickness: RenderThickness);
|
||||
startPt = points[i + 2];
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawRectangle(Color color, int x, int y, int width, int height)
|
||||
{
|
||||
_imGuiDrawList.AddRect(
|
||||
p_min: new(x, y),
|
||||
p_max: new(x + width, y + height),
|
||||
col: (uint)color.ToArgb(),
|
||||
rounding: 0,
|
||||
flags: ImDrawFlags.None,
|
||||
thickness: RenderThickness);
|
||||
}
|
||||
|
||||
public void FillRectangle(Color color, int x, int y, int width, int height)
|
||||
{
|
||||
_imGuiDrawList.AddRectFilled(
|
||||
p_min: new(x, y),
|
||||
p_max: new(x + width, y + height),
|
||||
col: (uint)color.ToArgb());
|
||||
}
|
||||
|
||||
public void DrawEllipse(Color color, int x, int y, int width, int height)
|
||||
{
|
||||
var radius = new Vector2(width / 2.0f, height / 2.0f);
|
||||
_imGuiDrawList.AddEllipse(
|
||||
center: new(x + radius.X, y + radius.Y),
|
||||
radius: radius,
|
||||
col: (uint)color.ToArgb(),
|
||||
rot: 0,
|
||||
num_segments: 0,
|
||||
RenderThickness);
|
||||
}
|
||||
|
||||
public void FillEllipse(Color color, int x, int y, int width, int height)
|
||||
{
|
||||
var radius = new Vector2(width / 2.0f, height / 2.0f);
|
||||
_imGuiDrawList.AddEllipseFilled(
|
||||
center: new(x + radius.X, y + radius.Y),
|
||||
radius: radius,
|
||||
col: (uint)color.ToArgb());
|
||||
}
|
||||
|
||||
public void DrawImage(Bitmap image, int x, int y)
|
||||
{
|
||||
var texture = new ImGuiUserTexture { Bitmap = image, WantCache = false };
|
||||
var handle = GCHandle.Alloc(texture, GCHandleType.Normal);
|
||||
_gcHandles.Add(handle);
|
||||
_imGuiDrawList.AddImage(
|
||||
user_texture_id: GCHandle.ToIntPtr(handle),
|
||||
p_min: new(x, y),
|
||||
p_max: new(x + image.Width, y + image.Height));
|
||||
}
|
||||
|
||||
public void DrawImage(Bitmap image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, bool cache)
|
||||
{
|
||||
var texture = new ImGuiUserTexture { Bitmap = image, WantCache = cache };
|
||||
if (cache && _resourceCache.TextureCache.TryGetValue(image, out var cachedTexture))
|
||||
{
|
||||
texture.CachedTexture = cachedTexture;
|
||||
}
|
||||
|
||||
var handle = GCHandle.Alloc(texture, GCHandleType.Normal);
|
||||
_gcHandles.Add(handle);
|
||||
var imgWidth = (float)image.Width;
|
||||
var imgHeight = (float)image.Height;
|
||||
_imGuiDrawList.AddImage(
|
||||
user_texture_id: GCHandle.ToIntPtr(handle),
|
||||
p_min: new(destRect.Left, destRect.Top),
|
||||
p_max: new(destRect.Right, destRect.Bottom),
|
||||
uv_min: new(srcX / imgWidth, srcY / imgHeight),
|
||||
uv_max: new((srcX + srcWidth) / imgWidth, (srcY + srcHeight) / imgHeight));
|
||||
}
|
||||
|
||||
public void DrawLine(Color color, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
_imGuiDrawList.AddLine(
|
||||
p1: new(x1, y1),
|
||||
p2: new(x2, y2),
|
||||
col: (uint)color.ToArgb(),
|
||||
thickness: RenderThickness);
|
||||
}
|
||||
|
||||
public void DrawPie(Color color, int x, int y, int width, int height, int startAngle, int sweepAngle)
|
||||
{
|
||||
var radius = new Vector2(width / 2.0f, height / 2.0f);
|
||||
var center = new Vector2(x + radius.X, y + radius.Y);
|
||||
var aMin = (float)(Math.PI / 180 * startAngle);
|
||||
var aMax = (float)(Math.PI / 180 * (startAngle + sweepAngle));
|
||||
_imGuiDrawList.PathEllipticalArcTo(center, radius, 0, aMin, aMax);
|
||||
_imGuiDrawList.PathLineTo(center);
|
||||
_imGuiDrawList.PathStroke((uint)color.ToArgb(), ImDrawFlags.Closed, RenderThickness);
|
||||
}
|
||||
|
||||
public void FillPie(Color color, int x, int y, int width, int height, int startAngle, int sweepAngle)
|
||||
{
|
||||
var radius = new Vector2(width / 2.0f, height / 2.0f);
|
||||
var center = new Vector2(x + radius.X, y + radius.Y);
|
||||
var aMin = (float)(Math.PI / 180 * startAngle);
|
||||
var aMax = (float)(Math.PI / 180 * (startAngle + sweepAngle));
|
||||
_imGuiDrawList.PathEllipticalArcTo(center, radius, 0, aMin, aMax);
|
||||
_imGuiDrawList.PathLineTo(center);
|
||||
_imGuiDrawList.PathFillConvex((uint)color.ToArgb());
|
||||
}
|
||||
|
||||
public unsafe void DrawPolygon(Color color, Point[] points)
|
||||
{
|
||||
var vectorPoints = Array.ConvertAll(points, static p => p.ToVector());
|
||||
fixed (Vector2* p = vectorPoints)
|
||||
{
|
||||
_imGuiDrawList.AddPolyline(
|
||||
points: ref *p,
|
||||
num_points: vectorPoints.Length,
|
||||
col: (uint)color.ToArgb(),
|
||||
flags: ImDrawFlags.Closed,
|
||||
thickness: RenderThickness);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void FillPolygon(Color color, Point[] points)
|
||||
{
|
||||
var vectorPoints = Array.ConvertAll(points, static p => p.ToVector());
|
||||
fixed (Vector2* p = vectorPoints)
|
||||
{
|
||||
_imGuiDrawList.AddConvexPolyFilled(
|
||||
points: ref *p,
|
||||
num_points: vectorPoints.Length,
|
||||
col: (uint)color.ToArgb());
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawString(string s, Font font, Color color, float x, float y, StringFormat format, TextRenderingHint textRenderingHint)
|
||||
{
|
||||
var stringArgs = new DrawStringArgs { Str = s, Font = font, Color = color, X = x, Y = y, Format = format, TextRenderingHint = textRenderingHint };
|
||||
var handle = GCHandle.Alloc(stringArgs, GCHandleType.Normal);
|
||||
_gcHandles.Add(handle);
|
||||
_imGuiDrawList.AddCallback((IntPtr)DrawCallbackId.DrawString, GCHandle.ToIntPtr(handle));
|
||||
_hasDrawStringCommand = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
using System;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps SDL2's software rendering with an ImGui 2D renderer
|
||||
/// Used for the GDI+ IGL, which doesn't understand vertexes and such
|
||||
/// </summary>
|
||||
internal class SDLImGui2DRenderer : ImGui2DRenderer
|
||||
{
|
||||
// SDL's software renderer sometimes doesn't fill in shapes all the way with a thickness of 1
|
||||
// a thickness of 2 seems to suffice however
|
||||
protected override float RenderThickness => 2.0f;
|
||||
|
||||
public SDLImGui2DRenderer(IGL_GDIPlus gdiPlus, ImGuiResourceCache resourceCache)
|
||||
: base(gdiPlus, resourceCache)
|
||||
{
|
||||
_ = gdiPlus; // this backend must use the GDI+ display method, as it assumes IRenderTarget is an GDIPlusRenderTarget
|
||||
}
|
||||
|
||||
private readonly ref struct SDLSurface
|
||||
{
|
||||
public readonly IntPtr Surface;
|
||||
|
||||
public SDLSurface(BitmapData bmpData)
|
||||
{
|
||||
Surface = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
bmpData.Scan0, bmpData.Width, bmpData.Height, 8, bmpData.Stride, SDL_PIXELFORMAT_ABGR8888);
|
||||
if (Surface == IntPtr.Zero)
|
||||
{
|
||||
throw new($"Failed to create SDL surface, SDL error: {SDL_GetError()}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> SDL_FreeSurface(Surface);
|
||||
}
|
||||
|
||||
private readonly ref struct SDLSoftwareRenderer
|
||||
{
|
||||
public readonly IntPtr Renderer;
|
||||
|
||||
public SDLSoftwareRenderer(SDLSurface surface)
|
||||
{
|
||||
Renderer = SDL_CreateSoftwareRenderer(surface.Surface);
|
||||
if (Renderer == IntPtr.Zero)
|
||||
{
|
||||
throw new($"Failed to create SDL software renderer, SDL error: {SDL_GetError()}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> SDL_DestroyRenderer(Renderer);
|
||||
}
|
||||
|
||||
// SDL2-CS import expects float[]/int[]'s instead of raw pointers :(
|
||||
[DllImport("SDL2", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
private static extern int SDL_RenderGeometryRaw(
|
||||
IntPtr renderer,
|
||||
IntPtr texture,
|
||||
IntPtr xy,
|
||||
int xy_stride,
|
||||
IntPtr color,
|
||||
int color_stride,
|
||||
IntPtr uv,
|
||||
int uv_stride,
|
||||
int num_vertices,
|
||||
IntPtr indices,
|
||||
int num_indices,
|
||||
int size_indices
|
||||
);
|
||||
|
||||
private static unsafe void RenderCommand(IntPtr sdlRenderer, IntPtr sdlTexture, ImDrawListPtr cmdList, ImDrawCmdPtr cmd)
|
||||
{
|
||||
var vtxBuffer = (ImDrawVert*)cmdList.VtxBuffer.Data;
|
||||
var idxBuffer = (ushort*)cmdList.IdxBuffer.Data;
|
||||
var vtx = &vtxBuffer![cmd.VtxOffset];
|
||||
_ = SDL_RenderGeometryRaw(
|
||||
renderer: sdlRenderer,
|
||||
texture: sdlTexture,
|
||||
xy: (IntPtr)(&vtx->pos),
|
||||
xy_stride: sizeof(ImDrawVert),
|
||||
color: (IntPtr)(&vtx->col),
|
||||
color_stride: sizeof(ImDrawVert),
|
||||
uv: (IntPtr)(&vtx->uv),
|
||||
uv_stride: sizeof(ImDrawVert),
|
||||
num_vertices: (int)(cmdList.VtxBuffer.Size - cmd.VtxOffset),
|
||||
indices: (IntPtr)(&idxBuffer![cmd.IdxOffset]),
|
||||
num_indices: (int)cmd.ElemCount,
|
||||
size_indices: sizeof(ushort)
|
||||
);
|
||||
}
|
||||
|
||||
protected override void RenderInternal(int width, int height)
|
||||
{
|
||||
var rt = (GDIPlusRenderTarget)_renderTarget;
|
||||
var bmpData = rt.SDBitmap.LockBits(rt.GetRectangle(), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
using var surface = new SDLSurface(bmpData);
|
||||
using var renderer = new SDLSoftwareRenderer(surface);
|
||||
var sdlRenderer = renderer.Renderer;
|
||||
|
||||
_ = SDL_SetRenderDrawBlendMode(sdlRenderer, EnableBlending
|
||||
? SDL_BlendMode.SDL_BLENDMODE_BLEND
|
||||
: SDL_BlendMode.SDL_BLENDMODE_NONE);
|
||||
|
||||
var rect = new SDL_Rect { x = 0, y = 0, w = width, h = height };
|
||||
_ = SDL_RenderSetViewport(sdlRenderer, ref rect);
|
||||
_ = SDL_RenderSetClipRect(sdlRenderer, IntPtr.Zero);
|
||||
|
||||
var cmdBuffer = _imGuiDrawList.CmdBuffer;
|
||||
for (var i = 0; i < cmdBuffer.Size; i++)
|
||||
{
|
||||
var cmd = cmdBuffer[i];
|
||||
var callbackId = (DrawCallbackId)cmd.UserCallback;
|
||||
switch (callbackId)
|
||||
{
|
||||
case DrawCallbackId.None:
|
||||
{
|
||||
var texId = cmd.GetTexID();
|
||||
if (texId != IntPtr.Zero)
|
||||
{
|
||||
var userTex = (ImGuiUserTexture)GCHandle.FromIntPtr(texId).Target!;
|
||||
var texBmpData = userTex.Bitmap.LockBits(
|
||||
new(0, 0, userTex.Bitmap.Width, userTex.Bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
using var texSurf = new SDLSurface(texBmpData);
|
||||
var sdlTex = SDL_CreateTextureFromSurface(sdlRenderer, texSurf.Surface);
|
||||
if (sdlTex == IntPtr.Zero)
|
||||
{
|
||||
throw new($"Failed to create SDL texture from surface, SDL error: {SDL_GetError()}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RenderCommand(sdlRenderer, sdlTex, _imGuiDrawList, cmd);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SDL_DestroyTexture(sdlTex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
userTex.Bitmap.UnlockBits(texBmpData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderCommand(sdlRenderer, IntPtr.Zero, _imGuiDrawList, cmd);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DrawCallbackId.DisableBlending:
|
||||
_ = SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BlendMode.SDL_BLENDMODE_NONE);
|
||||
break;
|
||||
case DrawCallbackId.EnableBlending:
|
||||
_ = SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BlendMode.SDL_BLENDMODE_BLEND);
|
||||
break;
|
||||
case DrawCallbackId.DrawString:
|
||||
{
|
||||
var stringArgs = (DrawStringArgs)GCHandle.FromIntPtr(cmd.UserCallbackData).Target!;
|
||||
var brush = _resourceCache.BrushCache.GetValueOrPutNew1(stringArgs.Color);
|
||||
_stringGraphics.TextRenderingHint = stringArgs.TextRenderingHint;
|
||||
_stringGraphics.DrawString(stringArgs.Str, stringArgs.Font, brush, stringArgs.X, stringArgs.Y, stringArgs.Format);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rt.SDBitmap.UnlockBits(bmpData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,8 +133,10 @@ namespace BizHawk.Bizware.Graphics
|
|||
pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; //useless color
|
||||
pData[i++] = 1; pData[i] = v1;
|
||||
|
||||
Pipeline.SetVertexData(new(pData), 4);
|
||||
|
||||
Owner.DisableBlending();
|
||||
Owner.Draw(new(pData), 4);
|
||||
Owner.Draw(4);
|
||||
}
|
||||
|
||||
public IGL Owner { get; }
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Drawing.Imaging;
|
|||
using System.Drawing.Text;
|
||||
using System.IO;
|
||||
|
||||
using BizHawk.Bizware.Graphics;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
|
@ -25,15 +26,9 @@ namespace BizHawk.Client.Common
|
|||
|
||||
private readonly DisplayManagerBase _displayManager;
|
||||
|
||||
private readonly Dictionary<string, Image> _imageCache = new Dictionary<string, Image>();
|
||||
private readonly Dictionary<string, Bitmap> _imageCache = new();
|
||||
|
||||
private readonly Bitmap _nullGraphicsBitmap = new Bitmap(1, 1);
|
||||
|
||||
private readonly Dictionary<Color, Pen> _pens = new Dictionary<Color, Pen>();
|
||||
|
||||
private readonly Dictionary<Color, SolidBrush> _solidBrushes = new Dictionary<Color, SolidBrush>();
|
||||
|
||||
private ImageAttributes _attributes = new ImageAttributes();
|
||||
private readonly Bitmap _nullGraphicsBitmap = new(1, 1);
|
||||
|
||||
private CompositingMode _compositingMode = CompositingMode.SourceOver;
|
||||
|
||||
|
@ -43,19 +38,12 @@ namespace BizHawk.Client.Common
|
|||
|
||||
private int _defaultPixelFont = 1; // = "gens"
|
||||
|
||||
private Color? _defaultTextBackground = Color.FromArgb(128, 0, 0, 0);
|
||||
|
||||
private IDisplaySurface _clientSurface;
|
||||
|
||||
private IDisplaySurface _GUISurface;
|
||||
private Color _defaultTextBackground = Color.FromArgb(128, 0, 0, 0);
|
||||
|
||||
private (int Left, int Top, int Right, int Bottom) _padding = (0, 0, 0, 0);
|
||||
|
||||
private DisplaySurfaceID? _usingSurfaceID = null;
|
||||
|
||||
public bool EnableLuaAutolockHack = false;
|
||||
|
||||
public bool HasGUISurface => _GUISurface != null;
|
||||
private DisplaySurfaceID? _usingSurfaceID;
|
||||
public bool HasGUISurface => true;
|
||||
|
||||
public GuiApi(Action<string> logCallback, DisplayManagerBase displayManager)
|
||||
{
|
||||
|
@ -63,90 +51,23 @@ namespace BizHawk.Client.Common
|
|||
_displayManager = displayManager;
|
||||
}
|
||||
|
||||
private SolidBrush GetBrush(Color color)
|
||||
=> _solidBrushes.GetValueOrPutNew1(color);
|
||||
|
||||
private Pen GetPen(Color color)
|
||||
=> _pens.GetValueOrPutNew1(color);
|
||||
|
||||
private Graphics GetGraphics(DisplaySurfaceID? surfaceID)
|
||||
private I2DRenderer Get2DRenderer(DisplaySurfaceID? surfaceID)
|
||||
{
|
||||
var g = GetRelevantSurface(surfaceID)?.GetGraphics() ?? Graphics.FromImage(_nullGraphicsBitmap);
|
||||
return g;
|
||||
var nnID = surfaceID ?? _usingSurfaceID ?? throw new Exception();
|
||||
return _displayManager.GetApiHawk2DRenderer(nnID);
|
||||
}
|
||||
|
||||
public void ToggleCompositingMode()
|
||||
=> _compositingMode = (CompositingMode) (1 - (int) _compositingMode); // enum has two members, 0 and 1
|
||||
|
||||
public ImageAttributes GetAttributes() => _attributes;
|
||||
public ImageAttributes GetAttributes() => null;
|
||||
|
||||
public void SetAttributes(ImageAttributes a) => _attributes = a;
|
||||
|
||||
private IDisplaySurface GetRelevantSurface(DisplaySurfaceID? surfaceID)
|
||||
public void SetAttributes(ImageAttributes a)
|
||||
{
|
||||
var nnID = surfaceID ?? _usingSurfaceID ?? throw new Exception();
|
||||
void ThisIsTheLuaAutolockHack()
|
||||
{
|
||||
try
|
||||
{
|
||||
UnlockSurface(nnID);
|
||||
LockSurface(nnID);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
LogCallback(ex.ToString());
|
||||
}
|
||||
}
|
||||
switch (nnID)
|
||||
{
|
||||
case DisplaySurfaceID.EmuCore:
|
||||
if (_GUISurface == null && EnableLuaAutolockHack) ThisIsTheLuaAutolockHack();
|
||||
return _GUISurface;
|
||||
case DisplaySurfaceID.Client:
|
||||
if (_clientSurface == null && EnableLuaAutolockHack) ThisIsTheLuaAutolockHack();
|
||||
return _clientSurface;
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
private void LockSurface(DisplaySurfaceID surfaceID)
|
||||
{
|
||||
switch (surfaceID)
|
||||
{
|
||||
case DisplaySurfaceID.EmuCore:
|
||||
if (_GUISurface != null) throw new InvalidOperationException("attempt to lock surface without unlocking previous");
|
||||
_GUISurface = _displayManager.LockApiHawkSurface(surfaceID, clear: true);
|
||||
break;
|
||||
case DisplaySurfaceID.Client:
|
||||
if (_clientSurface != null) throw new InvalidOperationException("attempt to lock surface without unlocking previous");
|
||||
_clientSurface = _displayManager.LockApiHawkSurface(surfaceID, clear: true);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(message: "not a valid enum member", paramName: nameof(surfaceID));
|
||||
}
|
||||
}
|
||||
|
||||
private void UnlockSurface(DisplaySurfaceID surfaceID)
|
||||
{
|
||||
switch (surfaceID)
|
||||
{
|
||||
case DisplaySurfaceID.EmuCore:
|
||||
if (_GUISurface != null) _displayManager.UnlockApiHawkSurface(_GUISurface);
|
||||
_GUISurface = null;
|
||||
break;
|
||||
case DisplaySurfaceID.Client:
|
||||
if (_clientSurface != null) _displayManager.UnlockApiHawkSurface(_clientSurface);
|
||||
_clientSurface = null;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(message: "not a valid enum member", paramName: nameof(surfaceID));
|
||||
}
|
||||
}
|
||||
|
||||
public void WithSurface(DisplaySurfaceID surfaceID, Action drawingCallsFunc)
|
||||
{
|
||||
LockSurface(surfaceID);
|
||||
_usingSurfaceID = surfaceID;
|
||||
try
|
||||
{
|
||||
|
@ -155,27 +76,9 @@ namespace BizHawk.Client.Common
|
|||
finally
|
||||
{
|
||||
_usingSurfaceID = null;
|
||||
UnlockSurface(surfaceID);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ref struct LuaAutoUnlockHack
|
||||
{
|
||||
private readonly GuiApi _guiApi;
|
||||
|
||||
internal LuaAutoUnlockHack(GuiApi guiApi)
|
||||
=> _guiApi = guiApi;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_guiApi.UnlockSurface(DisplaySurfaceID.EmuCore);
|
||||
_guiApi.UnlockSurface(DisplaySurfaceID.Client);
|
||||
}
|
||||
}
|
||||
|
||||
public LuaAutoUnlockHack ThisIsTheLuaAutoUnlockHack()
|
||||
=> new(this);
|
||||
|
||||
public void DrawNew(string name, bool clear)
|
||||
{
|
||||
switch (name)
|
||||
|
@ -204,7 +107,7 @@ namespace BizHawk.Client.Common
|
|||
public void AddMessage(string message, int? duration = null)
|
||||
=> _displayManager.OSD.AddMessage(message, duration);
|
||||
|
||||
public void ClearGraphics(DisplaySurfaceID? surfaceID = null) => GetRelevantSurface(surfaceID).Clear();
|
||||
public void ClearGraphics(DisplaySurfaceID? surfaceID = null) => Get2DRenderer(surfaceID).Clear();
|
||||
|
||||
public void ClearText() => _displayManager.OSD.ClearGuiText();
|
||||
|
||||
|
@ -212,7 +115,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void SetDefaultBackgroundColor(Color color) => _defaultBackground = color;
|
||||
|
||||
public Color? GetDefaultTextBackground() => _defaultTextBackground;
|
||||
public Color GetDefaultTextBackground() => _defaultTextBackground;
|
||||
|
||||
public void SetDefaultTextBackground(Color color) => _defaultTextBackground = color;
|
||||
|
||||
|
@ -238,9 +141,9 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawBezier(GetPen(color ?? _defaultForeground), p1, p2, p3, p4);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawBezier(color ?? _defaultForeground, p1, p2, p3, p4);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -252,9 +155,9 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawBeziers(GetPen(color ?? _defaultForeground), points);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawBeziers(color ?? _defaultForeground, points);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -266,7 +169,7 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
float w;
|
||||
int w;
|
||||
if (x < x2)
|
||||
{
|
||||
w = x2 - x;
|
||||
|
@ -275,9 +178,9 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
x2 = x - x2;
|
||||
x -= x2;
|
||||
w = Math.Max(x2, 0.1f);
|
||||
w = Math.Max(x2, 1);
|
||||
}
|
||||
float h;
|
||||
int h;
|
||||
if (y < y2)
|
||||
{
|
||||
h = y2 - y;
|
||||
|
@ -286,13 +189,13 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
y2 = y - y2;
|
||||
y -= y2;
|
||||
h = Math.Max(y2, 0.1f);
|
||||
h = Math.Max(y2, 1);
|
||||
}
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawRectangle(GetPen(line ?? _defaultForeground), x, y, w, h);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawRectangle(line ?? _defaultForeground, x, y, w, h);
|
||||
var bg = background ?? _defaultBackground;
|
||||
if (bg != null) g.FillRectangle(GetBrush(bg.Value), x + 1, y + 1, Math.Max(w - 1, 0), Math.Max(h - 1, 0));
|
||||
if (bg != null) r.FillRectangle(bg.Value, x + 1, y + 1, Math.Max(w - 2, 0), Math.Max(h - 2, 0));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -304,11 +207,11 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
var bg = background ?? _defaultBackground;
|
||||
if (bg != null) g.FillEllipse(GetBrush(bg.Value), x, y, width, height);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawEllipse(GetPen(line ?? _defaultForeground), x, y, width, height);
|
||||
if (bg != null) r.FillEllipse(bg.Value, x, y, width, height);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawEllipse(line ?? _defaultForeground, x, y, width, height);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -325,15 +228,14 @@ namespace BizHawk.Client.Common
|
|||
AddMessage($"File not found: {path}");
|
||||
return;
|
||||
}
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawIcon(
|
||||
width != null && height != null
|
||||
? new Icon(path, width.Value, height.Value)
|
||||
: new Icon(path),
|
||||
x,
|
||||
y
|
||||
);
|
||||
|
||||
var icon = width != null && height != null
|
||||
? new Icon(path, width.Value, height.Value)
|
||||
: new Icon(path);
|
||||
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawImage(icon.ToBitmap(), x, y);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -343,19 +245,19 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void DrawImage(Image img, int x, int y, int? width = null, int? height = null, bool cache = true, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawImage(
|
||||
img,
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawImage(
|
||||
new(img),
|
||||
new Rectangle(x, y, width ?? img.Width, height ?? img.Height),
|
||||
0,
|
||||
0,
|
||||
img.Width,
|
||||
img.Height,
|
||||
GraphicsUnit.Pixel,
|
||||
_attributes
|
||||
cache: false // caching is meaningless here
|
||||
);
|
||||
}
|
||||
|
||||
public void DrawImage(string path, int x, int y, int? width = null, int? height = null, bool cache = true, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
|
@ -363,44 +265,41 @@ namespace BizHawk.Client.Common
|
|||
LogCallback($"File not found: {path}");
|
||||
return;
|
||||
}
|
||||
using var g = GetGraphics(surfaceID);
|
||||
|
||||
if (!_imageCache.TryGetValue(path, out var img))
|
||||
{
|
||||
img = Image.FromFile(path);
|
||||
img = new(Image.FromFile(path));
|
||||
if (cache) _imageCache[path] = img;
|
||||
}
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawImage(
|
||||
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawImage(
|
||||
img,
|
||||
new Rectangle(x, y, width ?? img.Width, height ?? img.Height),
|
||||
0,
|
||||
0,
|
||||
img.Width,
|
||||
img.Height,
|
||||
GraphicsUnit.Pixel,
|
||||
_attributes
|
||||
cache
|
||||
);
|
||||
}
|
||||
|
||||
public void ClearImageCache()
|
||||
{
|
||||
foreach (var image in _imageCache) image.Value.Dispose();
|
||||
_imageCache.Clear();
|
||||
}
|
||||
=> _imageCache.Clear();
|
||||
|
||||
public void DrawImageRegion(Image img, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, int? dest_width = null, int? dest_height = null, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawImage(
|
||||
img,
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawImage(
|
||||
new(img),
|
||||
new Rectangle(dest_x, dest_y, dest_width ?? source_width, dest_height ?? source_height),
|
||||
source_x,
|
||||
source_y,
|
||||
source_width,
|
||||
source_height,
|
||||
GraphicsUnit.Pixel,
|
||||
_attributes
|
||||
cache: false
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -411,25 +310,25 @@ namespace BizHawk.Client.Common
|
|||
LogCallback($"File not found: {path}");
|
||||
return;
|
||||
}
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawImage(
|
||||
_imageCache.GetValueOrPut(path, Image.FromFile),
|
||||
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawImage(
|
||||
_imageCache.GetValueOrPut(path, static i => new(Image.FromFile(i))),
|
||||
new Rectangle(dest_x, dest_y, dest_width ?? source_width, dest_height ?? source_height),
|
||||
source_x,
|
||||
source_y,
|
||||
source_width,
|
||||
source_height,
|
||||
GraphicsUnit.Pixel,
|
||||
_attributes
|
||||
cache: true
|
||||
);
|
||||
}
|
||||
|
||||
public void DrawLine(int x1, int y1, int x2, int y2, Color? color = null, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
g.DrawLine(GetPen(color ?? _defaultForeground), x1, y1, x2, y2);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
r.DrawLine(color ?? _defaultForeground, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
public void DrawAxis(int x, int y, int size, Color? color = null, DisplaySurfaceID? surfaceID = null)
|
||||
|
@ -440,19 +339,19 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void DrawPie(int x, int y, int width, int height, int startangle, int sweepangle, Color? line = null, Color? background = null, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.CompositingMode = _compositingMode;
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.CompositingMode = _compositingMode;
|
||||
var bg = background ?? _defaultBackground;
|
||||
if (bg != null) g.FillPie(GetBrush(bg.Value), x, y, width, height, startangle, sweepangle);
|
||||
g.DrawPie(GetPen(line ?? _defaultForeground), x + 1, y + 1, width - 1, height - 1, startangle, sweepangle);
|
||||
if (bg != null) r.FillPie(bg.Value, x, y, width, height, startangle, sweepangle);
|
||||
r.DrawPie(line ?? _defaultForeground, x + 1, y + 1, width - 1, height - 1, startangle, sweepangle);
|
||||
}
|
||||
|
||||
public void DrawPixel(int x, int y, Color? color = null, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.DrawLine(GetPen(color ?? _defaultForeground), x, y, x + 0.1F, y);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.DrawLine(color ?? _defaultForeground, x, y, x + 1, y);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -464,10 +363,10 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
g.DrawPolygon(GetPen(line ?? _defaultForeground), points);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
r.DrawPolygon(line ?? _defaultForeground, points);
|
||||
var bg = background ?? _defaultBackground;
|
||||
if (bg != null) g.FillPolygon(GetBrush(bg.Value), points);
|
||||
if (bg != null) r.FillPolygon(bg.Value, points);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -477,12 +376,12 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void DrawRectangle(int x, int y, int width, int height, Color? line = null, Color? background = null, DisplaySurfaceID? surfaceID = null)
|
||||
{
|
||||
using var g = GetGraphics(surfaceID);
|
||||
var w = Math.Max(width, 0.1F);
|
||||
var h = Math.Max(height, 0.1F);
|
||||
g.DrawRectangle(GetPen(line ?? _defaultForeground), x, y, w, h);
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
var w = Math.Max(width, 1);
|
||||
var h = Math.Max(height, 1);
|
||||
r.DrawRectangle(line ?? _defaultForeground, x, y, w, h);
|
||||
var bg = background ?? _defaultBackground;
|
||||
if (bg != null) g.FillRectangle(GetBrush(bg.Value), x + 1, y + 1, Math.Max(w - 1, 0), Math.Max(h - 1, 0));
|
||||
if (bg != null) r.FillRectangle(bg.Value, x + 1, y + 1, Math.Max(w - 2, 0), Math.Max(h - 2, 0));
|
||||
}
|
||||
|
||||
public void DrawString(int x, int y, string message, Color? forecolor = null, Color? backcolor = null, int? fontsize = null, string fontfamily = null, string fontstyle = null, string horizalign = null, string vertalign = null, DisplaySurfaceID? surfaceID = null)
|
||||
|
@ -500,7 +399,7 @@ namespace BizHawk.Client.Common
|
|||
_ => FontStyle.Regular
|
||||
};
|
||||
|
||||
using var g = GetGraphics(surfaceID);
|
||||
using var g = Graphics.FromImage(_nullGraphicsBitmap);
|
||||
|
||||
// The text isn't written out using GenericTypographic, so measuring it using GenericTypographic seemed to make it worse.
|
||||
// And writing it out with GenericTypographic just made it uglier. :p
|
||||
|
@ -535,17 +434,17 @@ namespace BizHawk.Client.Common
|
|||
break;
|
||||
}
|
||||
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
var bg = backcolor ?? _defaultBackground;
|
||||
if (bg != null)
|
||||
{
|
||||
var brush = GetBrush(bg.Value);
|
||||
for (var xd = -1; xd <= 1; xd++) for (var yd = -1; yd <= 1; yd++)
|
||||
{
|
||||
g.DrawString(message, font, brush, x + xd, y + yd);
|
||||
r.DrawString(message, font, bg.Value, x + xd, y + yd);
|
||||
}
|
||||
}
|
||||
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
|
||||
g.DrawString(message, font, GetBrush(forecolor ?? _defaultForeground), x, y);
|
||||
|
||||
r.DrawString(message, font, forecolor ?? _defaultForeground, x, y, textRenderingHint: TextRenderingHint.SingleBitPerPixelGridFit);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -594,12 +493,14 @@ namespace BizHawk.Client.Common
|
|||
index = _defaultPixelFont;
|
||||
break;
|
||||
}
|
||||
using var g = GetGraphics(surfaceID);
|
||||
|
||||
using var g = Graphics.FromImage(_nullGraphicsBitmap);
|
||||
var font = new Font(_displayManager.CustomFonts.Families[index], 8, FontStyle.Regular, GraphicsUnit.Pixel);
|
||||
var sizeOfText = g.MeasureString(message, font, width: 0, PixelTextFormat).ToSize();
|
||||
if (backcolor.HasValue) g.FillRectangle(GetBrush(backcolor.Value), new Rectangle(new Point(x, y), sizeOfText + new Size(1, 0)));
|
||||
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
|
||||
g.DrawString(message, font, GetBrush(forecolor ?? _defaultForeground), x + 1, y, PixelTextFormat);
|
||||
|
||||
var r = Get2DRenderer(surfaceID);
|
||||
if (backcolor.HasValue) r.FillRectangle(backcolor.Value, x, y, sizeOfText.Width + 2, sizeOfText.Height);
|
||||
r.DrawString(message, font, forecolor ?? _defaultForeground, x + 1, y, PixelTextFormat, TextRenderingHint.SingleBitPerPixelGridFit);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -632,17 +533,11 @@ namespace BizHawk.Client.Common
|
|||
y -= oy;
|
||||
}
|
||||
|
||||
var pos = new MessagePosition{ X = x, Y = y, Anchor = (MessagePosition.AnchorType)a };
|
||||
var pos = new MessagePosition { X = x, Y = y, Anchor = (MessagePosition.AnchorType)a };
|
||||
_displayManager.OSD.AddGuiText(message, pos, Color.Black, forecolor ?? Color.White);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnlockSurface(DisplaySurfaceID.EmuCore);
|
||||
UnlockSurface(DisplaySurfaceID.Client);
|
||||
foreach (var brush in _solidBrushes.Values) brush.Dispose();
|
||||
foreach (var brush in _pens.Values) brush.Dispose();
|
||||
ClearImageCache();
|
||||
}
|
||||
=> ClearImageCache();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,21 @@ namespace BizHawk.Client.Common
|
|||
public interface IGuiApi : IDisposable, IExternalApi
|
||||
{
|
||||
void ToggleCompositingMode();
|
||||
|
||||
[Obsolete("No longer supported, returns null always.")]
|
||||
ImageAttributes GetAttributes();
|
||||
[Obsolete("No longer supported, no-op.")]
|
||||
void SetAttributes(ImageAttributes a);
|
||||
|
||||
void WithSurface(DisplaySurfaceID surfaceID, Action drawingCallsFunc);
|
||||
|
||||
[Obsolete]
|
||||
[Obsolete("No longer supported, no-op.")]
|
||||
void DrawNew(string name, bool clear = true);
|
||||
|
||||
[Obsolete]
|
||||
[Obsolete("No longer supported, no-op.")]
|
||||
void DrawFinish();
|
||||
|
||||
[Obsolete]
|
||||
[Obsolete("Always true")]
|
||||
bool HasGUISurface { get; }
|
||||
|
||||
void SetPadding(int all);
|
||||
|
@ -32,7 +35,7 @@ namespace BizHawk.Client.Common
|
|||
void ClearText();
|
||||
void SetDefaultForegroundColor(Color color);
|
||||
void SetDefaultBackgroundColor(Color color);
|
||||
Color? GetDefaultTextBackground();
|
||||
Color GetDefaultTextBackground();
|
||||
void SetDefaultTextBackground(Color color);
|
||||
void SetDefaultPixelFont(string fontfamily);
|
||||
void DrawBezier(Point p1, Point p2, Point p3, Point p4, Color? color = null, DisplaySurfaceID? surfaceID = null);
|
||||
|
|
|
@ -12,7 +12,6 @@ using System.Runtime.InteropServices;
|
|||
using BizHawk.Bizware.Graphics;
|
||||
using BizHawk.Client.Common.FilterManager;
|
||||
using BizHawk.Client.Common.Filters;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
using BizHawk.Common.PathExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Consoles.Nintendo.N3DS;
|
||||
|
@ -28,8 +27,6 @@ namespace BizHawk.Client.Common
|
|||
/// </summary>
|
||||
public abstract class DisplayManagerBase : IDisposable
|
||||
{
|
||||
private static DisplaySurface CreateDisplaySurface(int w, int h) => new(w, h);
|
||||
|
||||
protected class DisplayManagerRenderTargetProvider : IRenderTargetProvider
|
||||
{
|
||||
private readonly Func<Size, IRenderTarget> _callback;
|
||||
|
@ -66,9 +63,6 @@ namespace BizHawk.Client.Common
|
|||
_gl = gl;
|
||||
_renderer = renderer;
|
||||
|
||||
// it's sort of important for these to be initialized to something nonzero
|
||||
_currEmuWidth = _currEmuHeight = 1;
|
||||
|
||||
_videoTextureFrugalizer = new(_gl);
|
||||
|
||||
_shaderChainFrugalizers = new RenderTargetFrugalizer[16]; // hacky hardcoded limit.. need some other way to manage these
|
||||
|
@ -110,10 +104,9 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
}
|
||||
|
||||
_apiHawkSurfaceSets[DisplaySurfaceID.EmuCore] = new(CreateDisplaySurface);
|
||||
_apiHawkSurfaceSets[DisplaySurfaceID.Client] = new(CreateDisplaySurface);
|
||||
_apiHawkSurfaceFrugalizers[DisplaySurfaceID.EmuCore] = new(_gl);
|
||||
_apiHawkSurfaceFrugalizers[DisplaySurfaceID.Client] = new(_gl);
|
||||
_imGuiResourceCache = new ImGuiResourceCache(_gl);
|
||||
_apiHawkIDTo2DRenderer.Add(DisplaySurfaceID.EmuCore, _gl.Create2DRenderer(_imGuiResourceCache));
|
||||
_apiHawkIDTo2DRenderer.Add(DisplaySurfaceID.Client, _gl.Create2DRenderer(_imGuiResourceCache));
|
||||
|
||||
RefreshUserShader();
|
||||
}
|
||||
|
@ -140,11 +133,14 @@ namespace BizHawk.Client.Common
|
|||
ActivateOpenGLContext();
|
||||
|
||||
_videoTextureFrugalizer.Dispose();
|
||||
foreach (var f in _apiHawkSurfaceFrugalizers.Values)
|
||||
|
||||
foreach (var r in _apiHawkIDTo2DRenderer.Values)
|
||||
{
|
||||
f.Dispose();
|
||||
r.Dispose();
|
||||
}
|
||||
|
||||
_imGuiResourceCache.Dispose();
|
||||
|
||||
foreach (var f in _shaderChainFrugalizers)
|
||||
{
|
||||
f?.Dispose();
|
||||
|
@ -169,12 +165,6 @@ namespace BizHawk.Client.Common
|
|||
// layer resources
|
||||
protected FilterProgram _currentFilterProgram;
|
||||
|
||||
/// <summary>
|
||||
/// these variables will track the dimensions of the last frame's (or the next frame? this is confusing) emulator native output size
|
||||
/// THIS IS OLD JUNK. I should get rid of it, I think. complex results from the last filter ingestion should be saved instead.
|
||||
/// </summary>
|
||||
private int _currEmuWidth, _currEmuHeight;
|
||||
|
||||
/// <summary>
|
||||
/// additional pixels added at the unscaled level for the use of lua drawing. essentially increases the input video provider dimensions
|
||||
/// </summary>
|
||||
|
@ -192,8 +182,6 @@ namespace BizHawk.Client.Common
|
|||
|
||||
private readonly TextureFrugalizer _videoTextureFrugalizer;
|
||||
|
||||
private readonly Dictionary<DisplaySurfaceID, TextureFrugalizer> _apiHawkSurfaceFrugalizers = new();
|
||||
|
||||
protected readonly RenderTargetFrugalizer[] _shaderChainFrugalizers;
|
||||
|
||||
private readonly RetroShaderChain _shaderChainHq2X;
|
||||
|
@ -402,16 +390,9 @@ namespace BizHawk.Client.Common
|
|||
|
||||
private void AppendApiHawkLayer(FilterProgram chain, DisplaySurfaceID surfaceID)
|
||||
{
|
||||
var luaNativeSurface = _apiHawkSurfaceSets[surfaceID].GetCurrent();
|
||||
if (luaNativeSurface == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var luaNativeTexture = _apiHawkSurfaceFrugalizers[surfaceID].Get(luaNativeSurface);
|
||||
var fLuaLayer = new LuaLayer();
|
||||
fLuaLayer.SetTexture(luaNativeTexture);
|
||||
chain.AddFilter(fLuaLayer, surfaceID.GetName());
|
||||
var apiHawkRenderer = _apiHawkIDTo2DRenderer[surfaceID];
|
||||
var fApiHawkLayer = new ApiHawkLayer(apiHawkRenderer);
|
||||
chain.AddFilter(fApiHawkLayer, surfaceID.GetName());
|
||||
}
|
||||
|
||||
protected abstract Point GraphicsControlPointToClient(Point p);
|
||||
|
@ -815,10 +796,6 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
}
|
||||
|
||||
// record the size of what we received, since lua and stuff is gonna want to draw onto it
|
||||
_currEmuWidth = bufferWidth;
|
||||
_currEmuHeight = bufferHeight;
|
||||
|
||||
//build the default filter chain and set it up with services filters will need
|
||||
var chainInsize = new Size(bufferWidth, bufferHeight);
|
||||
|
||||
|
@ -930,87 +907,23 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<DisplaySurfaceID, IDisplaySurface> _apiHawkIDToSurface = new();
|
||||
|
||||
/// <remarks>Can't this just be a prop of <see cref="IDisplaySurface"/>? --yoshi</remarks>
|
||||
private readonly Dictionary<IDisplaySurface, DisplaySurfaceID> _apiHawkSurfaceToID = new();
|
||||
|
||||
private readonly Dictionary<DisplaySurfaceID, SwappableDisplaySurfaceSet<DisplaySurface>> _apiHawkSurfaceSets = new();
|
||||
private readonly ImGuiResourceCache _imGuiResourceCache;
|
||||
private readonly Dictionary<DisplaySurfaceID, I2DRenderer> _apiHawkIDTo2DRenderer = new();
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a locked lua surface, or returns null if it isn't locked
|
||||
/// Gets an ApiHawk 2D renderer, suitable for drawing with Gui/lua apis and such
|
||||
/// The size of this surface might change between different calls
|
||||
/// Implicitly, if the size changes the surface will be cleared
|
||||
/// </summary>
|
||||
public IDisplaySurface PeekApiHawkLockedSurface(DisplaySurfaceID surfaceID)
|
||||
=> _apiHawkIDToSurface.TryGetValue(surfaceID, out var surface) ? surface : null;
|
||||
|
||||
public IDisplaySurface LockApiHawkSurface(DisplaySurfaceID surfaceID, bool clear)
|
||||
{
|
||||
if (_apiHawkIDToSurface.ContainsKey(surfaceID))
|
||||
{
|
||||
throw new InvalidOperationException($"ApiHawk/Lua surface is already locked: {surfaceID.GetName()}");
|
||||
}
|
||||
|
||||
var sdss = _apiHawkSurfaceSets.GetValueOrPut(surfaceID, static _ => new(CreateDisplaySurface));
|
||||
|
||||
// placeholder logic for more abstracted surface definitions from filter chain
|
||||
var (currNativeWidth, currNativeHeight) = GetPanelNativeSize();
|
||||
currNativeWidth += ClientExtraPadding.Left + ClientExtraPadding.Right;
|
||||
currNativeHeight += ClientExtraPadding.Top + ClientExtraPadding.Bottom;
|
||||
|
||||
var (width, height) = surfaceID switch
|
||||
{
|
||||
DisplaySurfaceID.EmuCore => (GameExtraPadding.Left + _currEmuWidth + GameExtraPadding.Right, GameExtraPadding.Top + _currEmuHeight + GameExtraPadding.Bottom),
|
||||
DisplaySurfaceID.Client => (currNativeWidth, currNativeHeight),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
IDisplaySurface ret = sdss.AllocateSurface(width, height, clear);
|
||||
_apiHawkIDToSurface[surfaceID] = ret;
|
||||
_apiHawkSurfaceToID[ret] = surfaceID;
|
||||
return ret;
|
||||
}
|
||||
public I2DRenderer GetApiHawk2DRenderer(DisplaySurfaceID surfaceID)
|
||||
=> _apiHawkIDTo2DRenderer[surfaceID];
|
||||
|
||||
public void ClearApiHawkSurfaces()
|
||||
{
|
||||
foreach (var kvp in _apiHawkSurfaceSets)
|
||||
foreach (var renderer in _apiHawkIDTo2DRenderer.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PeekApiHawkLockedSurface(kvp.Key) == null)
|
||||
{
|
||||
var surfLocked = LockApiHawkSurface(kvp.Key, true);
|
||||
if (surfLocked != null)
|
||||
{
|
||||
UnlockApiHawkSurface(surfLocked);
|
||||
}
|
||||
}
|
||||
|
||||
_apiHawkSurfaceSets[kvp.Key].SetPending(null);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
renderer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>unlocks this IDisplaySurface which had better have been locked as a lua surface</summary>
|
||||
/// <exception cref="InvalidOperationException">already unlocked</exception>
|
||||
public void UnlockApiHawkSurface(IDisplaySurface surface)
|
||||
{
|
||||
if (surface is not DisplaySurface dispSurfaceImpl)
|
||||
{
|
||||
throw new ArgumentException("don't mix " + nameof(IDisplaySurface) + " implementations!", nameof(surface));
|
||||
}
|
||||
|
||||
if (!_apiHawkSurfaceToID.TryGetValue(dispSurfaceImpl, out var surfaceID))
|
||||
{
|
||||
throw new InvalidOperationException("Surface was not locked as a lua surface");
|
||||
}
|
||||
|
||||
_apiHawkSurfaceToID.Remove(dispSurfaceImpl);
|
||||
_apiHawkIDToSurface.Remove(surfaceID);
|
||||
_apiHawkSurfaceSets[surfaceID].SetPending(dispSurfaceImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a wrapper for a Bitmap.
|
||||
/// It should be phased out, in favor of BitmapBuffer and Texture2d's
|
||||
/// </summary>
|
||||
public unsafe class DisplaySurface : IDisplaySurface
|
||||
{
|
||||
private const PixelFormat Format = PixelFormat.Format32bppArgb;
|
||||
|
||||
private Bitmap _bmp;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var bmpData = _bmp.LockBits(new(0, 0, Width, Height), ImageLockMode.WriteOnly, Format);
|
||||
new Span<byte>((void*)bmpData.Scan0, bmpData.Stride * bmpData.Height).Clear();
|
||||
_bmp.UnlockBits(bmpData);
|
||||
}
|
||||
|
||||
public Bitmap PeekBitmap()
|
||||
{
|
||||
return _bmp;
|
||||
}
|
||||
|
||||
public Graphics GetGraphics()
|
||||
{
|
||||
return Graphics.FromImage(_bmp);
|
||||
}
|
||||
|
||||
public DisplaySurface(int width, int height)
|
||||
{
|
||||
// can't create a bitmap with zero dimensions, so for now, just bump it up to one
|
||||
if (width == 0)
|
||||
{
|
||||
width = 1;
|
||||
}
|
||||
|
||||
if (height == 0)
|
||||
{
|
||||
height = 1;
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
_bmp = new Bitmap(Width, Height, Format);
|
||||
}
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_bmp?.Dispose();
|
||||
_bmp = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -525,9 +525,13 @@ namespace BizHawk.Client.Common.Filters
|
|||
}
|
||||
}
|
||||
|
||||
/// <remarks>More accurately, ApiHawkLayer, since the <c>gui</c> Lua library is delegated.</remarks>
|
||||
public class LuaLayer : BaseFilter
|
||||
public class ApiHawkLayer : BaseFilter
|
||||
{
|
||||
private readonly I2DRenderer _renderer;
|
||||
|
||||
public ApiHawkLayer(I2DRenderer renderer)
|
||||
=> _renderer = renderer;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
DeclareInput(SurfaceDisposition.RenderTarget);
|
||||
|
@ -538,19 +542,26 @@ namespace BizHawk.Client.Common.Filters
|
|||
DeclareOutput(state);
|
||||
}
|
||||
|
||||
private ITexture2D _texture;
|
||||
|
||||
public void SetTexture(ITexture2D tex)
|
||||
{
|
||||
_texture = tex;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
var outSize = FindOutput().SurfaceFormat.Size;
|
||||
|
||||
var output = _renderer.Render(outSize.Width, outSize.Height);
|
||||
|
||||
// render target might have changed when rendering, rebind the filter chain's target
|
||||
var rt = FilterProgram.CurrRenderTarget;
|
||||
if (rt == null)
|
||||
{
|
||||
FilterProgram.GL.BindDefaultRenderTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
rt.Bind();
|
||||
}
|
||||
|
||||
FilterProgram.GuiRenderer.Begin(outSize);
|
||||
FilterProgram.GuiRenderer.EnableBlending();
|
||||
FilterProgram.GuiRenderer.Draw(_texture);
|
||||
FilterProgram.GuiRenderer.Draw(output);
|
||||
FilterProgram.GuiRenderer.End();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
public interface IDisplaySurface : IDisposable
|
||||
{
|
||||
int Height { get; }
|
||||
|
||||
int Width { get; }
|
||||
|
||||
void Clear();
|
||||
|
||||
/// <returns>a <see cref="Graphics"/> used to render to this surface; be sure to dispose it!</returns>
|
||||
Graphics GetGraphics();
|
||||
|
||||
Bitmap PeekBitmap();
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// encapsulates thread-safe concept of pending/current display surfaces, reusing buffers where matching
|
||||
/// sizes are available and keeping them cleaned up when they don't seem like they'll need to be used anymore
|
||||
/// </summary>
|
||||
public class SwappableDisplaySurfaceSet<T>
|
||||
where T : class, IDisplaySurface
|
||||
{
|
||||
private readonly Func<int, int, T> _createDispSurface;
|
||||
|
||||
private T _pending, _current;
|
||||
private bool _isPending;
|
||||
private readonly Queue<T> _releasedSurfaces = new();
|
||||
|
||||
public SwappableDisplaySurfaceSet(Func<int, int, T> createDispSurface) => _createDispSurface = createDispSurface;
|
||||
|
||||
/// <summary>
|
||||
/// retrieves a surface with the specified size, reusing an old buffer if available and clearing if requested
|
||||
/// </summary>
|
||||
public T AllocateSurface(int width, int height, bool needsClear = true)
|
||||
{
|
||||
for (; ; )
|
||||
{
|
||||
T trial;
|
||||
lock (this)
|
||||
{
|
||||
if (_releasedSurfaces.Count == 0) break;
|
||||
trial = _releasedSurfaces.Dequeue();
|
||||
}
|
||||
if (trial.Width == width && trial.Height == height)
|
||||
{
|
||||
if (needsClear)
|
||||
{
|
||||
trial.Clear();
|
||||
}
|
||||
|
||||
return trial;
|
||||
}
|
||||
|
||||
trial.Dispose();
|
||||
}
|
||||
|
||||
return _createDispSurface(width, height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sets the provided buffer as pending. takes control of the supplied buffer
|
||||
/// </summary>
|
||||
public void SetPending(T newPending)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (_pending != null) _releasedSurfaces.Enqueue(_pending);
|
||||
_pending = newPending;
|
||||
_isPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSurface(T surface)
|
||||
{
|
||||
lock (this) _releasedSurfaces.Enqueue(surface);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the current buffer, making the most recent pending buffer (if there is such) as the new current first.
|
||||
/// </summary>
|
||||
public T GetCurrent()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (_isPending)
|
||||
{
|
||||
if (_current != null) _releasedSurfaces.Enqueue(_current);
|
||||
_current = _pending;
|
||||
_pending = null;
|
||||
_isPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
return _current;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ using BizHawk.Bizware.Graphics;
|
|||
namespace BizHawk.Client.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Recycles a pair of temporary textures (in case double-buffering helps any) to contain a BitmapBuffer's or DisplaySurface's contents, as long as the dimensions match.
|
||||
/// Recycles a pair of temporary textures (in case double-buffering helps any) to contain a BitmapBuffer's contents, as long as the dimensions match.
|
||||
/// When the dimensions don't match, a new one will be allocated
|
||||
/// </summary>
|
||||
public class TextureFrugalizer : IDisposable
|
||||
|
@ -35,12 +35,6 @@ namespace BizHawk.Client.Common
|
|||
private readonly IGL _gl;
|
||||
private List<ITexture2D> _currentTextures;
|
||||
|
||||
public ITexture2D Get(IDisplaySurface ds)
|
||||
{
|
||||
using var bb = new BitmapBuffer(ds.PeekBitmap(), new());
|
||||
return Get(bb);
|
||||
}
|
||||
|
||||
public ITexture2D Get(BitmapBuffer bb)
|
||||
{
|
||||
//get the current entry
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace BizHawk.Client.Common
|
|||
=> DisplaySurfaceIDParser.Parse(surfaceName) ?? _rememberedSurfaceID;
|
||||
|
||||
#pragma warning disable CS0612
|
||||
#pragma warning disable CS0618
|
||||
[LuaDeprecatedMethod]
|
||||
[LuaMethod("DrawNew", "Changes drawing target to the specified lua surface name. This may clobber any previous drawing to this surface (pass false if you don't want it to)")]
|
||||
public void DrawNew(string name, bool? clear = true)
|
||||
|
@ -31,6 +32,7 @@ namespace BizHawk.Client.Common
|
|||
public void DrawFinish()
|
||||
=> APIs.Gui.DrawFinish();
|
||||
#pragma warning restore CS0612
|
||||
#pragma warning restore CS0618
|
||||
|
||||
[LuaMethodExample("gui.addmessage( \"Some message\" );")]
|
||||
[LuaMethod("addmessage", "Adds a message to the OSD's message area")]
|
||||
|
@ -92,7 +94,7 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return;
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +234,7 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return;
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,7 +315,7 @@ namespace BizHawk.Client.Common
|
|||
[LuaColorParam] object backcolor = null,
|
||||
string fontfamily = null,
|
||||
string surfaceName = null)
|
||||
=> APIs.Gui.PixelText(x, y, message, _th.SafeParseColor(forecolor), _th.SafeParseColor(backcolor) ?? APIs.Gui.GetDefaultTextBackground().Value, fontfamily, surfaceID: UseOrFallback(surfaceName));
|
||||
=> APIs.Gui.PixelText(x, y, message, _th.SafeParseColor(forecolor), _th.SafeParseColor(backcolor) ?? APIs.Gui.GetDefaultTextBackground(), fontfamily, surfaceID: UseOrFallback(surfaceName));
|
||||
|
||||
[LuaMethodExample("gui.text( 16, 32, \"Some message\", 0x7F0000FF, \"bottomleft\" );")]
|
||||
[LuaMethod("text", "Displays the given text on the screen at the given coordinates. Optional Foreground color. The optional anchor flag anchors the text to one of the four corners. Anchor flag parameters: topleft, topright, bottomleft, bottomright")]
|
||||
|
|
|
@ -97,7 +97,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
_luaContainer?.Dispose();
|
||||
_luaContainer = Register(serviceProvider, logCallback, mainForm, displayManager, inputManager, movieSession, toolManager, config, emulator, game);
|
||||
((GuiApi) _luaContainer.Gui).EnableLuaAutolockHack = true;
|
||||
return _luaContainer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,7 +252,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return TryInitIGL(initialConfig.DispMethod = method);
|
||||
}
|
||||
// need to have a context active for checking renderer, will be disposed afterwards
|
||||
using (new SDL2OpenGLContext(3, 0, true, false))
|
||||
using (new SDL2OpenGLContext(3, 2, true, false))
|
||||
{
|
||||
using var testIgl = new IGL_OpenGL();
|
||||
_ = CheckRenderer(testIgl);
|
||||
|
|
|
@ -205,8 +205,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void CallSaveStateEvent(string name)
|
||||
{
|
||||
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList())
|
||||
|
@ -222,8 +220,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void CallLoadStateEvent(string name)
|
||||
{
|
||||
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_LOADSTATE).ToList())
|
||||
|
@ -241,8 +237,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
if (IsUpdateSupressed) return;
|
||||
|
||||
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_PREFRAME).ToList())
|
||||
|
@ -260,8 +254,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
if (IsUpdateSupressed) return;
|
||||
|
||||
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList())
|
||||
|
@ -277,8 +269,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void CallExitEvent(LuaFile lf)
|
||||
{
|
||||
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
|
||||
|
||||
foreach (var exitCallback in RegisteredFunctions
|
||||
.Where(l => l.Event == NamedLuaFunction.EVENT_TYPE_ENGINESTOP
|
||||
&& (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread)))
|
||||
|
@ -340,7 +330,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf)
|
||||
{
|
||||
_currThread = lf.Thread;
|
||||
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue