Create IPipeline and classes for it, consolidate pipeline resources/creation of those resoures into pipeline construction, remove old OpenGL legacy binding

This commit is contained in:
CasualPokePlayer 2024-05-19 20:00:31 -07:00
parent f35f5f7aad
commit f04c85f249
33 changed files with 1026 additions and 1712 deletions

View File

@ -1,13 +1,15 @@
//Yeah, I'm sorry this uses really old non-generic attributes
//that's just how old this code is; support on ancient graphics cards is helpful
#ifdef VERTEX
uniform mat4 modelViewProj;
in vec4 position;
in vec2 tex;
out vec2 vTex;
void main()
{
gl_Position = modelViewProj * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = modelViewProj * position;
vTex = tex;
}
#endif
@ -17,15 +19,16 @@ void main()
uniform float uIntensity;
uniform sampler2D s_p;
uniform vec2 output_size;
in vec2 vTex;
out vec4 oColor;
void main()
{
vec2 vTex = gl_TexCoord[0].xy;
vec4 temp = texture2D(s_p,vTex);
vec2 wpos = gl_FragCoord.xy;
if(floor(wpos.y/2.0) != floor(wpos.y)/2.0) temp.rgb *= uIntensity;
gl_FragColor = temp;
oColor = temp;
}
#endif

View File

@ -10,10 +10,15 @@
#ifdef VERTEX
uniform mat4 modelViewProj;
in vec4 position;
in vec2 tex;
out vec2 vTex;
void main()
{
gl_Position = modelViewProj * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = modelViewProj * position;
vTex = tex;
}
#endif
@ -22,6 +27,10 @@ void main()
uniform sampler2D s_p;
in vec2 vTex;
out vec4 oColor;
// Tweakables.
#define saturation 1.0
#define gamma 1.5
@ -38,10 +47,10 @@ vec3 grayscale(vec3 col)
void main()
{
vec3 res = texture2D(s_p,gl_TexCoord[0].xy).xyz;
vec3 res = texture2D(s_p,vTex).xyz;
res = mix(grayscale(res), res, saturation); // Apply saturation
res = pow(res, vec3(gamma,gamma,gamma)); // Apply gamma
gl_FragColor = vec4(clamp(res * luminance,0.0,1.0), 1.0);
oColor = vec4(clamp(res * luminance,0.0,1.0), 1.0);
}
#endif

View File

@ -1,6 +1,3 @@
//Yeah, I'm sorry this uses really old non-generic attributes
//that's just how old this code is; support on ancient graphics cards is helpful
uniform struct
{
vec2 video_size;
@ -11,24 +8,29 @@ uniform struct
#ifdef VERTEX
uniform mat4 modelViewProj;
in vec4 position;
in vec2 tex;
out vec2 coords[9];
void main()
{
gl_Position = modelViewProj * gl_Vertex;
gl_Position = modelViewProj * position;
vec2 texsize = IN.texture_size;
vec2 delta = 0.5 / texsize;
float dx = delta.x;
float dy = delta.y;
gl_TexCoord[0].xy = gl_MultiTexCoord0.xy + vec2(-dx, -dy);
gl_TexCoord[1].xy = gl_MultiTexCoord0.xy + vec2(-dx, 0.0);
gl_TexCoord[2].xy = gl_MultiTexCoord0.xy + vec2(-dx, dy);
gl_TexCoord[3].xy = gl_MultiTexCoord0.xy + vec2(0.0, -dy);
gl_TexCoord[4].xy = gl_MultiTexCoord0.xy + vec2(0.0, 0.0);
gl_TexCoord[5].xy = gl_MultiTexCoord0.xy + vec2(0.0, dy);
gl_TexCoord[6].xy = gl_MultiTexCoord0.xy + vec2(dx, -dy);
gl_TexCoord[7].xy = gl_MultiTexCoord0.xy + vec2(dx, 0);
gl_TexCoord[7].zw = gl_MultiTexCoord0.xy + vec2(dx, dy);
coords[0] = tex + vec2(-dx, -dy);
coords[1] = tex + vec2(-dx, 0.0);
coords[2] = tex + vec2(-dx, dy);
coords[3] = tex + vec2(0.0, -dy);
coords[4] = tex + vec2(0.0, 0.0);
coords[5] = tex + vec2(0.0, dy);
coords[6] = tex + vec2(dx, -dy);
coords[7] = tex + vec2(dx, 0);
coords[8] = tex + vec2(dx, dy);
}
#endif
@ -37,6 +39,10 @@ void main()
uniform sampler2D s_p;
in vec2 coords[9];
out vec4 oColor;
const float mx = 0.325; // start smoothing wt.
const float k = -0.250; // wt. decrease factor
const float max_w = 0.25; // max filter weigth
@ -45,15 +51,15 @@ const float lum_add = 0.25; // effects smoothing
void main()
{
vec3 c00 = texture2D(s_p, gl_TexCoord[0].xy).xyz;
vec3 c01 = texture2D(s_p, gl_TexCoord[1].xy).xyz;
vec3 c02 = texture2D(s_p, gl_TexCoord[2].xy).xyz;
vec3 c10 = texture2D(s_p, gl_TexCoord[3].xy).xyz;
vec3 c11 = texture2D(s_p, gl_TexCoord[4].xy).xyz;
vec3 c12 = texture2D(s_p, gl_TexCoord[5].xy).xyz;
vec3 c20 = texture2D(s_p, gl_TexCoord[6].xy).xyz;
vec3 c21 = texture2D(s_p, gl_TexCoord[7].xy).xyz;
vec3 c22 = texture2D(s_p, gl_TexCoord[7].zw).xyz;
vec3 c00 = texture2D(s_p, coords[0]).xyz;
vec3 c01 = texture2D(s_p, coords[1]).xyz;
vec3 c02 = texture2D(s_p, coords[2]).xyz;
vec3 c10 = texture2D(s_p, coords[3]).xyz;
vec3 c11 = texture2D(s_p, coords[4]).xyz;
vec3 c12 = texture2D(s_p, coords[5]).xyz;
vec3 c20 = texture2D(s_p, coords[6]).xyz;
vec3 c21 = texture2D(s_p, coords[7]).xyz;
vec3 c22 = texture2D(s_p, coords[8]).xyz;
vec3 dt = vec3(1.0,1.0,1.0);
float md1 = dot(abs(c00 - c22), dt);
@ -78,7 +84,7 @@ void main()
w3 = clamp(lc1 * dot(abs(c11 - c12), dt) + mx, min_w, max_w);
w4 = clamp(lc2 * dot(abs(c11 - c01), dt) + mx, min_w, max_w);
gl_FragColor = vec4(w1 * c10 + w2 * c21 + w3 * c12 + w4 * c01 + (1.0 - w1 - w2 - w3 - w4) * c11, 1.0);
oColor = vec4(w1 * c10 + w2 * c21 + w3 * c12 + w4 * c01 + (1.0 - w1 - w2 - w3 - w4) * c11, 1.0);
}
#endif

View File

@ -6,17 +6,19 @@
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
//Yeah, I'm sorry this uses really old non-generic attributes
//that's just how old this code is; support on ancient graphics cards is helpful
#ifdef VERTEX
uniform mat4 modelViewProj;
in vec4 position;
in vec2 tex;
out vec2 vTexcoord;
void main()
{
gl_Position = modelViewProj * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = modelViewProj * position;
vTexcoord = tex;
}
#endif //VERTEX
@ -34,6 +36,10 @@ uniform float Time;
uniform sampler2D s_p;
in vec2 vTexcoord;
out vec4 oColor;
float saturate(float x)
{
return max(0, min(1, x));
@ -211,8 +217,8 @@ vec3 TVEffect(vec2 in_Position, vec2 FakeResolution, float Time) {
void main()
{
vec4 color = vec4(1.0f,1.0f,1.0f,1.0f);
color.xyz = TVEffect(gl_TexCoord[0].xy, IN.texture_size, Time);
gl_FragColor = color;
color.xyz = TVEffect(vTexcoord, IN.texture_size, Time);
oColor = color;
}
#endif

View File

@ -22,7 +22,7 @@
<PackageVersion Include="Silk.NET.OpenAL.Extensions.Creative" Version="2.21.0" />
<PackageVersion Include="Silk.NET.OpenAL.Extensions.Enumeration" Version="2.21.0" />
<PackageVersion Include="Silk.NET.OpenAL.Extensions.EXT" Version="2.21.0" />
<PackageVersion Include="Silk.NET.OpenGL.Legacy" Version="2.21.0" />
<PackageVersion Include="Silk.NET.OpenGL" Version="2.21.0" />
<PackageVersion Include="SQLitePCLRaw.provider.e_sqlite3" Version="2.1.8" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" /><!-- don't forget to update .stylecop.json at the same time -->
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />

View File

@ -41,7 +41,7 @@ namespace BizHawk.Bizware.Graphics.Controls
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
Context = new(Handle, 3, 0, false, false);
Context = new(Handle, 3, 0, true, false);
}
protected override void OnHandleDestroyed(EventArgs e)

View File

@ -2,9 +2,8 @@
{
public enum AttribUsage
{
Unspecified,
Position,
Color0,
Texcoord0, Texcoord1,
}
}
}

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Cyotek.Drawing.BitmapFont" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="Silk.NET.OpenGL.Legacy" />
<PackageReference Include="Silk.NET.OpenGL" />
<PackageReference Include="ppy.SDL2-CS" ExcludeAssets="native;contentFiles" />
<PackageReference Include="Vortice.Direct3D11" />
<PackageReference Include="Vortice.D3DCompiler" />

View File

@ -0,0 +1,358 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using BizHawk.Common.CollectionExtensions;
using BizHawk.Common.StringExtensions;
using Vortice.D3DCompiler;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.Direct3D11.Shader;
using Vortice.DXGI;
namespace BizHawk.Bizware.Graphics
{
internal class D3D11Pipeline : IPipeline
{
private readonly D3D11Resources _resources;
private ID3D11Device Device => _resources.Device;
private ID3D11DeviceContext Context => _resources.Context;
private ID3D11SamplerState PointSamplerState => _resources.PointSamplerState;
private ID3D11SamplerState LinearSamplerState => _resources.LinearSamplerState;
private readonly ReadOnlyMemory<byte> _vsBytecode, _psBytecode;
private readonly InputElementDescription[] _inputElements;
public ID3D11InputLayout VertexInputLayout;
public readonly int VertexStride;
public ID3D11Buffer VertexBuffer;
public int VertexBufferCount;
private readonly record struct D3D11Uniform(IntPtr VariablePointer, int VariableSize, D3D11PendingBuffer PB);
private readonly Dictionary<string, D3D11Uniform> _vsUniforms = new();
private readonly Dictionary<string, D3D11Uniform> _psUniforms = new();
private readonly Dictionary<string, int> _vsSamplers = new();
private readonly Dictionary<string, int> _psSamplers = new();
public class D3D11PendingBuffer
{
public IntPtr VSPendingBuffer, PSPendingBuffer;
public int VSBufferSize, PSBufferSize;
public bool VSBufferDirty, PSBufferDirty;
}
public readonly D3D11PendingBuffer[] PendingBuffers = new D3D11PendingBuffer[ID3D11DeviceContext.CommonShaderConstantBufferSlotCount];
public readonly ID3D11Buffer[] VSConstantBuffers = new ID3D11Buffer[ID3D11DeviceContext.CommonShaderConstantBufferSlotCount];
public readonly ID3D11Buffer[] PSConstantBuffers = new ID3D11Buffer[ID3D11DeviceContext.CommonShaderConstantBufferSlotCount];
public ID3D11VertexShader VS;
public ID3D11PixelShader PS;
private static ReadOnlyMemory<byte> CompileShader(PipelineCompileArgs.ShaderCompileArgs compileArgs, string profile, FeatureLevel featureLevel)
{
profile += featureLevel switch
{
FeatureLevel.Level_9_1 or FeatureLevel.Level_9_2 => "_4_0_level_9_1",
FeatureLevel.Level_9_3 => "_4_0_level_9_3",
_ => "_4_0",
};
// note: we use D3D9-like shaders for legacy reasons, so we need the backwards compat flag
// TODO: remove D3D9 syntax from shaders
return Compiler.Compile(
shaderSource: compileArgs.Source,
entryPoint: compileArgs.Entry,
sourceName: null!, // this is safe to be null
profile: profile,
shaderFlags: ShaderFlags.OptimizationLevel3 | ShaderFlags.EnableBackwardsCompatibility);
}
public D3D11Pipeline(D3D11Resources resources, PipelineCompileArgs compileArgs)
{
_resources = resources;
try
{
_vsBytecode = CompileShader(compileArgs.VertexShaderArgs, "vs", _resources.DeviceFeatureLevel);
_psBytecode = CompileShader(compileArgs.FragmentShaderArgs, "ps", _resources.DeviceFeatureLevel);
_inputElements = new InputElementDescription[compileArgs.VertexLayout.Count];
for (var i = 0; i < compileArgs.VertexLayout.Count; i++)
{
var item = compileArgs.VertexLayout[i];
var semanticName = item.Usage switch
{
AttribUsage.Position => "POSITION",
AttribUsage.Color0 => "COLOR",
AttribUsage.Texcoord0 or AttribUsage.Texcoord1 => "TEXCOORD",
_ => throw new InvalidOperationException()
};
var format = item.Components switch
{
1 => Format.R32_Float,
2 => Format.R32G32_Float,
3 => Format.R32G32B32_Float,
4 => Format.R32G32B32A32_Float,
_ => throw new InvalidOperationException()
};
_inputElements[i] = new(semanticName, item.Usage == AttribUsage.Texcoord1 ? 1 : 0, format, item.Offset, 0);
}
VertexStride = compileArgs.VertexLayoutStride;
using var vsRefl = Compiler.Reflect<ID3D11ShaderReflection>(_vsBytecode.Span);
using var psRefl = Compiler.Reflect<ID3D11ShaderReflection>(_psBytecode.Span);
foreach (var refl in new[] { vsRefl, psRefl })
{
var isVs = refl == vsRefl;
var reflCbs = refl.ConstantBuffers;
var todo = new Queue<(string, int, string, int, ID3D11ShaderReflectionType)>();
for (var i = 0; i < reflCbs.Length; i++)
{
var cbDesc = reflCbs[i].Description;
var bd = new BufferDescription((cbDesc.Size + 15) & ~15, BindFlags.ConstantBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
PendingBuffers[i] ??= new();
if (isVs)
{
VSConstantBuffers[i] = Device.CreateBuffer(bd);
PendingBuffers[i].VSPendingBuffer = Marshal.AllocCoTaskMem(cbDesc.Size);
PendingBuffers[i].VSBufferSize = cbDesc.Size;
}
else
{
PSConstantBuffers[i] = Device.CreateBuffer(bd);
PendingBuffers[i].PSPendingBuffer = Marshal.AllocCoTaskMem(cbDesc.Size);
PendingBuffers[i].PSBufferSize = cbDesc.Size;
}
var prefix = cbDesc.Name.RemovePrefix('$');
prefix = prefix is "Params" or "Globals" ? "" : $"{prefix}.";
foreach (var reflVari in reflCbs[i].Variables)
{
var reflVariDesc = reflVari.Description;
todo.Enqueue((prefix, i, reflVariDesc.Name, reflVariDesc.StartOffset, reflVari.VariableType));
}
}
while (todo.Count != 0)
{
var (prefix, pbIndex, reflVariName, reflVariOffset, reflVariType) = todo.Dequeue();
var reflVariTypeDesc = reflVariType.Description;
if (reflVariTypeDesc.MemberCount > 0)
{
prefix = $"{prefix}{reflVariName}.";
for (var i = 0; i < reflVariTypeDesc.MemberCount; i++)
{
var memberName = reflVariType.GetMemberTypeName(i);
var memberType = reflVariType.GetMemberTypeByIndex(i);
var memberOffset = memberType.Description.Offset;
todo.Enqueue((prefix, pbIndex, memberName, reflVariOffset + memberOffset, memberType));
}
continue;
}
if (reflVariTypeDesc.Type is not (ShaderVariableType.Bool or ShaderVariableType.Float))
{
// unsupported type
continue;
}
reflVariName = $"{prefix}{reflVariName}";
var reflVariSize = 4 * reflVariTypeDesc.ColumnCount * reflVariTypeDesc.RowCount;
if (isVs)
{
var reflVariPtr = PendingBuffers[pbIndex].VSPendingBuffer + reflVariOffset;
_vsUniforms.Add(reflVariName, new(reflVariPtr, reflVariSize, PendingBuffers[pbIndex]));
}
else
{
var reflVariPtr = PendingBuffers[pbIndex].PSPendingBuffer + reflVariOffset;
_psUniforms.Add(reflVariName, new(reflVariPtr, reflVariSize, PendingBuffers[pbIndex]));
}
}
foreach (var resource in refl.BoundResources)
{
if (resource.Type == ShaderInputType.Sampler)
{
var samplerName = resource.Name.RemovePrefix('$');
var samplerIndex = resource.BindPoint;
if (isVs)
{
_vsSamplers.Add(samplerName, samplerIndex);
}
else
{
_psSamplers.Add(samplerName, samplerIndex);
}
}
}
}
CreatePipeline();
_resources.Pipelines.Add(this);
}
catch
{
DestroyPipeline();
throw;
}
}
public void Dispose()
{
DestroyPipeline();
DestroyPendingBuffers();
_resources.Pipelines.Remove(this);
}
public void CreatePipeline()
{
VertexInputLayout = Device.CreateInputLayout(_inputElements, _vsBytecode.Span);
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
var pb = PendingBuffers[i];
if (pb == null)
{
break;
}
if (pb.VSBufferSize > 0)
{
var bd = new BufferDescription(pb.VSBufferSize, BindFlags.ConstantBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
VSConstantBuffers[i] = Device.CreateBuffer(bd);
pb.VSBufferDirty = true;
}
if (pb.PSBufferSize > 0)
{
var bd = new BufferDescription(pb.PSBufferSize, BindFlags.ConstantBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
PSConstantBuffers[i] = Device.CreateBuffer(bd);
pb.PSBufferDirty = true;
}
}
VS = Device.CreateVertexShader(_vsBytecode.Span);
PS = Device.CreatePixelShader(_psBytecode.Span);
}
public void DestroyPipeline()
{
VertexInputLayout?.Dispose();
VertexInputLayout = null;
VertexBuffer?.Dispose();
VertexBuffer = null;
VertexBufferCount = 0;
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
VSConstantBuffers[i]?.Dispose();
VSConstantBuffers[i] = null;
PSConstantBuffers[i]?.Dispose();
PSConstantBuffers[i] = null;
}
VS?.Dispose();
VS = null;
PS?.Dispose();
PS = null;
}
public void DestroyPendingBuffers()
{
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
if (PendingBuffers[i] == null)
{
break;
}
Marshal.FreeCoTaskMem(PendingBuffers[i].VSPendingBuffer);
Marshal.FreeCoTaskMem(PendingBuffers[i].PSPendingBuffer);
PendingBuffers[i] = null;
}
}
public bool HasUniformSampler(string name)
{
return _vsSamplers.ContainsKey(name)
|| _psSamplers.ContainsKey(name);
}
public string GetUniformSamplerName(int index)
{
var sampler = _psSamplers.AsEnumerable().FirstOrNull(s => s.Value == index)
?? _vsSamplers.AsEnumerable().FirstOrNull(s => s.Value == index);
return sampler?.Key;
}
public void SetUniformSampler(string name, ITexture2D tex)
{
var d3d11Tex = (D3D11Texture2D)tex;
var sampler = d3d11Tex.LinearFiltering ? LinearSamplerState : PointSamplerState;
if (_vsSamplers.TryGetValue(name, out var vsSamplerIndex))
{
Context.VSSetSampler(vsSamplerIndex, sampler);
Context.VSSetShaderResource(vsSamplerIndex, d3d11Tex.SRV);
}
if (_psSamplers.TryGetValue(name, out var psSamplerIndex))
{
Context.PSSetSampler(psSamplerIndex, sampler);
Context.PSSetShaderResource(psSamplerIndex, d3d11Tex.SRV);
}
}
private unsafe void SetUniform(string name, void* valPtr, int valSize)
{
if (_vsUniforms.TryGetValue(name, out var vsUniform))
{
Buffer.MemoryCopy(valPtr, (void*)vsUniform.VariablePointer, vsUniform.VariableSize, valSize);
vsUniform.PB.VSBufferDirty = true;
}
if (_psUniforms.TryGetValue(name, out var psUniform))
{
Buffer.MemoryCopy(valPtr, (void*)psUniform.VariablePointer, psUniform.VariableSize, valSize);
psUniform.PB.PSBufferDirty = true;
}
}
public void SetUniformMatrix(string name, Matrix4x4 mat, bool transpose)
=> SetUniformMatrix(name, ref mat, transpose);
public unsafe void SetUniformMatrix(string name, ref Matrix4x4 mat, bool transpose)
{
var m = transpose ? Matrix4x4.Transpose(mat) : mat;
SetUniform(name, &m, sizeof(Matrix4x4));
}
public unsafe void SetUniform(string name, Vector4 value)
=> SetUniform(name, &value, sizeof(Vector4));
public unsafe void SetUniform(string name, Vector2 value)
=> SetUniform(name, &value, sizeof(Vector2));
public unsafe void SetUniform(string name, float value)
=> SetUniform(name, &value, sizeof(float));
public unsafe void SetUniform(string name, bool value)
{
// note: HLSL bool is 4 bytes large
var b = value ? 1 : 0;
SetUniform(name, &b, sizeof(int));
}
}
}

View File

@ -27,11 +27,10 @@ namespace BizHawk.Bizware.Graphics
public FeatureLevel DeviceFeatureLevel;
public D3D11RenderTarget CurRenderTarget;
public D3D11Pipeline CurPipeline;
public readonly HashSet<D3D11Texture2D> Textures = new();
public readonly HashSet<Shader> VertexShaders = new();
public readonly HashSet<Shader> PixelShaders = new();
public readonly HashSet<Pipeline> Pipelines = new();
public readonly HashSet<D3D11Pipeline> Pipelines = new();
public void CreateResources()
{
@ -115,7 +114,13 @@ namespace BizHawk.Bizware.Graphics
tex2d.DestroyTexture();
}
foreach (var pipeline in Pipelines)
{
pipeline.DestroyPipeline();
}
CurRenderTarget = null;
CurPipeline = null;
LinearSamplerState?.Dispose();
LinearSamplerState = null;
@ -146,6 +151,13 @@ namespace BizHawk.Bizware.Graphics
{
DestroyResources();
Textures.Clear();
foreach (var pipeline in Pipelines)
{
pipeline.DestroyPendingBuffers();
}
Pipelines.Clear();
}
}
}

