Beginnings of IGL split-up, make Texture2d into an interface and make classes for each IGL implementation for this interface, do cleanups from this

This commit is contained in:
CasualPokePlayer 2024-05-16 22:25:39 -07:00
parent ea068cf70d
commit 25f62c6620
32 changed files with 644 additions and 655 deletions

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Encapsules all D3D11 resources
/// This is mainly needed as we need to be able to recreate these resources
/// Due to possibly getting a device reset/lost (e.g. from hibernation)
/// </summary>
internal sealed class D3D11Resources : IDisposable
{
public IDXGIFactory1 Factory1;
public IDXGIFactory2 Factory2;
public ID3D11Device Device;
public ID3D11DeviceContext Context;
public ID3D11BlendState BlendEnableState;
public ID3D11BlendState BlendDisableState;
public ID3D11SamplerState PointSamplerState;
public ID3D11SamplerState LinearSamplerState;
public ID3D11RasterizerState RasterizerState;
public FeatureLevel DeviceFeatureLevel;
public readonly HashSet<RenderTarget> RenderTargets = new();
public readonly HashSet<D3D11Texture2D> ShaderTextures = new();
public readonly HashSet<Shader> VertexShaders = new();
public readonly HashSet<Shader> PixelShaders = new();
public readonly HashSet<Pipeline> Pipelines = new();
public void CreateResources()
{
try
{
Factory1 = DXGI.CreateDXGIFactory1<IDXGIFactory1>();
// we want IDXGIFactory2 for CreateSwapChainForHwnd
// however, it's not guaranteed to be available (only available in Win8+ or Win7 with the Platform Update)
Factory2 = Factory1.QueryInterfaceOrNull<IDXGIFactory2>();
#if false
// use this to debug D3D11 calls
// note debug layer requires extra steps to use: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-layers#debug-layer
// also debug output will only be present with a "native debugger" attached (pure managed debugger can't see this output)
const DeviceCreationFlags creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport | DeviceCreationFlags.Debug;
#else
// IGL is not thread safe, so let's not bother making this implementation thread safe
const DeviceCreationFlags creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport;
#endif
D3D11.D3D11CreateDevice(
adapter: null,
DriverType.Hardware,
creationFlags,
null!, // this is safe to be null
out Device,
out Context).CheckError();
using var dxgiDevice = Device.QueryInterface<IDXGIDevice1>();
dxgiDevice.MaximumFrameLatency = 1;
var bd = default(BlendDescription);
bd.AlphaToCoverageEnable = false;
bd.IndependentBlendEnable = false;
bd.RenderTarget[0].BlendEnable = true;
bd.RenderTarget[0].SourceBlend = Blend.SourceAlpha;
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].BlendOperationAlpha = BlendOperation.Add;
bd.RenderTarget[0].RenderTargetWriteMask = ColorWriteEnable.All;
BlendEnableState = Device.CreateBlendState(bd);
bd.RenderTarget[0].BlendEnable = false;
bd.RenderTarget[0].SourceBlend = Blend.One;
bd.RenderTarget[0].DestinationBlend = Blend.Zero;
BlendDisableState = Device.CreateBlendState(bd);
PointSamplerState = Device.CreateSamplerState(SamplerDescription.PointClamp);
LinearSamplerState = Device.CreateSamplerState(SamplerDescription.LinearClamp);
DeviceFeatureLevel = Device.FeatureLevel;
var rd = new RasterizerDescription
{
CullMode = CullMode.None,
FillMode = FillMode.Solid,
ScissorEnable = true,
DepthClipEnable = DeviceFeatureLevel is FeatureLevel.Level_9_1 or FeatureLevel.Level_9_2 or FeatureLevel.Level_9_3,
};
RasterizerState = Device.CreateRasterizerState(rd);
Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
}
catch
{
Dispose();
throw;
}
}
public void Dispose()
{
LinearSamplerState?.Dispose();
LinearSamplerState = null;
PointSamplerState?.Dispose();
PointSamplerState = null;
RasterizerState?.Dispose();
RasterizerState = null;
BlendEnableState?.Dispose();
BlendEnableState = null;
BlendDisableState?.Dispose();
BlendDisableState = null;
Context?.Dispose();
Context = null;
Device?.Dispose();
Device = null;
Factory2?.Dispose();
Factory2 = null;
Factory1?.Dispose();
Factory1 = null;
}
}
}

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
namespace BizHawk.Bizware.Graphics
{
internal class D3D11Texture2D : ITexture2D
{
private readonly D3D11Resources _resources;
private readonly BindFlags _bindFlags;
private readonly ResourceUsage _usage;
private readonly CpuAccessFlags _cpuAccessFlags;
private ID3D11Device Device => _resources.Device;
private ID3D11DeviceContext Context => _resources.Context;
private HashSet<D3D11Texture2D> ShaderTextures => _resources.ShaderTextures;
private ID3D11Texture2D StagingTexture;
public ID3D11Texture2D Texture;
public ID3D11ShaderResourceView SRV;
public bool LinearFiltering;
public int Width { get; }
public int Height { get; }
public bool IsUpsideDown => false;
public D3D11Texture2D(D3D11Resources resources, BindFlags bindFlags, ResourceUsage usage, CpuAccessFlags cpuAccessFlags, int width, int height)
{
_resources = resources;
_bindFlags = bindFlags;
_usage = usage;
_cpuAccessFlags = cpuAccessFlags;
Width = width;
Height = height;
CreateTexture();
ShaderTextures.Add(this);
}
public void Dispose()
{
DestroyTexture();
ShaderTextures.Remove(this);
}
public void CreateTexture()
{
Texture = Device.CreateTexture2D(
Format.B8G8R8A8_UNorm,
Width,
Height,
mipLevels: 1,
bindFlags: _bindFlags,
usage: _usage,
cpuAccessFlags: _cpuAccessFlags);
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, Format.B8G8R8A8_UNorm, mostDetailedMip: 0, mipLevels: 1);
SRV = Device.CreateShaderResourceView(Texture, srvd);
}
public void DestroyTexture()
{
SRV?.Dispose();
SRV = null;
Texture?.Dispose();
Texture = null;
StagingTexture?.Dispose();
StagingTexture = null;
}
public BitmapBuffer Resolve()
{
StagingTexture ??= Device.CreateTexture2D(
Format.B8G8R8A8_UNorm,
Width,
Height,
mipLevels: 1,
bindFlags: BindFlags.None,
usage: ResourceUsage.Staging,
cpuAccessFlags: CpuAccessFlags.Read);
Context.CopyResource(StagingTexture, Texture);
try
{
var srcSpan = Context.MapReadOnly<byte>(StagingTexture);
var pixels = new int[Width * Height];
var dstSpan = MemoryMarshal.AsBytes(pixels.AsSpan());
if (srcSpan.Length == dstSpan.Length)
{
srcSpan.CopyTo(dstSpan);
}
else
{
int srcStart = 0, dstStart = 0;
int srcStride = srcSpan.Length / Height, dstStride = Width * sizeof(int);
for (var i = 0; i < Height; i++)
{
srcSpan.Slice(srcStart, dstStride)
.CopyTo(dstSpan.Slice(dstStart, dstStride));
srcStart += srcStride;
dstStart += dstStride;
}
}
return new(Width, Height, pixels);
}
finally
{
Context.Unmap(StagingTexture, 0);
}
}
public unsafe void LoadFrom(BitmapBuffer buffer)
{
if (buffer.Width != Width || buffer.Height != Height)
{
throw new InvalidOperationException("BitmapBuffer dimensions do not match texture dimensions");
}
if ((_cpuAccessFlags & CpuAccessFlags.Write) == 0)
{
throw new InvalidOperationException("This texture cannot be written by the CPU");
}
var bmpData = buffer.LockBits();
try
{
var srcSpan = new ReadOnlySpan<byte>(bmpData.Scan0.ToPointer(), bmpData.Stride * buffer.Height);
var mappedTex = Context.Map<byte>(Texture, 0, 0, MapMode.WriteDiscard);
if (srcSpan.Length == mappedTex.Length)
{
srcSpan.CopyTo(mappedTex);
}
else
{
// D3D11 sometimes has weird pitches (seen with 3DS)
int srcStart = 0, dstStart = 0;
int srcStride = bmpData.Stride, dstStride = mappedTex.Length / buffer.Height;
var height = buffer.Height;
for (var i = 0; i < height; i++)
{
srcSpan.Slice(srcStart, srcStride)
.CopyTo(mappedTex.Slice(dstStart, dstStride));
srcStart += srcStride;
dstStart += dstStride;
}
}
}
finally
{
Context.Unmap(Texture, 0);
buffer.UnlockBits(bmpData);
}
}
public void SetFilterLinear()
=> LinearFiltering = true;
public void SetFilterNearest()
=> LinearFiltering = false;
public override string ToString()
=> $"D3D11 Texture2D: {Width}x{Height}";
}
}