View File

@ -33,15 +33,11 @@ namespace BizHawk.Bizware.Graphics
private ID3D11DeviceContext Context => _resources.Context;
private ID3D11BlendState BlendEnableState => _resources.BlendEnableState;
private ID3D11BlendState BlendDisableState => _resources.BlendDisableState;
private ID3D11SamplerState PointSamplerState => _resources.PointSamplerState;
private ID3D11SamplerState LinearSamplerState => _resources.LinearSamplerState;
private ID3D11RasterizerState RasterizerState => _resources.RasterizerState;
private FeatureLevel DeviceFeatureLevel => _resources.DeviceFeatureLevel;
private D3D11RenderTarget CurRenderTarget => _resources.CurRenderTarget;
private D3D11Pipeline CurPipeline => _resources.CurPipeline;
// rendering state
private Pipeline _curPipeline;
private D3D11SwapChain.SwapChainResources _controlSwapChain;
public IGL_D3D11()
@ -117,82 +113,11 @@ namespace BizHawk.Bizware.Graphics
private void ResetDevice(D3D11SwapChain.ControlParameters cp)
{
_controlSwapChain.Dispose();
Context.Flush(); // important to properly dispose of the swapchain
foreach (var pipeline in _resources.Pipelines)
{
var pw = (PipelineWrapper)pipeline.Opaque;
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
pw.VSConstantBuffers[i]?.Dispose();
pw.VSConstantBuffers[i] = null;
pw.PSConstantBuffers[i]?.Dispose();
pw.PSConstantBuffers[i] = null;
}
var vlw = (VertexLayoutWrapper)pipeline.VertexLayout.Opaque;
vlw.VertexInputLayout.Dispose();
vlw.VertexInputLayout = null;
vlw.VertexBuffer.Dispose();
vlw.VertexBuffer = null;
vlw.VertexBufferCount = 0;
}
foreach (var sw in _resources.VertexShaders.Select(vertexShader => (ShaderWrapper)vertexShader.Opaque))
{
sw.VS.Dispose();
sw.VS = null;
}
foreach (var sw in _resources.PixelShaders.Select(pixelShader => (ShaderWrapper)pixelShader.Opaque))
{
sw.PS.Dispose();
sw.PS = null;
}
Context.Flush(); // important to immediately dispose of the swapchain (if it's still around, we can't recreate it)
_resources.DestroyResources();
_resources.CreateResources();
foreach (var sw in _resources.VertexShaders.Select(vertexShader => (ShaderWrapper)vertexShader.Opaque))
{
sw.VS = Device.CreateVertexShader(sw.Bytecode.Span);
}
foreach (var sw in _resources.PixelShaders.Select(pixelShader => (ShaderWrapper)pixelShader.Opaque))
{
sw.PS = Device.CreatePixelShader(sw.Bytecode.Span);
}
foreach (var pipeline in _resources.Pipelines)
{
var pw = (PipelineWrapper)pipeline.Opaque;
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
var cbw = pw.PendingBuffers[i];
if (cbw == null)
{
break;
}
if (cbw.VSBufferSize > 0)
{
var bd = new BufferDescription(cbw.VSBufferSize, BindFlags.ConstantBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
pw.VSConstantBuffers[i] = Device.CreateBuffer(bd);
cbw.VSBufferDirty = true;
}
if (cbw.PSBufferSize > 0)
{
var bd = new BufferDescription(cbw.PSBufferSize, BindFlags.ConstantBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
pw.PSConstantBuffers[i] = Device.CreateBuffer(bd);
cbw.PSBufferDirty = true;
}
}
CreateInputLayout(pipeline.VertexLayout, pw.VertexShader);
}
var swapChain = CreateDXGISwapChain(cp);
var bbTex = swapChain.GetBuffer<ID3D11Texture2D>(0);
var bbRtvd = new RenderTargetViewDescription(RenderTargetViewDimension.Texture2D, Format.B8G8R8A8_UNorm);
@ -236,575 +161,51 @@ namespace BizHawk.Bizware.Graphics
public void Dispose()
{
_controlSwapChain.Dispose();
Context.Flush();
_resources.Dispose();
}
public void ClearColor(Color color)
=> Context.ClearRenderTargetView(CurRenderTarget?.RTV ?? _controlSwapChain.RTV, new(color.R, color.B, color.G, color.A));
private class ShaderWrapper // Disposable fields cleaned up by Internal_FreeShader
{
public ReadOnlyMemory<byte> Bytecode;
public ID3D11ShaderReflection Reflection;
public ID3D11VertexShader VS;
public ID3D11PixelShader PS;
public Shader IGLShader;
}
/// <exception cref="InvalidOperationException"><paramref name="required"/> is <see langword="true"/> and compilation error occurred</exception>
public Shader CreateVertexShader(string source, string entry, bool required)
{
try
{
var sw = new ShaderWrapper();
var profile = DeviceFeatureLevel switch
{
FeatureLevel.Level_9_1 or FeatureLevel.Level_9_2 => "vs_4_0_level_9_1",
FeatureLevel.Level_9_3 => "vs_4_0_level_9_3",
_ => "vs_4_0",
};
// note: we use D3D9-like shaders for legacy reasons, so we need the backwards compat flag
// TODO: remove D3D9 syntax from shaders
var result = Compiler.Compile(
shaderSource: source,
entryPoint: entry,
sourceName: null!, // this is safe to be null
profile: profile,
shaderFlags: ShaderFlags.OptimizationLevel3 | ShaderFlags.EnableBackwardsCompatibility);
sw.VS = Device.CreateVertexShader(result.Span);
sw.Reflection = Compiler.Reflect<ID3D11ShaderReflection>(result.Span);
sw.Bytecode = result;
var s = new Shader(this, sw, true);
sw.IGLShader = s;
_resources.VertexShaders.Add(s);
return s;
}
catch (Exception ex)
{
if (required)
{
throw;
}
return new(this, null, false) { Errors = ex.ToString() };
}
}
/// <exception cref="InvalidOperationException"><paramref name="required"/> is <see langword="true"/> and compilation error occurred</exception>
public Shader CreateFragmentShader(string source, string entry, bool required)
{
try
{
var sw = new ShaderWrapper();
var profile = DeviceFeatureLevel switch
{
FeatureLevel.Level_9_1 or FeatureLevel.Level_9_2 => "ps_4_0_level_9_1",
FeatureLevel.Level_9_3 => "ps_4_0_level_9_3",
_ => "ps_4_0",
};
// note: we use D3D9-like shaders for legacy reasons, so we need the backwards compat flag
// TODO: remove D3D9 syntax from shaders
var result = Compiler.Compile(
shaderSource: source,
entryPoint: entry,
sourceName: null!, // this is safe to be null
profile: profile,
shaderFlags: ShaderFlags.OptimizationLevel3 | ShaderFlags.EnableBackwardsCompatibility);
sw.PS = Device.CreatePixelShader(result.Span);
sw.Reflection = Compiler.Reflect<ID3D11ShaderReflection>(result.Span);
sw.Bytecode = result;
var s = new Shader(this, sw, true);
sw.IGLShader = s;
_resources.PixelShaders.Add(s);
return s;
}
catch (Exception ex)
{
if (required)
{
throw;
}
return new(this, null, false) { Errors = ex.ToString() };
}
}
public void EnableBlending()
=> Context.OMSetBlendState(BlendEnableState);
public void DisableBlending()
=> Context.OMSetBlendState(BlendDisableState);
private void CreateInputLayout(VertexLayout vertexLayout, ShaderWrapper vertexShader)
public IPipeline CreatePipeline(PipelineCompileArgs compileArgs)
=> new D3D11Pipeline(_resources, compileArgs);
public void BindPipeline(IPipeline pipeline)
{
var ves = new InputElementDescription[vertexLayout.Items.Count];
var stride = 0;
foreach (var (i, item) in vertexLayout.Items)
{
if (item.AttribType != VertexAttribPointerType.Float)
{
throw new NotSupportedException();
}
var d3d11Pipeline = (D3D11Pipeline)pipeline;
_resources.CurPipeline = d3d11Pipeline;
var semanticName = item.Usage switch
{
AttribUsage.Position => "POSITION",
AttribUsage.Color0 => "COLOR",
AttribUsage.Texcoord0 or AttribUsage.Texcoord1 => "TEXCOORD",
_ => throw new InvalidOperationException()
};
var format = item.Components switch
{
1 => Format.R32_Float,
2 => Format.R32G32_Float,
3 => Format.R32G32B32_Float,
4 => Format.R32G32B32A32_Float,
_ => throw new InvalidOperationException()
};
ves[i] = new(semanticName, item.Usage == AttribUsage.Texcoord1 ? 1 : 0, format, item.Offset, 0);
stride += 4 * item.Components;
}
var vlw = (VertexLayoutWrapper)vertexLayout.Opaque;
var bc = vertexShader.Bytecode.Span;
vlw.VertexInputLayout = Device.CreateInputLayout(ves, bc);
vlw.VertexStride = stride;
}
/// <exception cref="InvalidOperationException">
/// <paramref name="required"/> is <see langword="true"/> and either <paramref name="vertexShader"/> or <paramref name="fragmentShader"/> is unavailable (their <see cref="Shader.Available"/> property is <see langword="false"/>), or
/// one of <paramref name="vertexLayout"/>'s items has an unsupported value in <see cref="VertexLayout.LayoutItem.AttribType"/>, <see cref="VertexLayout.LayoutItem.Components"/>, or <see cref="VertexLayout.LayoutItem.Usage"/>
/// </exception>
public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
{
if (!vertexShader.Available || !fragmentShader.Available)
{
var errors = $"Vertex Shader:\r\n {vertexShader.Errors} \r\n-------\r\nFragment Shader:\r\n{fragmentShader.Errors}";
if (required)
{
throw new InvalidOperationException($"Couldn't build required D3D11 pipeline:\r\n{errors}");
}
return new(this, null, false, null, null, null) { Errors = errors };
}
var pw = new PipelineWrapper
{
VertexShader = (ShaderWrapper)vertexShader.Opaque,
FragmentShader = (ShaderWrapper)fragmentShader.Opaque,
};
CreateInputLayout(vertexLayout, pw.VertexShader);
// scan uniforms from reflection
var uniforms = new List<UniformInfo>();
var vsrefl = pw.VertexShader.Reflection;
var psrefl = pw.FragmentShader.Reflection;
foreach (var refl in new[] { vsrefl, psrefl })
{
var isVs = refl == vsrefl;
var reflCbs = refl.ConstantBuffers;
var todo = new Queue<(string, PendingBufferWrapper, string, int, ID3D11ShaderReflectionType)>();
for (var i = 0; i < reflCbs.Length; i++)
{
var cbDesc = reflCbs[i].Description;
var bd = new BufferDescription((cbDesc.Size + 15) & ~15, BindFlags.ConstantBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
var constantBuffer = Device.CreateBuffer(bd);
var pendingBuffer = Marshal.AllocCoTaskMem(cbDesc.Size);
pw.PendingBuffers[i] ??= new();
if (isVs)
{
pw.VSConstantBuffers[i] = constantBuffer;
pw.PendingBuffers[i].VSPendingBuffer = pendingBuffer;
pw.PendingBuffers[i].VSBufferSize = cbDesc.Size;
}
else
{
pw.PSConstantBuffers[i] = constantBuffer;
pw.PendingBuffers[i].PSPendingBuffer = pendingBuffer;
pw.PendingBuffers[i].PSBufferSize = cbDesc.Size;
}
var prefix = cbDesc.Name.RemovePrefix('$');
prefix = prefix is "Params" or "Globals" ? "" : $"{prefix}.";
foreach (var reflVari in reflCbs[i].Variables)
{
var reflVariDesc = reflVari.Description;
todo.Enqueue((prefix, pw.PendingBuffers[i], reflVariDesc.Name, reflVariDesc.StartOffset, reflVari.VariableType));
}
}
while (todo.Count != 0)
{
var (prefix, pbw, reflVariName, reflVariOffset, reflVariType) = todo.Dequeue();
var reflVariTypeDesc = reflVariType.Description;
if (reflVariTypeDesc.MemberCount > 0)
{
prefix = $"{prefix}{reflVariName}.";
for (var i = 0; i < reflVariTypeDesc.MemberCount; i++)
{
var memberName = reflVariType.GetMemberTypeName(i);
var memberType = reflVariType.GetMemberTypeByIndex(i);
var memberOffset = memberType.Description.Offset;
todo.Enqueue((prefix, pbw, memberName, reflVariOffset + memberOffset, memberType));
}
continue;
}
if (reflVariTypeDesc.Type is not (ShaderVariableType.Bool or ShaderVariableType.Float))
{
// unsupported type
continue;
}
var reflVariSize = 4 * reflVariTypeDesc.ColumnCount * reflVariTypeDesc.RowCount;
uniforms.Add(new()
{
Name = $"{prefix}{reflVariName}",
Opaque = new UniformWrapper
{
PBW = pbw,
VariableStartOffset = reflVariOffset,
VariableSize = reflVariSize,
VS = isVs
}
});
}
uniforms.AddRange(refl.BoundResources
.Where(resource => resource.Type == ShaderInputType.Sampler)
.Select(resource => new UniformInfo
{
IsSampler = true,
Name = resource.Name.RemovePrefix('$'),
Opaque = new UniformWrapper { VS = isVs },
SamplerIndex = resource.BindPoint
}));
}
var ret = new Pipeline(this, pw, true, vertexLayout, uniforms, memo);
_resources.Pipelines.Add(ret);
return ret;
}
public void FreePipeline(Pipeline pipeline)
{
// unavailable pipelines will have no opaque
if (pipeline.Opaque is not PipelineWrapper pw)
{
return;
}
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
if (pw.PendingBuffers[i] != null)
{
Marshal.FreeCoTaskMem(pw.PendingBuffers[i].VSPendingBuffer);
Marshal.FreeCoTaskMem(pw.PendingBuffers[i].PSPendingBuffer);
pw.PendingBuffers[i] = null;
}
pw.VSConstantBuffers[i]?.Dispose();
pw.VSConstantBuffers[i] = null;
pw.PSConstantBuffers[i]?.Dispose();
pw.PSConstantBuffers[i] = null;
}
pw.VertexShader.IGLShader.Release();
pw.FragmentShader.IGLShader.Release();
_resources.Pipelines.Remove(pipeline);
}
public void Internal_FreeShader(Shader shader)
{
var sw = (ShaderWrapper)shader.Opaque;
sw.Reflection?.Dispose();
sw.Reflection = null;
if (sw.VS != null)
{
sw.VS.Dispose();
sw.VS = null;
_resources.VertexShaders.Remove(shader);
}
if (sw.PS != null)
{
sw.PS.Dispose();
sw.PS = null;
_resources.PixelShaders.Remove(shader);
}
}
private class UniformWrapper
{
public PendingBufferWrapper PBW;
public int VariableStartOffset;
public int VariableSize;
public bool VS;
}
private class PendingBufferWrapper
{
public IntPtr VSPendingBuffer, PSPendingBuffer;
public int VSBufferSize, PSBufferSize;
public bool VSBufferDirty, PSBufferDirty;
}
private class PipelineWrapper // Disposable fields cleaned up by FreePipeline
{
public readonly PendingBufferWrapper[] PendingBuffers = new PendingBufferWrapper[ID3D11DeviceContext.CommonShaderConstantBufferSlotCount];
public readonly ID3D11Buffer[] VSConstantBuffers = new ID3D11Buffer[ID3D11DeviceContext.CommonShaderConstantBufferSlotCount];
public readonly ID3D11Buffer[] PSConstantBuffers = new ID3D11Buffer[ID3D11DeviceContext.CommonShaderConstantBufferSlotCount];
public ShaderWrapper VertexShader, FragmentShader;
}
private class VertexLayoutWrapper
{
public ID3D11InputLayout VertexInputLayout;
public int VertexStride;
public ID3D11Buffer VertexBuffer;
public int VertexBufferCount;
}
public VertexLayout CreateVertexLayout()
=> new(this, new VertexLayoutWrapper());
public void Internal_FreeVertexLayout(VertexLayout layout)
{
var vlw = (VertexLayoutWrapper)layout.Opaque;
vlw.VertexInputLayout?.Dispose();
vlw.VertexInputLayout = null;
vlw.VertexBuffer?.Dispose();
vlw.VertexBuffer = null;
}
public void BindPipeline(Pipeline pipeline)
{
_curPipeline = pipeline;
if (pipeline == null)
if (d3d11Pipeline == null)
{
// unbind? i don't know
return;
}
var pw = (PipelineWrapper)pipeline.Opaque;
Context.VSSetShader(pw.VertexShader.VS);
Context.PSSetShader(pw.FragmentShader.PS);
Context.VSSetShader(d3d11Pipeline.VS);
Context.PSSetShader(d3d11Pipeline.PS);
Context.VSSetConstantBuffers(0, pw.VSConstantBuffers);
Context.PSSetConstantBuffers(0, pw.PSConstantBuffers);
Context.VSSetConstantBuffers(0, d3d11Pipeline.VSConstantBuffers);
Context.PSSetConstantBuffers(0, d3d11Pipeline.PSConstantBuffers);
var vlw = (VertexLayoutWrapper)pipeline.VertexLayout.Opaque;
Context.IASetInputLayout(vlw.VertexInputLayout);
Context.IASetVertexBuffer(0, vlw.VertexBuffer, vlw.VertexStride);
Context.VSUnsetSamplers(0, ID3D11DeviceContext.CommonShaderSamplerSlotCount);
Context.VSUnsetShaderResources(0, ID3D11DeviceContext.CommonShaderSamplerSlotCount);
Context.PSUnsetSamplers(0, ID3D11DeviceContext.CommonShaderSamplerSlotCount);
Context.PSUnsetShaderResources(0, ID3D11DeviceContext.CommonShaderSamplerSlotCount);
Context.IASetInputLayout(d3d11Pipeline.VertexInputLayout);
Context.IASetVertexBuffer(0, d3d11Pipeline.VertexBuffer, d3d11Pipeline.VertexStride);
// not sure if this applies to the current pipeline or all pipelines
// just set it every time to be safe
Context.RSSetState(RasterizerState);
}
public void SetPipelineUniform(PipelineUniform uniform, bool value)
{
foreach (var ui in uniform.UniformInfos)
{
var uw = (UniformWrapper)ui.Opaque;
var pendingBuffer = uw.VS ? uw.PBW.VSPendingBuffer : uw.PBW.PSPendingBuffer;
unsafe
{
// note: HLSL bool is 4 bytes large
var b = value ? 1 : 0;
var variablePtr = (void*)(pendingBuffer + uw.VariableStartOffset);
Buffer.MemoryCopy(&b, variablePtr, uw.VariableSize, sizeof(int));
}
if (uw.VS)
{
uw.PBW.VSBufferDirty = true;
}
else
{
uw.PBW.PSBufferDirty = true;
}
}
}
public void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4x4 mat, bool transpose)
=> SetPipelineUniformMatrix(uniform, ref mat, transpose);
public void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4x4 mat, bool transpose)
{
foreach (var ui in uniform.UniformInfos)
{
var uw = (UniformWrapper)ui.Opaque;
var pendingBuffer = uw.VS ? uw.PBW.VSPendingBuffer : uw.PBW.PSPendingBuffer;
unsafe
{
// transpose logic is inversed
var m = transpose ? Matrix4x4.Transpose(mat) : mat;
var variablePtr = (void*)(pendingBuffer + uw.VariableStartOffset);
Buffer.MemoryCopy(&m, variablePtr, uw.VariableSize, sizeof(Matrix4x4));
}
if (uw.VS)
{
uw.PBW.VSBufferDirty = true;
}
else
{
uw.PBW.PSBufferDirty = true;
}
}
}
public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
{
foreach (var ui in uniform.UniformInfos)
{
var uw = (UniformWrapper)ui.Opaque;
var pendingBuffer = uw.VS ? uw.PBW.VSPendingBuffer : uw.PBW.PSPendingBuffer;
unsafe
{
var variablePtr = (void*)(pendingBuffer + uw.VariableStartOffset);
Buffer.MemoryCopy(&value, variablePtr, uw.VariableSize, sizeof(Vector4));
}
if (uw.VS)
{
uw.PBW.VSBufferDirty = true;
}
else
{
uw.PBW.PSBufferDirty = true;
}
}
}
public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
{
foreach (var ui in uniform.UniformInfos)
{
var uw = (UniformWrapper)ui.Opaque;
var pendingBuffer = uw.VS ? uw.PBW.VSPendingBuffer : uw.PBW.PSPendingBuffer;
unsafe
{
var variablePtr = (void*)(pendingBuffer + uw.VariableStartOffset);
Buffer.MemoryCopy(&value, variablePtr, uw.VariableSize, sizeof(Vector2));
}
if (uw.VS)
{
uw.PBW.VSBufferDirty = true;
}
else
{
uw.PBW.PSBufferDirty = true;
}
}
}
public void SetPipelineUniform(PipelineUniform uniform, float value)
{
foreach (var ui in uniform.UniformInfos)
{
var uw = (UniformWrapper)ui.Opaque;
var pendingBuffer = uw.VS ? uw.PBW.VSPendingBuffer : uw.PBW.PSPendingBuffer;
unsafe
{
var variablePtr = (void*)(pendingBuffer + uw.VariableStartOffset);
Buffer.MemoryCopy(&value, variablePtr, uw.VariableSize, sizeof(float));
}
if (uw.VS)
{
uw.PBW.VSBufferDirty = true;
}
else
{
uw.PBW.PSBufferDirty = true;
}
}
}
public void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
{
foreach (var ui in uniform.UniformInfos)
{
var uw = (UniformWrapper)ui.Opaque;
var pendingBuffer = uw.VS ? uw.PBW.VSPendingBuffer : uw.PBW.PSPendingBuffer;
unsafe
{
fixed (Vector4* v = values)
{
var variablePtr = (void*)(pendingBuffer + uw.VariableStartOffset);
Buffer.MemoryCopy(v, variablePtr, uw.VariableSize, values.Length * sizeof(Vector4));
}
}
if (uw.VS)
{
uw.PBW.VSBufferDirty = true;
}
else
{
uw.PBW.PSBufferDirty = true;
}
}
}
public void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex)
{
if (uniform.Owner == null)
{
return; // uniform was optimized out
}
var d3d11Tex = (D3D11Texture2D)tex;
var sampler = d3d11Tex.LinearFiltering ? LinearSamplerState : PointSamplerState;
foreach (var ui in uniform.UniformInfos)
{
if (!ui.IsSampler)
{
throw new InvalidOperationException("Uniform was not a texture/sampler");
}
var uw = (UniformWrapper)ui.Opaque;
if (uw.VS)
{
if (DeviceFeatureLevel == FeatureLevel.Level_9_1)
{
throw new InvalidOperationException("Feature level 9.1 does not support setting a shader resource in a vertex shader");
}
Context.VSSetShaderResource(ui.SamplerIndex, d3d11Tex.SRV);
Context.VSSetSampler(ui.SamplerIndex, sampler);
}
else
{
Context.PSSetShaderResource(ui.SamplerIndex, d3d11Tex.SRV);
Context.PSSetSampler(ui.SamplerIndex, sampler);
}
}
}
public ITexture2D CreateTexture(int width, int height)
=> new D3D11Texture2D(_resources, BindFlags.ShaderResource, ResourceUsage.Dynamic, CpuAccessFlags.Write, width, height);
@ -848,57 +249,56 @@ namespace BizHawk.Bizware.Graphics
public void Draw(IntPtr data, int count)
{
var vlw = (VertexLayoutWrapper)_curPipeline.VertexLayout.Opaque;
var stride = vlw.VertexStride;
var pipeline = CurPipeline;
var stride = pipeline.VertexStride;
if (vlw.VertexBufferCount < count)
if (pipeline.VertexBufferCount < count)
{
vlw.VertexBuffer?.Dispose();
pipeline.VertexBuffer?.Dispose();
var bd = new BufferDescription(stride * count, BindFlags.VertexBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write);
vlw.VertexBuffer = Device.CreateBuffer(in bd, data);
vlw.VertexBufferCount = count;
pipeline.VertexBuffer = Device.CreateBuffer(in bd, data);
pipeline.VertexBufferCount = count;
}
else
{
var mappedVb = Context.Map(vlw.VertexBuffer, MapMode.WriteDiscard);
var mappedVb = Context.Map(pipeline.VertexBuffer, MapMode.WriteDiscard);
try
{
unsafe
{
Buffer.MemoryCopy((void*)data, (void*)mappedVb.DataPointer, stride * vlw.VertexBufferCount, stride * count);
Buffer.MemoryCopy((void*)data, (void*)mappedVb.DataPointer, stride * pipeline.VertexBufferCount, stride * count);
}
}
finally
{
Context.Unmap(vlw.VertexBuffer);
Context.Unmap(pipeline.VertexBuffer);
}
}
unsafe
{
var pw = (PipelineWrapper)_curPipeline.Opaque;
for (var i = 0; i < ID3D11DeviceContext.CommonShaderConstantBufferSlotCount; i++)
{
var pbw = pw.PendingBuffers[i];
if (pbw == null)
var pb = pipeline.PendingBuffers[i];
if (pb == null)
{
break;
}
if (pbw.VSBufferDirty)
if (pb.VSBufferDirty)
{
var vsCb = Context.Map(pw.VSConstantBuffers[i], MapMode.WriteDiscard);
Buffer.MemoryCopy((void*)pbw.VSPendingBuffer, (void*)vsCb.DataPointer, pbw.VSBufferSize, pbw.VSBufferSize);
Context.Unmap(pw.VSConstantBuffers[i]);
pbw.VSBufferDirty = false;
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 (pbw.PSBufferDirty)
if (pb.PSBufferDirty)
{
var psCb = Context.Map(pw.PSConstantBuffers[i], MapMode.WriteDiscard);
Buffer.MemoryCopy((void*)pbw.PSPendingBuffer, (void*)psCb.DataPointer, pbw.PSBufferSize, pbw.PSBufferSize);
Context.Unmap(pw.PSConstantBuffers[i]);
pbw.PSBufferDirty = false;
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;
}
}
}

View File

@ -25,12 +25,6 @@ namespace BizHawk.Bizware.Graphics
public void ClearColor(Color color)
=> GetCurrentGraphics().Clear(color);
public Shader CreateFragmentShader(string source, string entry, bool required)
=> null;
public Shader CreateVertexShader(string source, string entry, bool required)
=> null;
public void EnableBlending()
{
var g = GetCurrentGraphics();
@ -45,17 +39,10 @@ namespace BizHawk.Bizware.Graphics
g.CompositingQuality = CompositingQuality.HighSpeed;
}
public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
=> null;
public IPipeline CreatePipeline(PipelineCompileArgs compileArgs)
=> throw new NotSupportedException("GDI+ does not support pipelines");
public void FreePipeline(Pipeline pipeline)
{
}
public VertexLayout CreateVertexLayout()
=> new(this, null);
public void Internal_FreeVertexLayout(VertexLayout layout)
public void BindPipeline(IPipeline pipeline)
{
}
@ -63,47 +50,6 @@ namespace BizHawk.Bizware.Graphics
{
}
public void BindPipeline(Pipeline pipeline)
{
}
public void Internal_FreeShader(Shader shader)
{
}
public void SetPipelineUniform(PipelineUniform uniform, bool value)
{
}
public void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4x4 mat, bool transpose)
{
}
public void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4x4 mat, bool transpose)
{
}
public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
{
}
public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
{
}
public void SetPipelineUniform(PipelineUniform uniform, float value)
{
}
public void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
{
}
public void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex)
{
}
public ITexture2D CreateTexture(int width, int height)
=> new GDIPlusTexture2D(width, height);

View File

@ -5,10 +5,7 @@ using System.Numerics;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// This is a wrapper over OpenGL and direct3d to give a uniform interface
/// TODO - This really needs to be split up into an internal and a user interface. so many of the functions are made to support the smart wrappers
/// Maybe make a method that returns an interface used for advanced methods (and IGL_OpenGL could implement that as well and just "return this:")
///
/// This is a wrapper over OpenGL and Direct3D11 to give a uniform interface
/// NOTE: THIS SHOULD NOT BE ASSUMED TO BE THREAD SAFE! Make a new IGL if you want to use it in a new thread. I hope that will work...
/// </summary>
public interface IGL : IDisposable
@ -18,94 +15,6 @@ namespace BizHawk.Bizware.Graphics
/// </summary>
EDispMethod DispMethodEnum { get; }
/// <summary>
/// Clears the currently bound render target with the specified color
/// </summary>
void ClearColor(Color color);
/// <summary>
/// Compile a fragment shader. This is the simplified method. A more complex method may be added later which will accept multiple sources and preprocessor definitions independently
/// </summary>
Shader CreateFragmentShader(string source, string entry, bool required);
/// <summary>
/// Compile a vertex shader. This is the simplified method. A more complex method may be added later which will accept multiple sources and preprocessor definitions independently
/// </summary>
Shader CreateVertexShader(string source, string entry, bool required);
/// <summary>
/// Creates a complete pipeline from the provided vertex and fragment shader handles
/// </summary>
Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo);
/// <summary>
/// Binds this pipeline as the current used for rendering
/// </summary>
void BindPipeline(Pipeline pipeline);
/// <summary>
/// Sets a uniform sampler to use use the provided texture
/// </summary>
void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4x4 mat, bool transpose);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4x4 mat, bool transpose);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetPipelineUniform(PipelineUniform uniform, Vector4 value);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetPipelineUniform(PipelineUniform uniform, Vector2 value);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetPipelineUniform(PipelineUniform uniform, float value);
/// <summary>
/// Sets uniform values
/// </summary>
void SetPipelineUniform(PipelineUniform uniform, Vector4[] values);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetPipelineUniform(PipelineUniform uniform, bool value);
/// <summary>
/// Draws based on the currently set pipeline
/// Data contains vertexes based on the pipeline's VertexLayout
/// Count is the vertex count
/// Vertexes must form triangle strips
/// </summary>
void Draw(IntPtr data, int count);
/// <summary>
/// Creates a vertex layout resource
/// </summary>
VertexLayout CreateVertexLayout();
/// <summary>
/// Enables normal (non-premultiplied) alpha blending.
/// </summary>
void EnableBlending();
/// <summary>
/// Disables blending (alpha values are copied from the source fragment)
/// </summary>
void DisableBlending();
/// <summary>
/// Creates a texture with the specified dimensions
/// The texture will use a clamping address mode and nearest neighbor filtering by default
@ -118,22 +27,6 @@ namespace BizHawk.Bizware.Graphics
/// </summary>
ITexture2D WrapGLTexture2D(int glTexId, int width, int height);
/// <summary>
/// Sets the viewport (and scissor) according to the provided specifications
/// </summary>
void SetViewport(int x, int y, int width, int height);
/// <summary>
/// Generates a proper 2D othographic projection for the given destination size, suitable for use in a GUI
/// </summary>
Matrix4x4 CreateGuiProjectionMatrix(int width, int height);
/// <summary>
/// Generates a proper view transform for a standard 2D othographic projection, including half-pixel jitter if necessary
/// and re-establishing of a normal 2D graphics top-left origin. Suitable for use in a GUI
/// </summary>
Matrix4x4 CreateGuiViewMatrix(int width, int height, bool autoflip = true);
/// <summary>
/// Creates a render target. Only includes a color buffer, and will always be in byte order BGRA (i.e. little endian ARGB)
/// This may unbind a previously bound render target
@ -147,18 +40,52 @@ namespace BizHawk.Bizware.Graphics
void BindDefaultRenderTarget();
/// <summary>
/// Frees the provided pipeline. Same as disposing the resource.
/// Clears the currently bound render target with the specified color
/// </summary>
void FreePipeline(Pipeline pipeline);
void ClearColor(Color color);
/// <summary>
/// Frees the provided shader. For internal use only.
/// Creates a complete pipeline
/// </summary>
void Internal_FreeShader(Shader shader);
IPipeline CreatePipeline(PipelineCompileArgs compileArgs);
/// <summary>
/// Frees the provided vertex layout. For internal use only.
/// Binds this pipeline as the current used for rendering
/// </summary>
void Internal_FreeVertexLayout(VertexLayout vertexLayout);
void BindPipeline(IPipeline pipeline);
/// <summary>
/// Enables normal (non-premultiplied) alpha blending.
/// </summary>
void EnableBlending();
/// <summary>
/// Disables blending (alpha values are copied from the source fragment)
/// </summary>
void DisableBlending();
/// <summary>
/// Sets the viewport (and scissor) according to the provided specifications
/// </summary>
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
/// Vertexes must form triangle strips
/// </summary>
void Draw(IntPtr data, int count);
/// <summary>
/// Generates a proper 2D othographic projection for the given destination size, suitable for use in a GUI
/// </summary>
Matrix4x4 CreateGuiProjectionMatrix(int width, int height);
/// <summary>
/// Generates a proper view transform for a standard 2D othographic projection, including half-pixel jitter if necessary
/// and re-establishing of a normal 2D graphics top-left origin. Suitable for use in a GUI
/// </summary>
Matrix4x4 CreateGuiViewMatrix(int width, int height, bool autoflip = true);
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Numerics;
namespace BizHawk.Bizware.Graphics
{
public interface IPipeline : IDisposable
{
bool HasUniformSampler(string name);
string GetUniformSamplerName(int index);
/// <summary>
/// Sets a uniform sampler to use use the provided texture
/// </summary>
void SetUniformSampler(string name, ITexture2D tex);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetUniformMatrix(string name, Matrix4x4 mat, bool transpose = false);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetUniformMatrix(string name, ref Matrix4x4 mat, bool transpose = false);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetUniform(string name, Vector4 value);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetUniform(string name, Vector2 value);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetUniform(string name, float value);
/// <summary>
/// Sets a uniform value
/// </summary>
void SetUniform(string name, bool value);
}
}