View File

@ -23,121 +23,9 @@ namespace BizHawk.Bizware.Graphics
{
public EDispMethod DispMethodEnum => EDispMethod.D3D11;
private struct D3D11Resources : IDisposable
{
public IDXGIFactory1 Factory1;
public IDXGIFactory2 Factory2;
public ID3D11Device Device;
public ID3D11DeviceContext Context;
public ID3D11BlendState BlendEnableState;
public ID3D11BlendState BlendDisableState;
public ID3D11SamplerState PointSamplerState;
public ID3D11SamplerState LinearSamplerState;
public ID3D11RasterizerState RasterizerState;
public FeatureLevel DeviceFeatureLevel;
public void CreateResources()
{
try
{
Factory1 = DXGI.CreateDXGIFactory1<IDXGIFactory1>();
// we want IDXGIFactory2 for CreateSwapChainForHwnd
// however, it's not guaranteed to be available (only available in Win8+ or Win7 with the Platform Update)
Factory2 = Factory1.QueryInterfaceOrNull<IDXGIFactory2>();
#if false
// use this to debug D3D11 calls
// note debug layer requires extra steps to use: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-layers#debug-layer
// also debug output will only be present with a "native debugger" attached (pure managed debugger can't see this output)
const DeviceCreationFlags creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport | DeviceCreationFlags.Debug;
#else
// IGL is not thread safe, so let's not bother making this implementation thread safe
const DeviceCreationFlags creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport;
#endif
D3D11.D3D11CreateDevice(
adapter: null,
DriverType.Hardware,
creationFlags,
null!, // this is safe to be null
out Device,
out Context).CheckError();
using var dxgiDevice = Device.QueryInterface<IDXGIDevice1>();
dxgiDevice.MaximumFrameLatency = 1;
var bd = default(BlendDescription);
bd.AlphaToCoverageEnable = false;
bd.IndependentBlendEnable = false;
bd.RenderTarget[0].BlendEnable = true;
bd.RenderTarget[0].SourceBlend = Blend.SourceAlpha;
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].BlendOperationAlpha = BlendOperation.Add;
bd.RenderTarget[0].RenderTargetWriteMask = ColorWriteEnable.All;
BlendEnableState = Device.CreateBlendState(bd);
bd.RenderTarget[0].BlendEnable = false;
bd.RenderTarget[0].SourceBlend = Blend.One;
bd.RenderTarget[0].DestinationBlend = Blend.Zero;
BlendDisableState = Device.CreateBlendState(bd);
PointSamplerState = Device.CreateSamplerState(SamplerDescription.PointClamp);
LinearSamplerState = Device.CreateSamplerState(SamplerDescription.LinearClamp);
DeviceFeatureLevel = Device.FeatureLevel;
var rd = new RasterizerDescription
{
CullMode = CullMode.None,
FillMode = FillMode.Solid,
ScissorEnable = true,
DepthClipEnable = DeviceFeatureLevel is FeatureLevel.Level_9_1 or FeatureLevel.Level_9_2 or FeatureLevel.Level_9_3,
};
RasterizerState = Device.CreateRasterizerState(rd);
Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
}
catch
{
Dispose();
throw;
}
}
public void Dispose()
{
LinearSamplerState?.Dispose();
LinearSamplerState = null;
PointSamplerState?.Dispose();
PointSamplerState = null;
RasterizerState?.Dispose();
RasterizerState = null;
BlendEnableState?.Dispose();
BlendEnableState = null;
BlendDisableState?.Dispose();
BlendDisableState = null;
Context?.Dispose();
Context = null;
Device?.Dispose();
Device = null;
Factory2?.Dispose();
Factory2 = null;
Factory1?.Dispose();
Factory1 = null;
}
}
// D3D11 resources
// these might need to be thrown out and recreated if the device is lost
private D3D11Resources _resources;
private readonly D3D11Resources _resources;
private IDXGIFactory1 Factory1 => _resources.Factory1;
private IDXGIFactory2 Factory2 => _resources.Factory2;
@ -156,13 +44,6 @@ namespace BizHawk.Bizware.Graphics
private RenderTarget _curRenderTarget;
private D3D11SwapChain.SwapChainResources _controlSwapChain;
// misc state
private readonly HashSet<RenderTarget> _renderTargets = new();
private readonly HashSet<Texture2d> _shaderTextures = new();
private readonly HashSet<Shader> _vertexShaders = new();
private readonly HashSet<Shader> _pixelShaders = new();
private readonly HashSet<Pipeline> _pipelines = new();
public IGL_D3D11()
{
if (OSTailoredCode.IsUnixHost)
@ -170,6 +51,7 @@ namespace BizHawk.Bizware.Graphics
throw new NotSupportedException("D3D11 is Windows only");
}
_resources = new();
_resources.CreateResources();
}
@ -237,7 +119,7 @@ namespace BizHawk.Bizware.Graphics
_controlSwapChain.Dispose();
Context.Flush(); // important to properly dispose of the swapchain
foreach (var pipeline in _pipelines)
foreach (var pipeline in _resources.Pipelines)
{
var pw = (PipelineWrapper)pipeline.Opaque;
@ -257,75 +139,55 @@ namespace BizHawk.Bizware.Graphics
vlw.VertexBufferCount = 0;
}
foreach (var sw in _vertexShaders.Select(vertexShader => (ShaderWrapper)vertexShader.Opaque))
foreach (var sw in _resources.VertexShaders.Select(vertexShader => (ShaderWrapper)vertexShader.Opaque))
{
sw.VS.Dispose();
sw.VS = null;
}
foreach (var sw in _pixelShaders.Select(pixelShader => (ShaderWrapper)pixelShader.Opaque))
foreach (var sw in _resources.PixelShaders.Select(pixelShader => (ShaderWrapper)pixelShader.Opaque))
{
sw.PS.Dispose();
sw.PS = null;
}
foreach (var rt in _renderTargets)
foreach (var rw in _resources.RenderTargets.Select(rt => (RenderTargetWrapper)rt.Opaque))
{
var rw = (RenderTargetWrapper)rt.Opaque;
rw.RTV.Dispose();
rw.RTV = null;
var tw = (TextureWrapper)rt.Texture2d.Opaque;
tw.SRV.Dispose();
tw.SRV = null;
tw.Texture.Dispose();
tw.Texture = null;
}
foreach (var tw in _shaderTextures.Select(tex2d => (TextureWrapper)tex2d.Opaque))
foreach (var tex2d in _resources.ShaderTextures)
{
tw.SRV.Dispose();
tw.SRV = null;
tw.Texture.Dispose();
tw.Texture = null;
tex2d.DestroyTexture();
}
_resources.Dispose();
_resources.CreateResources();
foreach (var tex2d in _shaderTextures)
foreach (var tex2d in _resources.ShaderTextures)
{
var tw = (TextureWrapper)tex2d.Opaque;
tw.Texture = CreateTextureForShader(tex2d.IntWidth, tex2d.IntHeight);
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, Format.B8G8R8A8_UNorm, mostDetailedMip: 0, mipLevels: 1);
tw.SRV = Device.CreateShaderResourceView(tw.Texture, srvd);
tex2d.CreateTexture();
}
foreach (var rt in _renderTargets)
foreach (var rt in _resources.RenderTargets)
{
var tw = (TextureWrapper)rt.Texture2d.Opaque;
tw.Texture = CreateTextureForRenderTarget(rt.Texture2d.IntWidth, rt.Texture2d.IntHeight);
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, Format.B8G8R8A8_UNorm, mostDetailedMip: 0, mipLevels: 1);
tw.SRV = Device.CreateShaderResourceView(tw.Texture, srvd);
var rw = (RenderTargetWrapper)rt.Opaque;
var rtvd = new RenderTargetViewDescription(RenderTargetViewDimension.Texture2D, Format.B8G8R8A8_UNorm);
rw.RTV = Device.CreateRenderTargetView(tw.Texture, rtvd);
rw.RTV = Device.CreateRenderTargetView(((D3D11Texture2D)rt.Texture2D).Texture, rtvd);
}
foreach (var sw in _vertexShaders.Select(vertexShader => (ShaderWrapper)vertexShader.Opaque))
foreach (var sw in _resources.VertexShaders.Select(vertexShader => (ShaderWrapper)vertexShader.Opaque))
{
sw.VS = Device.CreateVertexShader(sw.Bytecode.Span);
}
foreach (var sw in _pixelShaders.Select(pixelShader => (ShaderWrapper)pixelShader.Opaque))
foreach (var sw in _resources.PixelShaders.Select(pixelShader => (ShaderWrapper)pixelShader.Opaque))
{
sw.PS = Device.CreatePixelShader(sw.Bytecode.Span);
}
foreach (var pipeline in _pipelines)
foreach (var pipeline in _resources.Pipelines)
{
var pw = (PipelineWrapper)pipeline.Opaque;
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
@ -414,14 +276,6 @@ namespace BizHawk.Bizware.Graphics
}
}
public void FreeTexture(Texture2d tex)
{
var tw = (TextureWrapper)tex.Opaque;
tw.SRV.Dispose();
tw.Texture.Dispose();
_shaderTextures.Remove(tex);
}
private class ShaderWrapper // Disposable fields cleaned up by Internal_FreeShader
{
public ReadOnlyMemory<byte> Bytecode;
@ -460,7 +314,7 @@ namespace BizHawk.Bizware.Graphics
var s = new Shader(this, sw, true);
sw.IGLShader = s;
_vertexShaders.Add(s);
_resources.VertexShaders.Add(s);
return s;
}
@ -504,7 +358,7 @@ namespace BizHawk.Bizware.Graphics
var s = new Shader(this, sw, true);
sw.IGLShader = s;
_pixelShaders.Add(s);
_resources.PixelShaders.Add(s);
return s;
}
@ -676,7 +530,7 @@ namespace BizHawk.Bizware.Graphics
}
var ret = new Pipeline(this, pw, true, vertexLayout, uniforms, memo);
_pipelines.Add(ret);
_resources.Pipelines.Add(ret);
return ret;
}
@ -706,7 +560,7 @@ namespace BizHawk.Bizware.Graphics
pw.VertexShader.IGLShader.Release();
pw.FragmentShader.IGLShader.Release();
_pipelines.Remove(pipeline);
_resources.Pipelines.Remove(pipeline);
}
public void Internal_FreeShader(Shader shader)
@ -719,14 +573,14 @@ namespace BizHawk.Bizware.Graphics
{
sw.VS.Dispose();
sw.VS = null;
_vertexShaders.Remove(shader);
_resources.VertexShaders.Remove(shader);
}
if (sw.PS != null)
{
sw.PS.Dispose();
sw.PS = null;
_pixelShaders.Remove(shader);
_resources.PixelShaders.Remove(shader);
}
}
@ -753,13 +607,6 @@ namespace BizHawk.Bizware.Graphics
public ShaderWrapper VertexShader, FragmentShader;
}
private class TextureWrapper
{
public ID3D11Texture2D Texture;
public ID3D11ShaderResourceView SRV;
public bool LinearFiltering;
}
private class RenderTargetWrapper
{
public ID3D11RenderTargetView RTV;
@ -960,15 +807,15 @@ namespace BizHawk.Bizware.Graphics
}
}
public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
public void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex)
{
if (uniform.Owner == null)
{
return; // uniform was optimized out
}
var tw = (TextureWrapper)tex.Opaque;
var sampler = tw.LinearFiltering ? LinearSamplerState : PointSamplerState;
var d3d11Tex = (D3D11Texture2D)tex;
var sampler = d3d11Tex.LinearFiltering ? LinearSamplerState : PointSamplerState;
foreach (var ui in uniform.UniformInfos)
{
@ -985,139 +832,23 @@ namespace BizHawk.Bizware.Graphics
throw new InvalidOperationException("Feature level 9.1 does not support setting a shader resource in a vertex shader");
}
Context.VSSetShaderResource(ui.SamplerIndex, tw.SRV);
Context.VSSetShaderResource(ui.SamplerIndex, d3d11Tex.SRV);
Context.VSSetSampler(ui.SamplerIndex, sampler);
}
else
{
Context.PSSetShaderResource(ui.SamplerIndex, tw.SRV);
Context.PSSetShaderResource(ui.SamplerIndex, d3d11Tex.SRV);
Context.PSSetSampler(ui.SamplerIndex, sampler);
}
}
}
public void SetTextureFilter(Texture2d texture, bool linear)
{
var tw = (TextureWrapper)texture.Opaque;
tw.LinearFiltering = linear;
}
public ITexture2D CreateTexture(int width, int height)
=> new D3D11Texture2D(_resources, BindFlags.ShaderResource, ResourceUsage.Dynamic, CpuAccessFlags.Write, width, height);
private ID3D11Texture2D CreateTextureForShader(int width, int height)
{
return Device.CreateTexture2D(
Format.B8G8R8A8_UNorm,
width,
height,
mipLevels: 1,
bindFlags: BindFlags.ShaderResource,
usage: ResourceUsage.Dynamic,
cpuAccessFlags: CpuAccessFlags.Write);
}
public Texture2d CreateTexture(int width, int height)
{
var tex = CreateTextureForShader(width, height);
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, Format.B8G8R8A8_UNorm, mostDetailedMip: 0, mipLevels: 1);
var srv = Device.CreateShaderResourceView(tex, srvd);
var tw = new TextureWrapper { Texture = tex, SRV = srv };
var ret = new Texture2d(this, tw, width, height);
_shaderTextures.Add(ret);
return ret;
}
public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
{
// not used for non-GL backends
return null;
}
public unsafe void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
{
if (bmp.Width != tex.IntWidth || bmp.Height != tex.IntHeight)
{
throw new InvalidOperationException();
}
var tw = (TextureWrapper)tex.Opaque;
var bmpData = bmp.LockBits();
try
{
var srcSpan = new ReadOnlySpan<byte>(bmpData.Scan0.ToPointer(), bmpData.Stride * bmp.Height);
var mappedTex = Context.Map<byte>(tw.Texture, 0, 0, MapMode.WriteDiscard);
if (srcSpan.Length == mappedTex.Length)
{
srcSpan.CopyTo(mappedTex);
}
else
{
// D3D11 sometimes has weird pitches (seen with 3DS)
int srcStart = 0, dstStart = 0;
int srcStride = bmpData.Stride, dstStride = mappedTex.Length / bmp.Height;
var height = bmp.Height;
for (var i = 0; i < height; i++)
{
srcSpan.Slice(srcStart, srcStride)
.CopyTo(mappedTex.Slice(dstStart, dstStride));
srcStart += srcStride;
dstStart += dstStride;
}
}
}
finally
{
Context.Unmap(tw.Texture, 0);
bmp.UnlockBits(bmpData);
}
}
/// <exception cref="InvalidOperationException">Vortice call returned unexpected data</exception>
public BitmapBuffer ResolveTexture2d(Texture2d tex)
{
using var target = Device.CreateTexture2D(
Format.B8G8R8A8_UNorm,
tex.IntWidth,
tex.IntHeight,
mipLevels: 1,
bindFlags: BindFlags.None,
usage: ResourceUsage.Staging,
cpuAccessFlags: CpuAccessFlags.Read);
var tw = (TextureWrapper)tex.Opaque;
Context.CopyResource(target, tw.Texture);
try
{
var srcSpan = Context.MapReadOnly<byte>(target);
var pixels = new int[tex.IntWidth * tex.IntHeight];
var dstSpan = MemoryMarshal.AsBytes(pixels.AsSpan());
if (srcSpan.Length == dstSpan.Length)
{
srcSpan.CopyTo(dstSpan);
}
else
{
int srcStart = 0, dstStart = 0;
int srcStride = srcSpan.Length / tex.IntHeight, dstStride = tex.IntWidth * sizeof(int);
var height = tex.IntHeight;
for (var i = 0; i < height; i++)
{
srcSpan.Slice(srcStart, dstStride)
.CopyTo(dstSpan.Slice(dstStart, dstStride));
srcStart += srcStride;
dstStart += dstStride;
}
}
return new(tex.IntWidth, tex.IntHeight, pixels);
}
finally
{
Context.Unmap(target, 0);
}
}
// not used for non-GL backends
public ITexture2D WrapGLTexture2D(int glTexId, int width, int height)
=> null;
public Matrix4x4 CreateGuiProjectionMatrix(int width, int height)
{
@ -1148,36 +879,17 @@ namespace BizHawk.Bizware.Graphics
{
var rw = (RenderTargetWrapper)rt.Opaque;
rw.RTV.Dispose();
_renderTargets.Remove(rt);
var tw = (TextureWrapper)rt.Texture2d.Opaque;
tw.SRV.Dispose();
tw.Texture.Dispose();
}
private ID3D11Texture2D CreateTextureForRenderTarget(int width, int height)
{
return Device.CreateTexture2D(
Format.B8G8R8A8_UNorm,
width,
height,
mipLevels: 1,
bindFlags: BindFlags.RenderTarget | BindFlags.ShaderResource, // ack! need to also let this be bound to shaders
usage: ResourceUsage.Default,
cpuAccessFlags: CpuAccessFlags.None);
_resources.RenderTargets.Remove(rt);
rt.Texture2D.Dispose();
}
public RenderTarget CreateRenderTarget(int width, int height)
{
var tex = CreateTextureForRenderTarget(width, height);
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, Format.B8G8R8A8_UNorm, mostDetailedMip: 0, mipLevels: 1);
var srv = Device.CreateShaderResourceView(tex, srvd);
var tw = new TextureWrapper { Texture = tex, SRV = srv };
var tex2d = new Texture2d(this, tw, width, height);
var tex = new D3D11Texture2D(_resources, BindFlags.ShaderResource | BindFlags.RenderTarget, ResourceUsage.Default, CpuAccessFlags.None, width, height);
var rtvd = new RenderTargetViewDescription(RenderTargetViewDimension.Texture2D, Format.B8G8R8A8_UNorm);
var rw = new RenderTargetWrapper { RTV = Device.CreateRenderTargetView(tw.Texture, rtvd) };
var rt = new RenderTarget(this, rw, tex2d);
_renderTargets.Add(rt);
var rw = new RenderTargetWrapper { RTV = Device.CreateRenderTargetView(tex.Texture, rtvd) };
var rt = new RenderTarget(this, rw, tex);
_resources.RenderTargets.Add(rt);
return rt;
}