View File

@ -1,30 +1,13 @@
// regarding binding and vertex arrays:
// http://stackoverflow.com/questions/8704801/glvertexattribpointer-clarification
// http://stackoverflow.com/questions/9536973/oes-vertex-array-object-and-client-state
// http://www.opengl.org/wiki/Vertex_Specification
// etc
// glBindAttribLocation (programID, 0, "vertexPosition_modelspace");
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Numerics;
using BizHawk.Common;
using Silk.NET.OpenGL.Legacy;
using BizShader = BizHawk.Bizware.Graphics.Shader;
using GLVertexAttribPointerType = Silk.NET.OpenGL.Legacy.VertexAttribPointerType;
using Silk.NET.OpenGL;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// OpenGL implementation of the BizwareGL.IGL interface
/// OpenGL implementation of the IGL interface
/// </summary>
public class IGL_OpenGL : IGL
{
@ -33,7 +16,7 @@ namespace BizHawk.Bizware.Graphics
private readonly GL GL;
// rendering state
private Pipeline _currPipeline;
private OpenGLPipeline _curPipeline;
internal bool DefaultRenderTargetBound;
// this IGL either requires at least OpenGL 3.0
@ -58,12 +41,6 @@ namespace BizHawk.Bizware.Graphics
GL.Clear(ClearBufferMask.ColorBufferBit);
}
public BizShader CreateVertexShader(string source, string entry, bool required)
=> CreateShader(ShaderType.VertexShader, source, required);
public BizShader CreateFragmentShader(string source, string entry, bool required)
=> CreateShader(ShaderType.FragmentShader, source, required);
public void EnableBlending()
{
GL.Enable(EnableCap.Blend);
@ -74,375 +51,60 @@ namespace BizHawk.Bizware.Graphics
public void DisableBlending()
=> GL.Disable(EnableCap.Blend);
private class ShaderWrapper
public IPipeline CreatePipeline(PipelineCompileArgs compileArgs)
{
public uint SID;
try
{
return new OpenGLPipeline(GL, compileArgs);
}
finally
{
BindPipeline(null);
}
}
private class PipelineWrapper
public void BindPipeline(IPipeline pipeline)
{
public uint PID;
public BizShader FragmentShader, VertexShader;
public List<int> SamplerLocs;
}
_curPipeline = (OpenGLPipeline)pipeline;
/// <exception cref="InvalidOperationException">
/// <paramref name="required"/> is <see langword="true"/> and either <paramref name="vertexShader"/> or <paramref name="fragmentShader"/> is unavailable (their <see cref="BizShader.Available"/> property is <see langword="false"/>), or
/// <c>glLinkProgram</c> call did not produce expected result
/// </exception>
public Pipeline CreatePipeline(VertexLayout vertexLayout, BizShader vertexShader, BizShader fragmentShader, bool required, string memo)
{
// if the shaders aren't available, the pipeline isn't either
if (!vertexShader.Available || !fragmentShader.Available)
{
var errors = $"Vertex Shader:\r\n {vertexShader.Errors} \r\n-------\r\nFragment Shader:\r\n{fragmentShader.Errors}";
if (required)
{
throw new InvalidOperationException($"Couldn't build required GL pipeline:\r\n{errors}");
}
return new(this, null, false, null, null, null) { Errors = errors };
}
var success = true;
var vsw = (ShaderWrapper)vertexShader.Opaque;
var fsw = (ShaderWrapper)fragmentShader.Opaque;
var pid = GL.CreateProgram();
GL.AttachShader(pid, vsw.SID);
GL.AttachShader(pid, fsw.SID);
_ = GL.GetError();
GL.LinkProgram(pid);
var errcode = (ErrorCode)GL.GetError();
var resultLog = GL.GetProgramInfoLog(pid);
if (errcode != ErrorCode.NoError)
{
if (required)
{
throw new InvalidOperationException($"Error creating pipeline (error returned from glLinkProgram): {errcode}\r\n\r\n{resultLog}");
}
success = false;
}
GL.GetProgram(pid, GLEnum.LinkStatus, out var linkStatus);
if (linkStatus == 0)
{
if (required)
{
throw new InvalidOperationException($"Error creating pipeline (link status false returned from glLinkProgram): \r\n\r\n{resultLog}");
}
success = false;
#if DEBUG
resultLog = GL.GetProgramInfoLog(pid);
Util.DebugWriteLine(resultLog);
#endif
}
// need to work on validation. apparently there are some weird caveats to glValidate which make it complicated and possibly excuses (barely) the intel drivers' dysfunctional operation
// "A sampler points to a texture unit used by fixed function with an incompatible target"
//
// info:
// http://www.opengl.org/sdk/docs/man/xhtml/glValidateProgram.xml
// This function mimics the validation operation that OpenGL implementations must perform when rendering commands are issued while programmable shaders are part of current state.
// glValidateProgram checks to see whether the executables contained in program can execute given the current OpenGL state
// This function is typically useful only during application development.
//
// So, this is no big deal. we shouldn't be calling validate right now anyway.
// conclusion: glValidate is very complicated and is of virtually no use unless your draw calls are returning errors and you want to know why
// _ = GL.GetError();
// GL.ValidateProgram(pid);
// errcode = (ErrorCode)GL.GetError();
// resultLog = GL.GetProgramInfoLog(pid);
// if (errcode != ErrorCode.NoError)
// throw new InvalidOperationException($"Error creating pipeline (error returned from glValidateProgram): {errcode}\r\n\r\n{resultLog}");
// GL.GetProgram(pid, GetProgramParameterName.ValidateStatus, out var validateStatus);
// if (validateStatus == 0)
// throw new InvalidOperationException($"Error creating pipeline (validateStatus status false returned from glValidateProgram): \r\n\r\n{resultLog}");
// set the program to active, in case we need to set sampler uniforms on it
GL.UseProgram(pid);
// get all the uniforms
var uniforms = new List<UniformInfo>();
GL.GetProgram(pid, GLEnum.ActiveUniforms, out var nUniforms);
var samplers = new List<int>();
for (uint i = 0; i < nUniforms; i++)
{
GL.GetActiveUniform(pid, i, 1024, out _, out _, out UniformType type, out string name);
var loc = GL.GetUniformLocation(pid, name);
var ui = new UniformInfo { Name = name, Opaque = loc };
if (type == UniformType.Sampler2D)
{
ui.IsSampler = true;
ui.SamplerIndex = samplers.Count;
ui.Opaque = loc | (samplers.Count << 24);
samplers.Add(loc);
}
uniforms.Add(ui);
}
// deactivate the program, so we don't accidentally use it
GL.UseProgram(0);
if (!vertexShader.Available) success = false;
if (!fragmentShader.Available) success = false;
var pw = new PipelineWrapper { PID = pid, VertexShader = vertexShader, FragmentShader = fragmentShader, SamplerLocs = samplers };
return new(this, pw, success, vertexLayout, uniforms, memo);
}
public void FreePipeline(Pipeline pipeline)
{
// unavailable pipelines will have no opaque
if (pipeline.Opaque is not PipelineWrapper pw)
{
return;
}
GL.DeleteProgram(pw.PID);
pw.FragmentShader.Release();
pw.VertexShader.Release();
}
public void Internal_FreeShader(BizShader shader)
{
var sw = (ShaderWrapper)shader.Opaque;
GL.DeleteShader(sw.SID);
}
/// <exception cref="InvalidOperationException"><paramref name="pipeline"/>.<see cref="Pipeline.Available"/> is <see langword="false"/></exception>
public void BindPipeline(Pipeline pipeline)
{
_currPipeline = pipeline;
if (pipeline == null)
if (_curPipeline == null)
{
GL.BindVertexArray(0);
GL.BindBuffer(GLEnum.ArrayBuffer, 0);
GL.UseProgram(0);
_curPipeline = null;
return;
}
if (!pipeline.Available)
{
throw new InvalidOperationException("Attempt to bind unavailable pipeline");
}
var pw = (PipelineWrapper)pipeline.Opaque;
GL.UseProgram(pw.PID);
// this is dumb and confusing, but we have to bind physical sampler numbers to sampler variables.
for (var i = 0; i < pw.SamplerLocs.Count; i++)
{
GL.Uniform1(pw.SamplerLocs[i], i);
}
}
private class VertexLayoutWrapper
{
public uint VAO;
public uint VBO;
public int BufferLen;
}
public VertexLayout CreateVertexLayout()
{
var vlw = new VertexLayoutWrapper
{
VAO = GL.GenVertexArray(),
VBO = GL.GenBuffer(),
BufferLen = 0,
};
return new(this, vlw);
}
public void Internal_FreeVertexLayout(VertexLayout vl)
{
var vlw = (VertexLayoutWrapper)vl.Opaque;
GL.DeleteVertexArray(vlw.VAO);
GL.DeleteBuffer(vlw.VBO);
}
private void LegacyBindArrayData(VertexLayout vertexLayout, IntPtr pData)
{
// DEPRECATED CRAP USED, NEEDED FOR ANCIENT SHADERS
// ALSO THIS IS WHY LEGACY PACKAGE IS USED AS NON-LEGACY DOESN'T HAVE THESE
// TODO: REMOVE NEED FOR THIS
#pragma warning disable CS0618
#pragma warning disable CS0612
// disable all the client states.. a lot of overhead right now, to be sure
GL.DisableClientState(EnableCap.VertexArray);
GL.DisableClientState(EnableCap.ColorArray);
for (var i = 1; i >= 0; i--)
{
GL.ClientActiveTexture(TextureUnit.Texture0 + i);
GL.DisableClientState(EnableCap.TextureCoordArray);
}
unsafe
{
foreach (var (_, item) in vertexLayout.Items)
{
switch (item.Usage)
{
case AttribUsage.Position:
GL.EnableClientState(EnableCap.VertexArray);
GL.VertexPointer(item.Components, VertexPointerType.Float, (uint)item.Stride, (pData + item.Offset).ToPointer());
break;
case AttribUsage.Texcoord0:
GL.ClientActiveTexture(TextureUnit.Texture0);
GL.EnableClientState(EnableCap.TextureCoordArray);
GL.TexCoordPointer(item.Components, TexCoordPointerType.Float, (uint)item.Stride, (pData + item.Offset).ToPointer());
break;
case AttribUsage.Texcoord1:
GL.ClientActiveTexture(TextureUnit.Texture1);
GL.EnableClientState(EnableCap.TextureCoordArray);
GL.TexCoordPointer(item.Components, TexCoordPointerType.Float, (uint)item.Stride, (pData + item.Offset).ToPointer());
GL.ClientActiveTexture(TextureUnit.Texture0);
break;
case AttribUsage.Color0:
break;
case AttribUsage.Unspecified:
default:
throw new InvalidOperationException();
}
}
}
#pragma warning restore CS0618
#pragma warning restore CS0612
GL.BindVertexArray(_curPipeline.VAO);
GL.BindBuffer(GLEnum.ArrayBuffer, _curPipeline.VBO);
GL.UseProgram(_curPipeline.PID);
}
public void Draw(IntPtr data, int count)
{
if (_currPipeline == null)
if (_curPipeline == null)
{
throw new InvalidOperationException($"Tried to {nameof(Draw)} without pipeline!");
}
var vertexLayout = _currPipeline.VertexLayout;
if (_currPipeline.Memo != "xgui")
{
LegacyBindArrayData(vertexLayout, data);
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, (uint)count);
return;
}
var vlw = (VertexLayoutWrapper)vertexLayout.Opaque;
GL.BindVertexArray(vlw.VAO);
GL.BindBuffer(GLEnum.ArrayBuffer, vlw.VBO);
var stride = vertexLayout.Items[0].Stride;
Debug.Assert(vertexLayout.Items.All(i => i.Value.Stride == stride));
unsafe
{
var vertexes = new ReadOnlySpan<byte>(data.ToPointer(), count * stride);
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 > vlw.BufferLen)
if (vertexes.Length > _curPipeline.VertexBufferLen)
{
GL.BufferData(GLEnum.ArrayBuffer, vertexes, GLEnum.DynamicDraw);
vlw.BufferLen = vertexes.Length;
_curPipeline.VertexBufferLen = vertexes.Length;
}
else
{
GL.BufferSubData(GLEnum.ArrayBuffer, 0, vertexes);
}
foreach (var (i, item) in vertexLayout.Items)
{
GL.VertexAttribPointer(
(uint)i,
item.Components,
(GLVertexAttribPointerType)item.AttribType, // these are the same enum
item.Normalized,
(uint)item.Stride,
(void*)item.Offset);
GL.EnableVertexAttribArray((uint)i);
}
}
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, (uint)count);
foreach (var (i, _) in vertexLayout.Items)
{
GL.DisableVertexAttribArray((uint)i);
}
GL.BindBuffer(GLEnum.ArrayBuffer, 0);
GL.BindVertexArray(0);
}
public void SetPipelineUniform(PipelineUniform uniform, bool value)
{
GL.Uniform1((int)uniform.Sole.Opaque, value ? 1 : 0);
}
public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4x4 mat, bool transpose)
{
GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)&mat);
}
public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4x4 mat, bool transpose)
{
fixed (Matrix4x4* pMat = &mat)
{
GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)pMat);
}
}
public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
{
GL.Uniform4((int)uniform.Sole.Opaque, value.X, value.Y, value.Z, value.W);
}
public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
{
GL.Uniform2((int)uniform.Sole.Opaque, value.X, value.Y);
}
public void SetPipelineUniform(PipelineUniform uniform, float value)
{
if (uniform.Owner == null)
{
return; // uniform was optimized out
}
GL.Uniform1((int)uniform.Sole.Opaque, value);
}
public unsafe void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
{
fixed (Vector4* pValues = &values[0])
{
GL.Uniform4((int)uniform.Sole.Opaque, (uint)values.Length, (float*)pValues);
}
}
public void SetPipelineUniformSampler(PipelineUniform uniform, ITexture2D tex)
{
var n = (int)uniform.Sole.Opaque >> 24;
// set the sampler index into the uniform first
GL.ActiveTexture(TextureUnit.Texture0 + n);
// now bind the texture
GL.BindTexture(TextureTarget.Texture2D, ((OpenGLTexture2D)tex).TexID);
}
public ITexture2D CreateTexture(int width, int height)
@ -490,89 +152,5 @@ namespace BizHawk.Bizware.Graphics
GL.Scissor(x, y, (uint)width, (uint)height); // hack for mupen[rice]+intel: at least the rice plugin leaves the scissor rectangle scrambled, and we're trying to run it in the main graphics context for intel
// BUT ALSO: new specifications.. viewport+scissor make sense together
}
private BizShader CreateShader(ShaderType type, string source, bool required)
{
var sw = new ShaderWrapper();
var info = string.Empty;
_ = GL.GetError();
var sid = GL.CreateShader(type);
var ok = CompileShaderSimple(sid, source, required);
if (!ok)
{
GL.GetShaderInfoLog(sid, out info);
GL.DeleteShader(sid);
sid = 0;
}
var ret = new BizShader(this, sw, ok)
{
Errors = info
};
sw.SID = sid;
return ret;
}
private bool CompileShaderSimple(uint sid, string source, bool required)
{
var success = true;
var errcode = (ErrorCode)GL.GetError();
if (errcode != ErrorCode.NoError)
{
if (required)
{
throw new InvalidOperationException($"Error compiling shader (from previous operation) {errcode}");
}
success = false;
}
GL.ShaderSource(sid, source);
errcode = (ErrorCode)GL.GetError();
if (errcode != ErrorCode.NoError)
{
if (required)
{
throw new InvalidOperationException($"Error compiling shader ({nameof(GL.ShaderSource)}) {errcode}");
}
success = false;
}
GL.CompileShader(sid);
errcode = (ErrorCode)GL.GetError();
var resultLog = GL.GetShaderInfoLog(sid);
if (errcode != ErrorCode.NoError)
{
var message = $"Error compiling shader ({nameof(GL.CompileShader)}) {errcode}\r\n\r\n{resultLog}";
if (required)
{
throw new InvalidOperationException(message);
}
Console.WriteLine(message);
success = false;
}
GL.GetShader(sid, ShaderParameterName.CompileStatus, out var n);
if (n == 0)
{
if (required)
{
throw new InvalidOperationException($"Error compiling shader ({nameof(GL.GetShader)})\r\n\r\n{resultLog}");
}
success = false;
}
return success;
}
}
}

View File

@ -0,0 +1,275 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Silk.NET.OpenGL;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Bizware.Graphics
{
internal class OpenGLPipeline : IPipeline
{
private readonly GL GL;
public readonly uint VAO;
public readonly uint VBO;
public readonly int VertexStride;
public int VertexBufferLen;
public readonly uint VertexSID;
public readonly uint FragmentSID;
public readonly uint PID;
private readonly Dictionary<string, int> _uniforms = new();
private readonly Dictionary<string, int> _samplers = new();
private uint CompileShader(string source, ShaderType type)
{
var sid = GL.CreateShader(type);
try
{
var errcode = (ErrorCode)GL.GetError();
if (errcode != ErrorCode.NoError)
{
throw new InvalidOperationException($"Error compiling shader (from previous operation) {errcode}");
}
GL.ShaderSource(sid, source);
errcode = (ErrorCode)GL.GetError();
if (errcode != ErrorCode.NoError)
{
throw new InvalidOperationException($"Error compiling shader ({nameof(GL.ShaderSource)}) {errcode}");
}
GL.CompileShader(sid);
errcode = (ErrorCode)GL.GetError();
var resultLog = GL.GetShaderInfoLog(sid);
if (errcode != ErrorCode.NoError)
{
throw new InvalidOperationException( $"Error compiling shader ({nameof(GL.CompileShader)}) {errcode}\r\n\r\n{resultLog}");
}
GL.GetShader(sid, ShaderParameterName.CompileStatus, out var n);
if (n == 0)
{
throw new InvalidOperationException($"Error compiling shader ({nameof(GL.GetShader)})\r\n\r\n{resultLog}");
}
return sid;
}
catch
{
GL.DeleteShader(sid);
throw;
}
}
public OpenGLPipeline(GL gl, PipelineCompileArgs compileArgs)
{
GL = gl;
try
{
VAO = GL.GenVertexArray();
VBO = GL.GenBuffer();
VertexStride = compileArgs.VertexLayoutStride;
_ = GL.GetError();
VertexSID = CompileShader(compileArgs.VertexShaderArgs.Source, ShaderType.VertexShader);
FragmentSID = CompileShader(compileArgs.FragmentShaderArgs.Source, ShaderType.FragmentShader);
PID = GL.CreateProgram();
GL.AttachShader(PID, VertexSID);
GL.AttachShader(PID, FragmentSID);
GL.BindVertexArray(VAO); // used by EnableVertexAttribArray
GL.BindBuffer(GLEnum.ArrayBuffer, VBO); // used by VertexAttribPointer
unsafe
{
for (var i = 0; i < compileArgs.VertexLayout.Count; i++)
{
var item = compileArgs.VertexLayout[i];
var attribIndex = (uint)i;
GL.BindAttribLocation(PID, attribIndex, item.Name);
GL.EnableVertexAttribArray(attribIndex);
GL.VertexAttribPointer(
attribIndex,
item.Components,
VertexAttribPointerType.Float,
normalized: false,
(uint)VertexStride,
(void*)item.Offset);
}
}
GL.BindFragDataLocation(PID, 0, compileArgs.FragmentOutputName);
_ = GL.GetError();
GL.LinkProgram(PID);
var errcode = (ErrorCode)GL.GetError();
var resultLog = GL.GetProgramInfoLog(PID);
if (errcode != ErrorCode.NoError)
{
throw new InvalidOperationException($"Error creating pipeline (error returned from ({nameof(GL.LinkProgram)}): {errcode}\r\n\r\n{resultLog}");
}
GL.GetProgram(PID, GLEnum.LinkStatus, out var linkStatus);
if (linkStatus == 0)
{
throw new InvalidOperationException($"Error creating pipeline (link status false returned from {nameof(GL.GetProgram)}): \r\n\r\n{resultLog}");
}
// need to work on validation. apparently there are some weird caveats to glValidate which make it complicated and possibly excuses (barely) the intel drivers' dysfunctional operation
// "A sampler points to a texture unit used by fixed function with an incompatible target"
//
// info:
// http://www.opengl.org/sdk/docs/man/xhtml/glValidateProgram.xml
// This function mimics the validation operation that OpenGL implementations must perform when rendering commands are issued while programmable shaders are part of current state.
// glValidateProgram checks to see whether the executables contained in program can execute given the current OpenGL state
// This function is typically useful only during application development.
//
// So, this is no big deal. we shouldn't be calling validate right now anyway.
// conclusion: glValidate is very complicated and is of virtually no use unless your draw calls are returning errors and you want to know why
#if false
_ = GL.GetError();
GL.ValidateProgram(PID);
errcode = (ErrorCode)GL.GetError();
resultLog = GL.GetProgramInfoLog(PID);
if (errcode != ErrorCode.NoError)
{
throw new InvalidOperationException($"Error creating pipeline (error returned from {nameof(GL.ValidateProgram)}): {errcode}\r\n\r\n{resultLog}");
}
GL.GetProgram(PID, GLEnum.ValidateStatus, out var validateStatus);
if (validateStatus == 0)
{
throw new InvalidOperationException($"Error creating pipeline (validateStatus status false returned from glValidateProgram): \r\n\r\n{resultLog}");
}
#endif
// set the program to active, in case we need to set sampler uniforms on it
GL.UseProgram(PID);
// get all the uniforms
GL.GetProgram(PID, GLEnum.ActiveUniforms, out var numUniforms);
for (uint i = 0; i < numUniforms; i++)
{
GL.GetActiveUniform(PID, i, 1024, out _, out _, out UniformType type, out string name);
var loc = GL.GetUniformLocation(PID, name);
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (type)
{
case UniformType.Bool:
case UniformType.Float:
case UniformType.FloatVec2:
case UniformType.FloatVec3:
case UniformType.FloatVec4:
case UniformType.FloatMat4:
_uniforms.Add(name, loc);
break;
case UniformType.Sampler2D:
// this is dumb and confusing, but we have to bind physical sampler numbers to sampler variables
var bindPoint = _samplers.Count;
GL.Uniform1(loc, bindPoint);
_samplers.Add(name, bindPoint);
break;
}
}
}
catch
{
Dispose();
throw;
}
}
public void Dispose()
{
GL.DeleteProgram(PID);
GL.DeleteShader(VertexSID);
GL.DeleteShader(FragmentSID);
GL.DeleteVertexArray(VAO);
GL.DeleteBuffer(VBO);
}
public bool HasUniformSampler(string name)
=> _samplers.ContainsKey(name);
public string GetUniformSamplerName(int index)
{
var sampler = _samplers.AsEnumerable().FirstOrNull(s => s.Value == index);
return sampler?.Key;
}
public void SetUniformSampler(string name, ITexture2D tex)
{
if (_samplers.TryGetValue(name, out var sampler))
{
var oglTex = (OpenGLTexture2D)tex;
GL.ActiveTexture(TextureUnit.Texture0 + sampler);
GL.BindTexture(TextureTarget.Texture2D, oglTex.TexID);
}
}
public void SetUniformMatrix(string name, Matrix4x4 mat, bool transpose)
=> SetUniformMatrix(name, ref mat, transpose);
public unsafe void SetUniformMatrix(string name, ref Matrix4x4 mat, bool transpose)
{
if (_uniforms.TryGetValue(name, out var uid))
{
fixed (Matrix4x4* p = &mat)
{
GL.UniformMatrix4(uid, 1, transpose, (float*)p);
}
}
}
public unsafe void SetUniform(string name, Vector4 value)
{
if (_uniforms.TryGetValue(name, out var uid))
{
GL.Uniform4(uid, 1, (float*)&value);
}
}
public unsafe void SetUniform(string name, Vector2 value)
{
if (_uniforms.TryGetValue(name, out var uid))
{
GL.Uniform2(uid, 1, (float*)&value);
}
}
public unsafe void SetUniform(string name, float value)
{
if (_uniforms.TryGetValue(name, out var uid))
{
GL.Uniform1(uid, 1, &value);
}
}
public unsafe void SetUniform(string name, bool value)
{
if (_uniforms.TryGetValue(name, out var uid))
{
// note: GLSL bool is 4 bytes large
var b = value ? 1 : 0;
GL.Uniform1(uid, 1, &b);
}
}
}
}