View File

@ -6,7 +6,7 @@ namespace BizHawk.Bizware.Graphics
{
public static class IGLExtensions
{
public static IGuiRenderer CreateRenderer(this IGL gl)
public static IGuiRenderer CreateGuiRenderer(this IGL gl)
=> gl is IGL_GDIPlus gdipImpl
? new GDIPlusGuiRenderer(gdipImpl)
: new GuiRenderer(gl);
@ -14,7 +14,7 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Loads a texture from disk
/// </summary>
public static Texture2d LoadTexture(this IGL igl, string path)
public static ITexture2D LoadTexture(this IGL igl, string path)
{
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return igl.LoadTexture(fs);
@ -23,7 +23,7 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Loads a texture from the stream
/// </summary>
public static Texture2d LoadTexture(this IGL igl, Stream stream)
public static ITexture2D LoadTexture(this IGL igl, Stream stream)
{
using var bmp = new BitmapBuffer(stream, new());
return igl.LoadTexture(bmp);
@ -32,7 +32,7 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Loads a texture from the System.Drawing.Bitmap
/// </summary>
public static Texture2d LoadTexture(this IGL igl, Bitmap bitmap)
public static ITexture2D LoadTexture(this IGL igl, Bitmap bitmap)
{
using var bmp = new BitmapBuffer(bitmap, new());
return igl.LoadTexture(bmp);
@ -41,10 +41,10 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Loads a texture from the BitmapBuffer
/// </summary>
public static Texture2d LoadTexture(this IGL igl, BitmapBuffer buffer)
public static ITexture2D LoadTexture(this IGL igl, BitmapBuffer buffer)
{
var ret = igl.CreateTexture(buffer.Width, buffer.Height);
igl.LoadTextureData(ret, buffer);
ret.LoadFrom(buffer);
return ret;
}

View File

@ -13,19 +13,19 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Draws the specified texture2d resource.
/// </summary>
public static void Draw(this IGuiRenderer guiRenderer, Texture2d tex)
public static void Draw(this IGuiRenderer guiRenderer, ITexture2D tex)
=> guiRenderer.Draw(tex, 0, 0, tex.Width, tex.Height);
/// <summary>
/// Draws the specified texture2d resource with the specified offset.
/// </summary>
public static void Draw(this IGuiRenderer guiRenderer, Texture2d tex, float x, float y)
public static void Draw(this IGuiRenderer guiRenderer, ITexture2D tex, float x, float y)
=> guiRenderer.Draw(tex, x, y, tex.Width, tex.Height);
/// <summary>
/// Draws the specified Texture with the specified offset and the specified size. This could be tricky if youve applied other rotate or scale transforms first.
/// </summary>
public static void Draw(this IGuiRenderer guiRenderer, Texture2d tex, float x, float y, float width, float height)
public static void Draw(this IGuiRenderer guiRenderer, ITexture2D tex, float x, float y, float width, float height)
{
const float u0 = 0, u1 = 1;
float v0, v1;

View File

@ -0,0 +1,13 @@
using System.Drawing;
namespace BizHawk.Bizware.Graphics
{
public static class ITexture2DExtensions
{
public static Rectangle GetRectangle(this ITexture2D texture2D)
=> new(0, 0, texture2D.Width, texture2D.Height);
public static Size GetSize(this ITexture2D texture2D)
=> new(texture2D.Width, texture2D.Height);
}
}

View File

@ -50,10 +50,10 @@ namespace BizHawk.Bizware.Graphics
}
else
{
var gtex = (GDIPlusTexture)Target.Texture2d.Opaque;
var gtex = (GDIPlusTexture2D)Target.Texture2D;
CurGraphics?.Dispose();
CurGraphics = SDGraphics.FromImage(gtex.SDBitmap);
r = Target.Texture2d.Rectangle;
r = gtex.GetRectangle();
}
BufferedGraphics?.Dispose();

View File

@ -1,17 +0,0 @@
using System;
using System.Drawing;
namespace BizHawk.Bizware.Graphics
{
public class GDIPlusTexture : IDisposable
{
public Bitmap SDBitmap;
public bool LinearFiltering;
public void Dispose()
{
SDBitmap?.Dispose();
SDBitmap = null;
}
}
}

View File

@ -0,0 +1,50 @@
using System.Drawing;
using System.Drawing.Imaging;
namespace BizHawk.Bizware.Graphics
{
public class GDIPlusTexture2D : ITexture2D
{
public Bitmap SDBitmap;
public bool LinearFiltering;
public int Width { get; }
public int Height { get; }
public bool IsUpsideDown => false;
public GDIPlusTexture2D(int width, int height)
{
Width = width;
Height = height;
SDBitmap = new(width, height, PixelFormat.Format32bppArgb);
}
public void Dispose()
{
SDBitmap?.Dispose();
SDBitmap = null;
}
public BitmapBuffer Resolve()
{
var blo = new BitmapLoadOptions
{
AllowWrap = false // must be an independent resource
};
return new(SDBitmap, blo);
}
public void LoadFrom(BitmapBuffer buffer)
=> buffer.ToSysdrawingBitmap(SDBitmap);
public void SetFilterLinear()
=> LinearFiltering = true;
public void SetFilterNearest()
=> LinearFiltering = false;
public override string ToString()
=> $"GDI+ Texture2D: {Width}x{Height}";
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Numerics;
using SDGraphics = System.Drawing.Graphics;
@ -14,19 +13,11 @@ namespace BizHawk.Bizware.Graphics
public EDispMethod DispMethodEnum => EDispMethod.GdiPlus;
public void Dispose()
{
BufferedGraphicsContext.Dispose();
}
=> BufferedGraphicsContext.Dispose();
public void ClearColor(Color color)
=> GetCurrentGraphics().Clear(color);
public void FreeTexture(Texture2d tex)
{
var gtex = (GDIPlusTexture)tex.Opaque;
gtex.Dispose();
}
public Shader CreateFragmentShader(string source, string entry, bool required)
=> null;
@ -48,9 +39,7 @@ namespace BizHawk.Bizware.Graphics
}
public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
{
return null;
}
=> null;
public void FreePipeline(Pipeline pipeline)
{
@ -104,57 +93,26 @@ namespace BizHawk.Bizware.Graphics
{
}
public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
public void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex)
{
}
public void SetTextureFilter(Texture2d texture, bool linear)
=> ((GDIPlusTexture) texture.Opaque).LinearFiltering = linear;
public ITexture2D CreateTexture(int width, int height)
=> new GDIPlusTexture2D(width, height);
public Texture2d CreateTexture(int width, int height)
{
var sdBitmap = new Bitmap(width, height);
var gtex = new GDIPlusTexture { SDBitmap = sdBitmap };
return new(this, gtex, width, height);
}
public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
{
// only used for OpenGL
return null;
}
public void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
{
var gtex = (GDIPlusTexture)tex.Opaque;
bmp.ToSysdrawingBitmap(gtex.SDBitmap);
}
public BitmapBuffer ResolveTexture2d(Texture2d tex)
{
var gtex = (GDIPlusTexture)tex.Opaque;
var blow = new BitmapLoadOptions
{
AllowWrap = false // must be an independent resource
};
var bb = new BitmapBuffer(gtex.SDBitmap, blow);
return bb;
}
// only used for OpenGL
public ITexture2D WrapGLTexture2D(int glTexId, int width, int height)
=> null;
// see CreateGuiViewMatrix for more
public Matrix4x4 CreateGuiProjectionMatrix(int width, int height)
{
// see CreateGuiViewMatrix for more
return Matrix4x4.Identity;
}
=> Matrix4x4.Identity;
// on account of gdi+ working internally with a default view exactly like we want, we don't need to setup a new one here
// furthermore, we _cant_, without inverting the GuiView and GuiProjection before drawing, to completely undo it
// this might be feasible, but its kind of slow and annoying and worse, seemingly numerically unstable
public Matrix4x4 CreateGuiViewMatrix(int width, int height, bool autoFlip)
{
// on account of gdi+ working internally with a default view exactly like we want, we don't need to setup a new one here
// furthermore, we _cant_, without inverting the GuiView and GuiProjection before drawing, to completely undo it
// this might be feasible, but its kind of slow and annoying and worse, seemingly numerically unstable
return Matrix4x4.Identity;
}
=> Matrix4x4.Identity;
public void SetViewport(int x, int y, int width, int height)
{
@ -168,14 +126,9 @@ namespace BizHawk.Bizware.Graphics
public RenderTarget CreateRenderTarget(int width, int height)
{
var gtex = new GDIPlusTexture
{
SDBitmap = new(width, height, PixelFormat.Format32bppArgb)
};
var tex = new Texture2d(this, gtex, width, height);
var tex2d = new GDIPlusTexture2D(width, height);
var grt = new GDIPlusRenderTarget(() => BufferedGraphicsContext);
var rt = new RenderTarget(this, grt, tex);
var rt = new RenderTarget(this, grt, tex2d);
grt.Target = rt;
return rt;
}
@ -195,7 +148,7 @@ namespace BizHawk.Bizware.Graphics
}
else
{
var gtex = (GDIPlusTexture)rt.Texture2d.Opaque;
var gtex = (GDIPlusTexture2D)rt.Texture2D;
CurrentRenderTarget = (GDIPlusRenderTarget)rt.Opaque;
_currOffscreenGraphics = SDGraphics.FromImage(gtex.SDBitmap);
}
@ -217,9 +170,7 @@ namespace BizHawk.Bizware.Graphics
private SDGraphics _currOffscreenGraphics;
public SDGraphics GetCurrentGraphics()
{
return _currOffscreenGraphics ?? CurrentRenderTarget.BufferedGraphics.Graphics;
}
=> _currOffscreenGraphics ?? CurrentRenderTarget.BufferedGraphics.Graphics;
public GDIPlusRenderTarget CurrentRenderTarget;

View File

@ -46,7 +46,7 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Sets a uniform sampler to use use the provided texture
/// </summary>
void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex);
void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex);
/// <summary>
/// Sets a uniform value
@ -91,11 +91,6 @@ namespace BizHawk.Bizware.Graphics
/// </summary>
void Draw(IntPtr data, int count);
/// <summary>
/// resolves the texture into a new BitmapBuffer
/// </summary>
BitmapBuffer ResolveTexture2d(Texture2d texture);
/// <summary>
/// creates a vertex layout resource
/// </summary>
@ -113,26 +108,15 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Creates a texture with the specified dimensions
/// The texture will use a clamping address mode
/// The texture will use a clamping address mode and nearest neighbor filtering by default
/// </summary>
Texture2d CreateTexture(int width, int height);
ITexture2D CreateTexture(int width, int height);
/// <summary>
/// In case you already have the texture ID (from an opengl emulator gpu core) you can get a Texture2d with it this way.
/// In case you already have the texture ID (from an OpenGL emulator gpu core) you can get an ITexture2D with it this way.
/// Otherwise, if this isn't an OpenGL frontend implementation, the core is expected to readback the texture for GetVideoBuffer()
/// </summary>
Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height);
/// <summary>
/// Sets the texture's filtering mode
/// The default is linear = false (i.e. nearest neighbor)
/// </summary>
public void SetTextureFilter(Texture2d texture, bool linear);
/// <summary>
/// Loads the texture with new data. This isn't supposed to be especially versatile, it just blasts a bitmap buffer into the texture
/// </summary>
void LoadTextureData(Texture2d tex, BitmapBuffer bmp);
ITexture2D WrapGLTexture2D(int glTexId, int width, int height);
/// <summary>
/// sets the viewport (and scissor) according to the provided specifications
@ -165,11 +149,6 @@ namespace BizHawk.Bizware.Graphics
/// </summary>
void FreeRenderTarget(RenderTarget rt);
/// <summary>
/// Frees the provided texture. Same as disposing the resource.
/// </summary>
void FreeTexture(Texture2d tex);
/// <summary>
/// Frees the provided pipeline. Same as disposing the resource.
/// </summary>

View File

@ -0,0 +1,39 @@
using System;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// A full-scale 2D texture, with mip levels and everything.
/// In OpenGL tradition, this encapsulates the sampler state, as well, which is equal parts annoying and convenient
/// </summary>
public interface ITexture2D : IDisposable
{
int Width { get; }
int Height { get; }
/// <summary>
/// OpenGL wrapped textures from IGLTextureProvider will be upside down
/// </summary>
bool IsUpsideDown { get; }
/// <summary>
/// Resolves the texture into a new BitmapBuffer
/// </summary>
BitmapBuffer Resolve();
/// <summary>
/// Loads the texture with new data. This isn't supposed to be especially versatile, it just blasts a bitmap buffer into the texture
/// </summary>
void LoadFrom(BitmapBuffer buffer);
/// <summary>
/// Sets the texture's filtering mode to linear
/// </summary>
void SetFilterLinear();
/// <summary>
/// Sets the texture's filtering mode to nearest neighbor
/// </summary>
void SetFilterNearest();
}
}

View File

@ -50,9 +50,7 @@ namespace BizHawk.Bizware.Graphics
}
public void Dispose()
{
GL.Dispose();
}
=> GL.Dispose();
public void ClearColor(Color color)
{
@ -60,20 +58,11 @@ namespace BizHawk.Bizware.Graphics
GL.Clear(ClearBufferMask.ColorBufferBit);
}
public void FreeTexture(Texture2d tex)
{
GL.DeleteTexture((uint)tex.Opaque);
}
public BizShader CreateVertexShader(string source, string entry, bool required)
=> CreateShader(ShaderType.VertexShader, source, required);
public BizShader CreateFragmentShader(string source, string entry, bool required)
{
return CreateShader(ShaderType.FragmentShader, source, required);
}
public BizShader CreateVertexShader(string source, string entry, bool required)
{
return CreateShader(ShaderType.VertexShader, source, required);
}
=> CreateShader(ShaderType.FragmentShader, source, required);
public void EnableBlending()
{
@ -285,11 +274,6 @@ namespace BizHawk.Bizware.Graphics
GL.DeleteBuffer(vlw.VBO);
}
private void BindTexture2d(Texture2d tex)
{
GL.BindTexture(TextureTarget.Texture2D, (uint)tex.Opaque);
}
private void LegacyBindArrayData(VertexLayout vertexLayout, IntPtr pData)
{
// DEPRECATED CRAP USED, NEEDED FOR ANCIENT SHADERS
@ -450,7 +434,7 @@ namespace BizHawk.Bizware.Graphics
}
}
public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
public void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex)
{
var n = (int)uniform.Sole.Opaque >> 24;
@ -458,54 +442,18 @@ namespace BizHawk.Bizware.Graphics
GL.ActiveTexture(TextureUnit.Texture0 + n);
// now bind the texture
GL.BindTexture(TextureTarget.Texture2D, (uint)tex.Opaque);
GL.BindTexture(TextureTarget.Texture2D, ((OpenGLTexture2D)tex).TexID);
}
public void SetTextureFilter(Texture2d texture, bool linear)
{
BindTexture2d(texture);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)(linear ? TextureMinFilter.Linear : TextureMinFilter.Nearest));
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)(linear ? TextureMagFilter.Linear : TextureMagFilter.Nearest));
}
public ITexture2D CreateTexture(int width, int height)
=> new OpenGLTexture2D(GL, width, height);
public Texture2d CreateTexture(int width, int height)
{
var texId = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, texId);
unsafe
{
GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)width, (uint)height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, null);
}
// sensible defaults
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
return new(this, texId, width, height);
}
public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
=> new(this, (uint)glTexId.ToInt32(), width, height) { IsUpsideDown = true };
public unsafe void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
{
var bmpData = bmp.LockBits();
try
{
GL.BindTexture(TextureTarget.Texture2D, (uint)tex.Opaque);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, (uint)bmp.Width, (uint)bmp.Height, PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0.ToPointer());
}
finally
{
bmp.UnlockBits(bmpData);
}
}
public ITexture2D WrapGLTexture2D(int glTexId, int width, int height)
=> new OpenGLTexture2D(GL, (uint)glTexId, width, height);
public void FreeRenderTarget(RenderTarget rt)
{
rt.Texture2d.Dispose();
rt.Texture2D.Dispose();
GL.DeleteFramebuffer((uint)rt.Opaque);
}
@ -513,15 +461,14 @@ namespace BizHawk.Bizware.Graphics
public RenderTarget CreateRenderTarget(int width, int height)
{
// create a texture for it
var tex = CreateTexture(width, height);
var texId = (uint)tex.Opaque;
var tex2d = new OpenGLTexture2D(GL, width, height);
// create the FBO
var fbId = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, fbId);
// bind the tex to the FBO
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texId, 0);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, tex2d.TexID, 0);
// do something, I guess say which color buffers are used by the framebuffer
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
@ -534,7 +481,7 @@ namespace BizHawk.Bizware.Graphics
// since we're done configuring unbind this framebuffer, to return to the default
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
return new(this, fbId, tex);
return new(this, fbId, tex2d);
}
public void BindRenderTarget(RenderTarget rt)
@ -550,17 +497,6 @@ namespace BizHawk.Bizware.Graphics
}
}
public unsafe BitmapBuffer ResolveTexture2d(Texture2d tex)
{
// note - this is dangerous since it changes the bound texture. could we save it?
BindTexture2d(tex);
var bb = new BitmapBuffer(tex.IntWidth, tex.IntHeight);
var bmpdata = bb.LockBits();
GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgra, PixelType.UnsignedByte, bmpdata.Scan0.ToPointer());
bb.UnlockBits(bmpdata);
return bb;
}
public Matrix4x4 CreateGuiProjectionMatrix(int width, int height)
{
var ret = Matrix4x4.Identity;

View File

@ -0,0 +1,97 @@
using System;
using Silk.NET.OpenGL.Legacy;
namespace BizHawk.Bizware.Graphics
{
internal class OpenGLTexture2D : ITexture2D
{
private readonly GL GL;
public readonly uint TexID;
public int Width { get; }
public int Height { get; }
public bool IsUpsideDown { get; }
public OpenGLTexture2D(GL gl, int width, int height)
{
GL = gl;
TexID = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, TexID);
unsafe
{
GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)width, (uint)height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, null);
}
// sensible defaults
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
Width = width;
Height = height;
IsUpsideDown = false;
}
public OpenGLTexture2D(GL gl, uint texID, int width, int height)
{
GL = gl;
TexID = texID;
Width = width;
Height = height;
IsUpsideDown = true;
}
public void Dispose()
=> GL.DeleteTexture(TexID);
public unsafe BitmapBuffer Resolve()
{
GL.BindTexture(TextureTarget.Texture2D, TexID);
var pixels = new int[Width * Height];
fixed (int* p = pixels)
{
GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgra, PixelType.UnsignedByte, p);
}
return new(Width, Height, pixels);
}
public unsafe void LoadFrom(BitmapBuffer buffer)
{
if (buffer.Width != Width || buffer.Height != Height)
{
throw new InvalidOperationException("BitmapBuffer dimensions do not match texture dimensions");
}
var bmpData = buffer.LockBits();
try
{
GL.BindTexture(TextureTarget.Texture2D, TexID);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, (uint)buffer.Width, (uint)buffer.Height, PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0.ToPointer());
}
finally
{
buffer.UnlockBits(bmpData);
}
}
public void SetFilterLinear()
{
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
}
public void SetFilterNearest()
{
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
}
public override string ToString()
=> $"OpenGL Texture2D: {Width}x{Height}";
}
}