View File

@ -1,6 +1,6 @@
using System;
using Silk.NET.OpenGL.Legacy;
using Silk.NET.OpenGL;
namespace BizHawk.Bizware.Graphics
{

View File

@ -1,6 +1,6 @@
using System;
using Silk.NET.OpenGL.Legacy;
using Silk.NET.OpenGL;
namespace BizHawk.Bizware.Graphics
{

View File

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using BizHawk.Common.CollectionExtensions;
using Silk.NET.OpenGL.Legacy;
using Silk.NET.OpenGL;
using static SDL2.SDL;

View File

@ -4,7 +4,7 @@ using System;
#if DEBUG_OPENGL
using System.Runtime.InteropServices;
using Silk.NET.OpenGL.Legacy;
using Silk.NET.OpenGL;
#endif
using static SDL2.SDL;

View File

@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// WARNING! PLEASE SET THIS PIPELINE CURRENT BEFORE SETTING UNIFORMS IN IT! NOT TOO GREAT, I KNOW.
/// </summary>
public class Pipeline : IDisposable
{
public readonly string Memo;
public Pipeline(IGL owner, object opaque, bool available, VertexLayout vertexLayout, IReadOnlyList<UniformInfo> uniforms, string memo)
{
Memo = memo;
Owner = owner;
Opaque = opaque;
VertexLayout = vertexLayout;
Available = available;
// create the uniforms from the info list we got
if (!Available)
{
return;
}
UniformsDictionary = new(this);
foreach (var ui in uniforms)
{
UniformsDictionary[ui.Name] = new(this);
}
foreach (var ui in uniforms)
{
UniformsDictionary[ui.Name].AddUniformInfo(ui);
}
}
/// <summary>
/// Allows us to create PipelineUniforms on the fly, in case a non-existing one has been requested.
/// Shader compilers will optimize out unused uniforms, and we wont have a record of it in the uniforms population loop
/// </summary>
private class UniformWorkingDictionary : Dictionary<string, PipelineUniform>
{
public UniformWorkingDictionary(Pipeline owner)
{
Owner = owner;
}
private Pipeline Owner;
public new PipelineUniform this[string key]
{
#if true
get => this.GetValueOrPut(key, static _ => new(null));
#else
get => this.GetValueOrPut(key, static _ => new(new UniformInfo { Opaque = null }));
#endif
internal set => base[key] = value;
}
}
private readonly UniformWorkingDictionary UniformsDictionary;
private IDictionary<string, PipelineUniform> Uniforms => UniformsDictionary;
public IEnumerable<PipelineUniform> GetUniforms() => Uniforms.Values;
public PipelineUniform TryGetUniform(string name)
{
_ = Uniforms.TryGetValue(name, out var ret);
return ret;
}
public PipelineUniform this[string key] => UniformsDictionary[key];
public IGL Owner { get; }
public object Opaque { get; }
public VertexLayout VertexLayout { get; }
public bool Available { get; }
public string Errors { get; set; }
public void Dispose()
{
Owner.FreePipeline(this);
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Defines arguments for compiling a pipeline
/// </summary>
public class PipelineCompileArgs
{
/// <summary>
/// Defines an item within a vertex layout
/// This is currently restricted to float types
/// </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="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);
/// <summary>
/// Defines arguments for compiling a shader
/// This currently assumes the native shader format
/// </summary>
/// <param name="Source">Source code for the shader</param>
/// <param name="Entry">Entrypoint for this shader</param>
public readonly record struct ShaderCompileArgs(string Source, string Entry);
internal readonly IReadOnlyList<VertexLayoutItem> VertexLayout;
internal readonly int VertexLayoutStride;
internal readonly ShaderCompileArgs VertexShaderArgs;
internal readonly ShaderCompileArgs FragmentShaderArgs;
internal readonly string FragmentOutputName;
public PipelineCompileArgs(IReadOnlyList<VertexLayoutItem> vertexLayoutItems,
ShaderCompileArgs vertexShaderArgs, ShaderCompileArgs fragmentShaderArgs,
string fragmentOutputName)
{
VertexLayout = vertexLayoutItems;
foreach (var item in VertexLayout)
{
if (item.Components is < 1 or > 4)
{
throw new InvalidOperationException("A vertex layout item must have 1-4 components");
}
VertexLayoutStride += item.Components * 4;
}
VertexShaderArgs = vertexShaderArgs;
FragmentShaderArgs = fragmentShaderArgs;
FragmentOutputName = fragmentOutputName;
}
}
}

View File

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Represents a pipeline uniform...
/// and one of those can represent multiple shader uniforms!!
/// </summary>
public class PipelineUniform
{
internal PipelineUniform(Pipeline owner)
{
Owner = owner;
}
internal void AddUniformInfo(UniformInfo ui)
{
_uniformInfos.Add(ui);
}
public IEnumerable<UniformInfo> UniformInfos => _uniformInfos;
private readonly List<UniformInfo> _uniformInfos = new();
/// <returns>The first and only <see cref="UniformInfo"/>, Only relevant for OpenGL</returns>
/// <exception cref="InvalidOperationException">more than one <see cref="UniformInfo"/> exists</exception>
public UniformInfo Sole
{
get
{
if (_uniformInfos.Count != 1)
{
throw new InvalidOperationException();
}
return _uniformInfos[0];
}
}
public Pipeline Owner { get; }
public void Set(Matrix4x4 mat, bool transpose = false)
{
Owner?.Owner.SetPipelineUniformMatrix(this, mat, transpose);
}
public void Set(Vector4 vec)
{
Owner?.Owner.SetPipelineUniform(this, vec);
}
public void Set(Vector2 vec)
{
Owner?.Owner.SetPipelineUniform(this, vec);
}
public void Set(float f)
{
Owner?.Owner.SetPipelineUniform(this, f);
}
public void Set(Vector4[] vecs)
{
Owner?.Owner.SetPipelineUniform(this, vecs);
}
public void Set(ref Matrix4x4 mat, bool transpose = false)
{
Owner?.Owner.SetPipelineUniformMatrix(this, ref mat, transpose);
}
public void Set(bool value)
{
Owner?.Owner.SetPipelineUniform(this, value);
}
public void Set(ITexture2D tex)
{
Owner?.Owner.SetPipelineUniformSampler(this, tex);
}
}
}

View File

@ -47,7 +47,7 @@ namespace BizHawk.Bizware.Graphics
public void Dispose()
=> CurrentImageAttributes?.Dispose();
public void SetPipeline(Pipeline pipeline)
public void SetPipeline(IPipeline pipeline)
{
}

View File

@ -18,12 +18,6 @@ namespace BizHawk.Bizware.Graphics
{
Owner = owner;
VertexLayout = owner.CreateVertexLayout();
VertexLayout.DefineVertexAttribute("aPosition", 0, 2, VertexAttribPointerType.Float, AttribUsage.Position, false, 32);
VertexLayout.DefineVertexAttribute("aTexcoord", 1, 2, VertexAttribPointerType.Float, AttribUsage.Texcoord0, false, 32, 8);
VertexLayout.DefineVertexAttribute("aColor", 2, 4, VertexAttribPointerType.Float, AttribUsage.Texcoord1, false, 32, 16);
VertexLayout.Close();
_projection = new();
_modelView = new();
@ -44,9 +38,17 @@ namespace BizHawk.Bizware.Graphics
throw new InvalidOperationException();
}
var vs = Owner.CreateVertexShader(vsProgram, "vsmain", true);
var ps = Owner.CreateFragmentShader(psProgram, "psmain", true);
CurrPipeline = DefaultPipeline = Owner.CreatePipeline(VertexLayout, vs, ps, true, "xgui");
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.Texcoord1);
var compileArgs = new PipelineCompileArgs(
vertexLayoutItems,
vertexShaderArgs: new(vsProgram, "vsmain"),
fragmentShaderArgs: new(psProgram, "psmain"),
fragmentOutputName: "oColor");
CurrPipeline = DefaultPipeline = Owner.CreatePipeline(compileArgs);
}
private readonly Vector4[] CornerColors =
@ -80,13 +82,10 @@ namespace BizHawk.Bizware.Graphics
}
public void Dispose()
{
DefaultPipeline.Dispose();
VertexLayout.Dispose();
}
=> DefaultPipeline.Dispose();
/// <exception cref="InvalidOperationException"><see cref="IsActive"/> is <see langword="true"/></exception>
public void SetPipeline(Pipeline pipeline)
public void SetPipeline(IPipeline pipeline)
{
if (IsActive)
{
@ -115,7 +114,7 @@ namespace BizHawk.Bizware.Graphics
public void SetModulateColor(Color color)
{
Flush();
CurrPipeline["uModulateColor"].Set(new Vector4(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f));
CurrPipeline.SetUniform("uModulateColor", new Vector4(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f));
}
public void EnableBlending()
@ -168,7 +167,7 @@ namespace BizHawk.Bizware.Graphics
//clear state cache
sTexture = null;
CurrPipeline["uSamplerEnable"].Set(false);
CurrPipeline.SetUniform("uSamplerEnable", false);
ModelView.Clear();
Projection.Clear();
SetModulateColorWhite();
@ -206,19 +205,19 @@ namespace BizHawk.Bizware.Graphics
if (sTexture != tex)
{
sTexture = tex;
CurrPipeline["uSampler0"].Set(tex);
CurrPipeline["uSamplerEnable"].Set(tex != null);
CurrPipeline.SetUniformSampler("uSampler0", tex);
CurrPipeline.SetUniform("uSamplerEnable", tex != null);
}
if (_projection.IsDirty)
{
CurrPipeline["um44Projection"].Set(ref _projection.Top);
CurrPipeline.SetUniformMatrix("um44Projection", ref _projection.Top);
_projection.IsDirty = false;
}
if (_modelView.IsDirty)
{
CurrPipeline["um44Modelview"].Set(ref _modelView.Top);
CurrPipeline.SetUniformMatrix("um44Modelview", ref _modelView.Top);
_modelView.IsDirty = false;
}
}
@ -265,10 +264,8 @@ namespace BizHawk.Bizware.Graphics
public bool IsActive { get; private set; }
public IGL Owner { get; }
private readonly VertexLayout VertexLayout;
private Pipeline CurrPipeline;
private readonly Pipeline DefaultPipeline;
private IPipeline CurrPipeline;
private readonly IPipeline DefaultPipeline;
// state cache
private ITexture2D sTexture;
@ -327,17 +324,17 @@ float4 psmain(PS_INPUT src) : SV_Target
";
public const string DefaultVertexShader_gl = @"
//opengl 2.0 ~ 2004
#version 110
//opengl 3.0
#version 130
uniform mat4 um44Modelview, um44Projection;
uniform vec4 uModulateColor;
attribute vec2 aPosition;
attribute vec2 aTexcoord;
attribute vec4 aColor;
in vec2 aPosition;
in vec2 aTexcoord;
in vec4 aColor;
varying vec2 vTexcoord0;
varying vec4 vCornerColor;
out vec2 vTexcoord0;
out vec4 vCornerColor;
void main()
{
@ -348,20 +345,22 @@ void main()
}";
public const string DefaultPixelShader_gl = @"
//opengl 2.0 ~ 2004
#version 110
//opengl 3.0
#version 130
uniform bool uSamplerEnable;
uniform sampler2D uSampler0;
varying vec2 vTexcoord0;
varying vec4 vCornerColor;
in vec2 vTexcoord0;
in vec4 vCornerColor;
out vec4 oColor;
void main()
{
vec4 temp = vCornerColor;
if(uSamplerEnable) temp *= texture2D(uSampler0,vTexcoord0);
gl_FragColor = temp;
oColor = temp;
}";
}
}
}