View File

@ -76,7 +76,7 @@ namespace BizHawk.Bizware.Graphics
Owner?.Owner.SetPipelineUniform(this, value);
}
public void Set(Texture2d tex)
public void Set(ITexture2D tex)
{
Owner?.Owner.SetPipelineUniformSampler(this, tex);
}

View File

@ -4,21 +4,21 @@ namespace BizHawk.Bizware.Graphics
{
public class RenderTarget : IDisposable
{
public RenderTarget(IGL owner, object opaque, Texture2d tex)
public RenderTarget(IGL owner, object opaque, ITexture2D tex)
{
Owner = owner;
Opaque = opaque;
Texture2d = tex;
Texture2D = tex;
}
public override string ToString()
{
return $"GL RT: {Texture2d.Width}x{Texture2d.Height}";
return $"GL RT: {Texture2D.Width}x{Texture2D.Height}";
}
public object Opaque { get; }
public IGL Owner { get; }
public Texture2d Texture2d { get; }
public ITexture2D Texture2D { get; }
public void Unbind()
{

View File

@ -26,9 +26,7 @@ namespace BizHawk.Bizware.Graphics
};
public void SetCornerColor(int which, Vector4 color)
{
CornerColors[which] = color;
}
=> CornerColors[which] = color;
/// <exception cref="ArgumentException"><paramref name="colors"/> does not have exactly <c>4</c> elements</exception>
public void SetCornerColors(Vector4[] colors)
@ -47,9 +45,7 @@ namespace BizHawk.Bizware.Graphics
}
public void Dispose()
{
CurrentImageAttributes?.Dispose();
}
=> CurrentImageAttributes?.Dispose();
public void SetPipeline(Pipeline pipeline)
{
@ -60,9 +56,7 @@ namespace BizHawk.Bizware.Graphics
}
public void SetModulateColorWhite()
{
SetModulateColor(Color.White);
}
=> SetModulateColor(Color.White);
private ImageAttributes CurrentImageAttributes;
@ -94,14 +88,10 @@ namespace BizHawk.Bizware.Graphics
}
public void EnableBlending()
{
Owner.EnableBlending();
}
=> Owner.EnableBlending();
public void DisableBlending()
{
Owner.DisableBlending();
}
=> Owner.DisableBlending();
private MatrixStack _projection, _modelView;
@ -158,13 +148,13 @@ namespace BizHawk.Bizware.Graphics
CurrentImageAttributes = null;
}
public void DrawSubrect(Texture2d tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1)
public void DrawSubrect(ITexture2D tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1)
{
var g = _gdi.GetCurrentGraphics();
var tw = (GDIPlusTexture)tex.Opaque;
var tex2d = (GDIPlusTexture2D)tex;
// TODO - we can support bicubic for the final presentation...
g.InterpolationMode = tw.LinearFiltering ? InterpolationMode.Bilinear : InterpolationMode.NearestNeighbor;
g.InterpolationMode = tex2d.LinearFiltering ? InterpolationMode.Bilinear : InterpolationMode.NearestNeighbor;
SetupMatrix(g);
@ -180,9 +170,8 @@ namespace BizHawk.Bizware.Graphics
var x1 = u1 * tex.Width;
var y1 = v1 * tex.Height;
var gtex = (GDIPlusTexture)tex.Opaque;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(gtex.SDBitmap, destPoints, new(x0, y0, x1 - x0, y1 - y0), GraphicsUnit.Pixel, CurrentImageAttributes);
g.DrawImage(tex2d.SDBitmap, destPoints, new(x0, y0, x1 - x0, y1 - y0), GraphicsUnit.Pixel, CurrentImageAttributes);
g.Transform = new(); // .Reset() doesnt work?
}