View File

@ -64,6 +64,6 @@ namespace BizHawk.Bizware.Graphics
/// Sets the pipeline for this GuiRenderer to use. We won't keep possession of it.
/// This pipeline must work in certain ways, which can be discerned by inspecting the built-in one
/// </summary>
void SetPipeline(Pipeline pipeline);
void SetPipeline(IPipeline pipeline);
}
}

View File

@ -15,60 +15,64 @@ namespace BizHawk.Bizware.Graphics
{
Owner = owner;
VertexLayout = owner.CreateVertexLayout();
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 vertexLayoutItems = new PipelineCompileArgs.VertexLayoutItem[3];
vertexLayoutItems[0] = new("position", 4, 0, AttribUsage.Position);
vertexLayoutItems[1] = new("color", 4, 16, AttribUsage.Color0); // 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
vertexLayoutItems[2] = new("tex", 2, 32, AttribUsage.Texcoord0);
var vsSource = $"#define VERTEX\r\n{source}";
var psSource = $"#define FRAGMENT\r\n{source}";
var vs = owner.CreateVertexShader(vsSource, "main_vertex", debug);
var ps = owner.CreateFragmentShader(psSource, "main_fragment", debug);
Pipeline = Owner.CreatePipeline(VertexLayout, vs, ps, debug, "retro");
if (!Pipeline.Available)
string vsSource, psSource;
if (owner.DispMethodEnum == EDispMethod.OpenGL)
{
// make sure we release the vertex shader if it was compiled ok
if (vs.Available)
vsSource = "#version 130\r\n";
psSource = "#version 130\r\n";
}
else
{
vsSource = "";
psSource = "";
}
vsSource += $"#define VERTEX\r\n{source}";
psSource += $"#define FRAGMENT\r\n{source}";
var compileArgs = new PipelineCompileArgs(
vertexLayoutItems,
vertexShaderArgs: new(vsSource, "main_vertex"),
fragmentShaderArgs: new(psSource, "main_fragment"),
fragmentOutputName: "oColor");
try
{
Pipeline = Owner.CreatePipeline(compileArgs);
}
catch (Exception ex)
{
if (!debug)
{
vs.Release();
Errors = ex.Message;
return;
}
Available = false;
return;
throw;
}
// retroarch shaders will sometimes not have the right sampler name
// it's unclear whether we should bind to s_p or sampler0
// lets bind to sampler0 in case we don't have s_p
sampler0 = Pipeline.TryGetUniform("s_p");
if (sampler0 == null)
{
// sampler wasn't named correctly. this can happen on some retroarch shaders
foreach (var u in Pipeline.GetUniforms())
{
if (u.Sole.IsSampler && u.Sole.SamplerIndex == 0)
{
sampler0 = u;
break;
}
}
}
sampler0 = Pipeline.HasUniformSampler("s_p") ? "s_p" : Pipeline.GetUniformSamplerName(0);
// if a sampler isn't available, we can't do much, although this does interfere with debugging (shaders just returning colors will malfunction)
Available = sampler0 != null;
}
public bool Available { get; }
public string Errors => Pipeline.Errors;
public string Errors { get; }
private readonly PipelineUniform sampler0;
private readonly string sampler0;
public void Dispose()
{
Pipeline.Dispose();
VertexLayout.Dispose();
}
public void Bind()
@ -82,22 +86,22 @@ namespace BizHawk.Bizware.Graphics
// ack! make sure to set the pipeline before setting uniforms
Bind();
Pipeline["IN.video_size"].Set(new Vector2(InputSize.Width, InputSize.Height));
Pipeline["IN.texture_size"].Set(new Vector2(tex.Width, tex.Height));
Pipeline["IN.output_size"].Set(new Vector2(OutputSize.Width, OutputSize.Height));
Pipeline["IN.frame_count"].Set(1); //todo
Pipeline["IN.frame_direction"].Set(1); //todo
Pipeline.SetUniform("IN.video_size", new Vector2(InputSize.Width, InputSize.Height));
Pipeline.SetUniform("IN.texture_size", new Vector2(tex.Width, tex.Height));
Pipeline.SetUniform("IN.output_size", new Vector2(OutputSize.Width, OutputSize.Height));
Pipeline.SetUniform("IN.frame_count", 1); //todo
Pipeline.SetUniform("IN.frame_direction", 1); //todo
var Projection = Owner.CreateGuiProjectionMatrix(OutputSize);
var Modelview = Owner.CreateGuiViewMatrix(OutputSize);
var mat = Matrix4x4.Transpose(Modelview * Projection);
Pipeline["modelViewProj"].Set(mat, true);
var mat = Modelview * Projection;
Pipeline.SetUniformMatrix("modelViewProj", mat);
sampler0.Set(tex);
Pipeline.SetUniformSampler(sampler0, tex);
Owner.SetViewport(OutputSize);
var time = DateTime.Now.Second + (float)DateTime.Now.Millisecond / 1000;
Pipeline["Time"].Set(time);
Pipeline.SetUniform("Time", time);
var w = OutputSize.Width;
var h = OutputSize.Height;
@ -135,7 +139,6 @@ namespace BizHawk.Bizware.Graphics
public IGL Owner { get; }
private readonly VertexLayout VertexLayout;
public readonly Pipeline Pipeline;
public readonly IPipeline Pipeline;
}
}