View File

@ -195,13 +195,13 @@ namespace BizHawk.Bizware.Graphics
IsActive = false;
}
public void DrawSubrect(Texture2d tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1)
public void DrawSubrect(ITexture2D tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1)
{
PrepDrawSubrectInternal(tex);
EmitRectangleInternal(x, y, w, h, u0, v0, u1, v1);
}
private void PrepDrawSubrectInternal(Texture2d tex)
private void PrepDrawSubrectInternal(ITexture2D tex)
{
if (sTexture != tex)
{
@ -271,7 +271,7 @@ namespace BizHawk.Bizware.Graphics
private readonly Pipeline DefaultPipeline;
// state cache
private Texture2d sTexture;
private ITexture2D sTexture;
// shaders are hand-coded for each platform to make sure they stay as fast as possible

View File

@ -14,7 +14,7 @@ namespace BizHawk.Bizware.Graphics
/// <summary>
/// Draws a subrectangle from the provided texture. For advanced users only
/// </summary>
void DrawSubrect(Texture2d tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1);
void DrawSubrect(ITexture2D tex, float x, float y, float w, float h, float u0, float v0, float u1, float v1);
/// <summary>
/// Ends rendering

View File

@ -116,8 +116,8 @@ namespace BizHawk.Bizware.Graphics
// calculate texcoords (we shouldve already had this cached, but im speedcoding now)
var tex = TexturePages[bfc.TexturePage];
var w = tex.Width;
var h = tex.Height;
var w = (float)tex.Width;
var h = (float)tex.Height;
var bounds = new Rectangle(bfc.X, bfc.Y, bfc.Width, bfc.Height);
var u0 = bounds.Left / w;
var v0 = bounds.Top / h;
@ -135,6 +135,6 @@ namespace BizHawk.Bizware.Graphics
public IGL Owner { get; }
private readonly BitmapFont FontInfo;
private List<Texture2d> TexturePages = new();
private List<ITexture2D> TexturePages = new();
}
}

View File

@ -16,9 +16,9 @@ namespace BizHawk.Bizware.Graphics
Owner = owner;
VertexLayout = owner.CreateVertexLayout();
VertexLayout.DefineVertexAttribute("position", 0, 4, VertexAttribPointerType.Float, AttribUsage.Position, false, 40, 0);
VertexLayout.DefineVertexAttribute("color", 1, 4, VertexAttribPointerType.Float, AttribUsage.Color0, false, 40, 16); //just dead weight, i have no idea why this is here. but some old HLSL compilers (used in bizhawk for various reasons) will want it to exist here since it exists in the vertex shader
VertexLayout.DefineVertexAttribute("tex", 2, 2, VertexAttribPointerType.Float, AttribUsage.Texcoord0, false, 40, 32);
VertexLayout.DefineVertexAttribute("position", 0, 4, VertexAttribPointerType.Float, AttribUsage.Position, normalized: false, stride: 40, offset: 0);
VertexLayout.DefineVertexAttribute("color", 1, 4, VertexAttribPointerType.Float, AttribUsage.Color0, normalized: false, stride: 40, offset: 16); // just dead weight, i have no idea why this is here. but some old HLSL compilers (used in bizhawk for various reasons) will want it to exist here since it exists in the vertex shader
VertexLayout.DefineVertexAttribute("tex", 2, 2, VertexAttribPointerType.Float, AttribUsage.Texcoord0, normalized: false, stride: 40, offset: 32);
VertexLayout.Close();
var vsSource = $"#define VERTEX\r\n{source}";
@ -77,7 +77,7 @@ namespace BizHawk.Bizware.Graphics
Owner.BindPipeline(Pipeline);
}
public unsafe void Run(Texture2d tex, Size InputSize, Size OutputSize, bool flip)
public unsafe void Run(ITexture2D tex, Size InputSize, Size OutputSize, bool flip)
{
// ack! make sure to set the pipeline before setting uniforms
Bind();

View File

@ -1,65 +0,0 @@
using System;
using System.Drawing;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// A full-scale 2D texture, with mip levels and everything.
/// In OpenGL tradition, this encapsulates the sampler state, as well, which is equal parts annoying and convenient
/// </summary>
public class Texture2d : IDisposable
{
/// <summary>
/// resolves the texture into a new BitmapBuffer
/// </summary>
public BitmapBuffer Resolve()
{
return Owner.ResolveTexture2d(this);
}
public void Dispose()
{
Owner.FreeTexture(this);
Opaque = null;
}
public Texture2d(IGL owner, object opaque, int width, int height)
{
Owner = owner;
Opaque = opaque;
Width = width;
Height = height;
}
public override string ToString()
{
return $"GL Tex: {Width}x{Height}";
}
public void LoadFrom(BitmapBuffer buffer)
{
}
public void SetFilterLinear() => Owner.SetTextureFilter(this, true);
public void SetFilterNearest() => Owner.SetTextureFilter(this, false);
public IGL Owner { get; }
public object Opaque { get; private set; }
// note.. this was a lame idea. convenient, but weird. lets just change this back to ints.
public float Width { get; }
public float Height { get; }
public int IntWidth => (int)Width;
public int IntHeight => (int)Height;
public Rectangle Rectangle => new(0, 0, IntWidth, IntHeight);
public Size Size => new(IntWidth, IntHeight);
/// <summary>
/// opengl sucks, man. seriously, screw this (textures from render targets are upside down)
/// (couldn't we fix it up in the matrices somewhere?)
/// </summary>
public bool IsUpsideDown;
}
}