View File

@ -1,41 +0,0 @@
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Represents an individual (fragment,vertex) shader.
/// It isn't IDisposable because it'll be lifecycle-managed by the IGL (disposed when all dependent pipelines are disposed)
/// But if you want to be sure to save it for later, use AddRef
/// </summary>
public class Shader
{
public Shader(IGL owner, object opaque, bool available)
{
Owner = owner;
Opaque = opaque;
Available = available;
Errors = "";
}
public IGL Owner { get; }
public object Opaque { get; }
public bool Available { get; private set; }
public string Errors { get; set; }
private int RefCount;
public void Release()
{
RefCount--;
if (RefCount <= 0)
{
Owner.Internal_FreeShader(this);
Available = false;
}
}
public void AddRef()
{
RefCount++;
}
}
}

View File

@ -1,10 +0,0 @@
namespace BizHawk.Bizware.Graphics
{
public class UniformInfo
{
public object Opaque;
public string Name;
public int SamplerIndex;
public bool IsSampler;
}
}

View File

@ -1,19 +0,0 @@
namespace BizHawk.Bizware.Graphics
{
public enum VertexAttribPointerType
{
Byte = 0x1400,
UnsignedByte = 0x1401,
Short = 0x1402,
UnsignedShort = 0x1403,
Int = 0x1404,
UnsignedInt = 0x1405,
Float = 0x1406,
Double = 0x140A,
HalfFloat = 0x140B,
Fixed = 0x140C,
UnsignedInt2101010Rev = 0x8368,
UnsignedInt10F11F11FRev = 0x8C3B,
Int2101010Rev = 0x8D9F,
}
}

View File

@ -1,70 +0,0 @@
using System;
using BizHawk.Common;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Represents a vertex layout, really a kind of a peer of the vertex and fragment shaders.
/// Only can be held by 1 pipeline at a time
/// </summary>
public class VertexLayout : IDisposable
{
public VertexLayout(IGL owner, object opaque)
{
Owner = owner;
Opaque = opaque;
Items = new();
}
public object Opaque { get; }
public IGL Owner { get; }
public void Dispose()
{
Owner.Internal_FreeVertexLayout(this);
}
/// <exception cref="InvalidOperationException">already closed (by call to <see cref="Close"/>)</exception>
public void DefineVertexAttribute(string name, int index, int components, VertexAttribPointerType attribType, AttribUsage usage, bool normalized, int stride, int offset = 0)
{
if (Closed)
{
throw new InvalidOperationException("Type is Closed and is now immutable.");
}
Items[index] = new() { Name = name, Components = components, AttribType = attribType, Usage = usage, Normalized = normalized, Stride = stride, Offset = offset };
}
/// <summary>
/// finishes this VertexLayout and renders it immutable
/// </summary>
public void Close()
{
Closed = true;
}
public class LayoutItem
{
public string Name { get; internal set; }
public int Components { get; internal set; }
public VertexAttribPointerType AttribType { get; internal set; }
public bool Normalized { get; internal set; }
public int Stride { get; internal set; }
public int Offset { get; internal set; }
public AttribUsage Usage { get; internal set; }
}
public class LayoutItemWorkingDictionary : WorkingDictionary<int, LayoutItem>
{
public new LayoutItem this[int key]
{
get => base[key];
internal set => base[key] = value;
}
}
public LayoutItemWorkingDictionary Items { get; }
private bool Closed;
}
}

View File

@ -351,7 +351,7 @@ namespace BizHawk.Client.Common.Filters
{
if (v is float value)
{
shader.Pipeline[k].Set(value);
shader.Pipeline.SetUniform(k, value);
}
}
}

View File

@ -251,12 +251,15 @@ namespace BizHawk.Client.EmuHawk
new ExceptionBox(new Exception($"Initialization of OpenGL Display Method failed; falling back to {name}")).ShowDialog();
return TryInitIGL(initialConfig.DispMethod = method);
}
var igl = new IGL_OpenGL();
// need to have a context active for checking renderer, will be disposed afterwards
using (new SDL2OpenGLContext(3, 0, false, false))
using (new SDL2OpenGLContext(3, 0, true, false))
{
return CheckRenderer(igl);
using var testIgl = new IGL_OpenGL();
_ = CheckRenderer(testIgl);
}
// don't return the same IGL, we don't want the test context to be part of this IGL
return new IGL_OpenGL();
default:
case EDispMethod.GdiPlus:
// if this fails, we're screwed