View File

@ -795,13 +795,13 @@ namespace BizHawk.Client.Common
if (vh < 1) vh = 1;
BitmapBuffer bb = null;
Texture2d videoTexture = null;
ITexture2D videoTexture = null;
if (!simulate)
{
if (videoProvider is IGLTextureProvider glTextureProvider && _gl.DispMethodEnum == EDispMethod.OpenGL)
{
// FYI: this is a million years from happening on n64, since it's all geriatric non-FBO code
videoTexture = _gl.WrapGLTexture2d(new(glTextureProvider.GetGLTexture()), bufferWidth, bufferHeight);
videoTexture = _gl.WrapGLTexture2D(glTextureProvider.GetGLTexture(), bufferWidth, bufferHeight);
}
else
{
@ -880,13 +880,13 @@ namespace BizHawk.Client.Common
RunFilterChainSteps(ref rtCounter, out var rtCurr, out _);
job.OffscreenBb = rtCurr.Texture2d.Resolve();
job.OffscreenBb = rtCurr.Texture2D.Resolve();
job.OffscreenBb.DiscardAlpha();
}
protected void RunFilterChainSteps(ref int rtCounter, out RenderTarget rtCurr, out bool inFinalTarget)
{
Texture2d texCurr = null;
ITexture2D texCurr = null;
rtCurr = null;
inFinalTarget = false;
foreach (var step in _currentFilterProgram.Program) switch (step.Type)
@ -921,7 +921,7 @@ namespace BizHawk.Client.Common
try
{
var fontData = new byte[fontStream.Length];
fontStream.Read(fontData, 0, (int)fontStream.Length);
_ = fontStream.Read(fontData, 0, (int)fontStream.Length);
Marshal.Copy(fontData, 0, data, (int)fontStream.Length);
CustomFonts.AddMemoryFont(data, fontData.Length);
}

View File

@ -74,7 +74,7 @@ namespace BizHawk.Client.Common.Filters
return point;
}
public void SetInput(Texture2d tex)
public void SetInput(ITexture2D tex)
{
InputTexture = tex;
}
@ -83,18 +83,18 @@ namespace BizHawk.Client.Common.Filters
{
}
public Texture2d GetOutput()
public ITexture2D GetOutput()
=> _outputTexture;
// filter actions
protected void YieldOutput(Texture2d tex)
protected void YieldOutput(ITexture2D tex)
{
_outputTexture = tex;
}
protected FilterProgram FilterProgram;
protected Texture2d InputTexture;
private Texture2d _outputTexture;
protected ITexture2D InputTexture;
private ITexture2D _outputTexture;
/// <summary>
/// Indicate a 'RenderTarget' disposition if you want to draw directly to the input

View File

@ -538,9 +538,9 @@ namespace BizHawk.Client.Common.Filters
DeclareOutput(state);
}
private Texture2d _texture;
private ITexture2D _texture;
public void SetTexture(Texture2d tex)
public void SetTexture(ITexture2D tex)
{
_texture = tex;
}

View File

@ -366,7 +366,7 @@ namespace BizHawk.Client.Common.Filters
InputTexture.SetFilterNearest();
}
_rsc.Shaders[_rsi].Run(input, input.Size, _outputSize, InputTexture.IsUpsideDown);
_rsc.Shaders[_rsi].Run(input, input.GetSize(), _outputSize, InputTexture.IsUpsideDown);
// maintain invariant.. i think.
InputTexture.SetFilterNearest();

View File

@ -14,7 +14,7 @@ namespace BizHawk.Client.Common.Filters
private readonly Size _size;
public Texture2d Texture { get; set; }
public ITexture2D Texture { get; set; }
public override void Run()
{
@ -71,7 +71,7 @@ namespace BizHawk.Client.Common.Filters
public override void Run()
{
YieldOutput(FilterProgram.CurrRenderTarget.Texture2d);
YieldOutput(FilterProgram.CurrRenderTarget.Texture2D);
}
}
}

View File

@ -44,12 +44,12 @@ namespace BizHawk.Client.Common
public RenderTarget Get(int width, int height)
{
//get the current entry
RenderTarget currentRenderTarget = _currentRenderTargets[0];
var currentRenderTarget = _currentRenderTargets[0];
//check if its rotten and needs recreating
if (currentRenderTarget == null
|| currentRenderTarget.Texture2d.IntWidth != width
|| currentRenderTarget.Texture2d.IntHeight != height)
|| currentRenderTarget.Texture2D.Width != width
|| currentRenderTarget.Texture2D.Height != height)
{
// needs recreating. be sure to kill the old one...
currentRenderTarget?.Dispose();

View File

@ -29,27 +29,28 @@ namespace BizHawk.Client.Common
private void ResetList()
{
_currentTextures = new List<Texture2d> { null, null };
_currentTextures = new List<ITexture2D> { null, null };
}
private readonly IGL _gl;
private List<Texture2d> _currentTextures;
private List<ITexture2D> _currentTextures;
public Texture2d Get(IDisplaySurface ds)
public ITexture2D Get(IDisplaySurface ds)
{
using var bb = new BitmapBuffer(ds.PeekBitmap(), new BitmapLoadOptions());
using var bb = new BitmapBuffer(ds.PeekBitmap(), new());
return Get(bb);
}
public Texture2d Get(BitmapBuffer bb)
public ITexture2D Get(BitmapBuffer bb)
{
//get the current entry
Texture2d currentTexture = _currentTextures[0];
var currentTexture = _currentTextures[0];
// TODO - its a bit cruddy here that we don't respect the current texture HasAlpha condition (in fact, there's no such concept)
// we might need to deal with that in the future to fix some bugs.
//check if its rotten and needs recreating
if (currentTexture == null || currentTexture.IntWidth != bb.Width || currentTexture.IntHeight != bb.Height)
if (currentTexture == null || currentTexture.Width != bb.Width || currentTexture.Height != bb.Height)
{
//needs recreating. be sure to kill the old one...
currentTexture?.Dispose();
@ -59,7 +60,7 @@ namespace BizHawk.Client.Common
else
{
//its good! just load in the data
_gl.LoadTextureData(currentTexture, bb);
currentTexture.LoadFrom(bb);
}
//now shuffle the buffers

View File

@ -31,7 +31,7 @@ namespace BizHawk.Client.EmuHawk
IGL gl,
PresentationPanel presentationPanel,
Func<bool> getIsSecondaryThrottlingDisabled)
: base(config, emulator, inputManager, movieSession, gl.DispMethodEnum, gl, gl.CreateRenderer())
: base(config, emulator, inputManager, movieSession, gl.DispMethodEnum, gl, gl.CreateGuiRenderer())
{
_presentationPanel = presentationPanel;
_getIsSecondaryThrottlingDisabled = getIsSecondaryThrottlingDisabled;
@ -99,7 +99,7 @@ namespace BizHawk.Client.EmuHawk
if (job.Offscreen)
{
job.OffscreenBb = rtCurr.Texture2d.Resolve();
job.OffscreenBb = rtCurr.Texture2D.Resolve();
job.OffscreenBb.DiscardAlpha();
return;
}

View File

@ -104,7 +104,7 @@ namespace BizHawk.Client.EmuHawk
_guiRenderer.Begin(Width, Height);
_guiRenderer.DisableBlending();
_guiRenderer.Draw(_rt.Texture2d);
_guiRenderer.Draw(_rt.Texture2D);
_guiRenderer.End();
_graphicsControl.SwapBuffers();
}

View File

@ -215,7 +215,7 @@ namespace BizHawk.Client.EmuHawk
{
try
{
using (gl.CreateRenderer()) return gl;
using (gl.CreateGuiRenderer()) return gl;
}
catch (Exception ex)
{