Run string draws at any point, rather than only at the end

fixes #4036
This commit is contained in:
CasualPokePlayer 2024-09-17 23:40:54 -07:00
parent a1d4c5e68e
commit 060670b87b
3 changed files with 116 additions and 10 deletions

View File

@ -63,6 +63,7 @@ namespace BizHawk.Bizware.Graphics
protected bool _hasClearPending;
protected Bitmap _stringOutput;
protected SDGraphics _stringGraphics;
protected ITexture2D _stringTexture;
protected IRenderTarget _renderTarget;
public ImGui2DRenderer(IGL igl, ImGuiResourceCache resourceCache)
@ -84,6 +85,8 @@ namespace BizHawk.Bizware.Graphics
ClearGCHandles();
_renderTarget?.Dispose();
_renderTarget = null;
_stringTexture?.Dispose();
_stringTexture = null;
_stringGraphics?.Dispose();
_stringGraphics = null;
_stringOutput?.Dispose();
@ -92,7 +95,7 @@ namespace BizHawk.Bizware.Graphics
_imGuiDrawList = IntPtr.Zero;
}
private void ClearStringOutput()
protected void ClearStringOutput()
{
var bmpData = _stringOutput.LockBits(new(0, 0, _stringOutput.Width, _stringOutput.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Util.UnsafeSpanFromPointer(ptr: bmpData.Scan0, length: bmpData.Stride * bmpData.Height).Clear();
@ -206,6 +209,12 @@ namespace BizHawk.Bizware.Graphics
}
else
{
// skip this draw if it's the string output draw (we execute this at arbitrary points rather)
if (userTex.Bitmap == _stringOutput)
{
continue;
}
tempTex = _igl.LoadTexture(userTex.Bitmap);
_resourceCache.SetTexture(tempTex);
if (userTex.WantCache)
@ -226,9 +235,11 @@ namespace BizHawk.Bizware.Graphics
}
case DrawCallbackId.DisableBlending:
_igl.DisableBlending();
EnableBlending = false;
break;
case DrawCallbackId.EnableBlendAlpha:
_igl.EnableBlendAlpha();
EnableBlending = true;
break;
case DrawCallbackId.EnableBlendNormal:
_igl.EnableBlendNormal();
@ -239,6 +250,38 @@ namespace BizHawk.Bizware.Graphics
var brush = _resourceCache.BrushCache.GetValueOrPutNew1(stringArgs.Color);
_stringGraphics.TextRenderingHint = stringArgs.TextRenderingHint;
_stringGraphics.DrawString(stringArgs.Str, stringArgs.Font, brush, stringArgs.X, stringArgs.Y, stringArgs.Format);
// now draw the string graphics, if the next command is not another draw string command
if ((DrawCallbackId)cmdBuffer[i + 1].UserCallback != DrawCallbackId.DrawString)
{
var lastCmd = cmdBuffer[cmdBuffer.Size - 1];
var texId = lastCmd.GetTexID();
// last command must be for drawing the string output bitmap
var userTex = (ImGuiUserTexture)GCHandle.FromIntPtr(texId).Target!;
if (userTex.Bitmap != _stringOutput)
{
throw new InvalidOperationException("Unexpected bitmap mismatch!");
}
// we want to normal blend the text image rather than alpha blend it (matches GDI+ behavior it seems?)
if (EnableBlending)
{
_igl.EnableBlendNormal();
}
_stringTexture.LoadFrom(new BitmapBuffer(userTex.Bitmap, new()));
_resourceCache.SetTexture(_stringTexture);
_igl.DrawIndexed((int)lastCmd.ElemCount, (int)lastCmd.IdxOffset, (int)lastCmd.VtxOffset);
if (EnableBlending)
{
_igl.EnableBlendAlpha();
}
ClearStringOutput();
}
break;
}
default:
@ -263,10 +306,12 @@ namespace BizHawk.Bizware.Graphics
|| _stringOutput.Width != width
|| _stringOutput.Height != height))
{
_stringTexture?.Dispose();
_stringGraphics?.Dispose();
_stringOutput?.Dispose();
_stringOutput = new(width, height, PixelFormat.Format32bppArgb);
_stringGraphics = SDGraphics.FromImage(_stringOutput);
_stringTexture = _igl.CreateTexture(width, height);
}
_renderTarget.Bind();
@ -283,13 +328,18 @@ namespace BizHawk.Bizware.Graphics
if (_hasDrawStringCommand)
{
ClearStringOutput();
// synthesize an add image command for our string bitmap
if (!_pendingBlendEnable)
{
// always normal blend the string (it covers the entire image, if it was alpha that'd obscure everything else)
_imGuiDrawList.AddCallback((IntPtr)DrawCallbackId.EnableBlendNormal, IntPtr.Zero);
}
DrawImage(_stringOutput, 0, 0);
// we have to do this now as the string bitmap isn't properly created until here
// however, the position in the command list (the end) will be a lie
// we'll rather just reuse the command at the end when we need to draw graphics
var texture = new ImGuiUserTexture { Bitmap = _stringOutput, WantCache = false };
var handle = GCHandle.Alloc(texture, GCHandleType.Normal);
_gcHandles.Add(handle);
_imGuiDrawList.AddImage(
user_texture_id: GCHandle.ToIntPtr(handle),
p_min: new(0, 0),
p_max: new(_stringOutput.Width, _stringOutput.Height));
}
_imGuiDrawList._PopUnusedDrawCmd();
@ -309,6 +359,13 @@ namespace BizHawk.Bizware.Graphics
public void Discard()
=> ResetDrawList();
protected enum BlendState
{
NoBlending,
AlphaBlending,
NormalBlending,
}
protected bool EnableBlending { get; private set; }
private bool _pendingBlendEnable;

View File

@ -131,6 +131,12 @@ namespace BizHawk.Bizware.Graphics
if (texId != IntPtr.Zero)
{
var userTex = (ImGuiUserTexture)GCHandle.FromIntPtr(texId).Target!;
// skip this draw if it's the string output draw (we execute this at arbitrary points rather)
if (userTex.Bitmap == _stringOutput)
{
continue;
}
var texBmpData = userTex.Bitmap.LockBits(
new(0, 0, userTex.Bitmap.Width, userTex.Bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try
@ -176,6 +182,49 @@ namespace BizHawk.Bizware.Graphics
var brush = _resourceCache.BrushCache.GetValueOrPutNew1(stringArgs.Color);
_stringGraphics.TextRenderingHint = stringArgs.TextRenderingHint;
_stringGraphics.DrawString(stringArgs.Str, stringArgs.Font, brush, stringArgs.X, stringArgs.Y, stringArgs.Format);
// now draw the string graphics, if the next command is not another draw string command
if (i == cmdBuffer.Size
|| (DrawCallbackId)cmdBuffer[i + 1].UserCallback != DrawCallbackId.DrawString)
{
var lastCmd = cmdBuffer[cmdBuffer.Size - 1];
var texId = lastCmd.GetTexID();
// last command must be for drawing the string output bitmap
var userTex = (ImGuiUserTexture)GCHandle.FromIntPtr(texId).Target!;
if (userTex.Bitmap != _stringOutput)
{
throw new InvalidOperationException("Unexpected bitmap mismatch!");
}
var texBmpData = _stringOutput.LockBits(
new(0, 0, _stringOutput.Width, _stringOutput.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try
{
using var texSurf = new SDLSurface(texBmpData);
var sdlTex = SDL_CreateTextureFromSurface(sdlRenderer, texSurf.Surface);
if (sdlTex == IntPtr.Zero)
{
throw new($"Failed to create SDL texture from surface, SDL error: {SDL_GetError()}");
}
try
{
RenderCommand(sdlRenderer, sdlTex, _imGuiDrawList, lastCmd);
}
finally
{
SDL_DestroyTexture(sdlTex);
}
}
finally
{
_stringOutput.UnlockBits(texBmpData);
}
ClearStringOutput();
}
break;
}
default:

View File

@ -250,7 +250,7 @@ namespace BizHawk.Client.Common
=> APIs.Gui.DrawRectangle(x, y, width, height, _th.SafeParseColor(line), _th.SafeParseColor(background), surfaceID: UseOrFallback(surfaceName));
[LuaMethodExample("gui.drawString( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")]
[LuaMethod("drawString", "Draws the given message in the emulator screen space (like all draw functions) at the given x,y coordinates and the given color. The default color is white. A fontfamily can be specified and is monospace generic if none is specified (font family options are the same as the .NET FontFamily class). The fontsize default is 12. The default font style is regular. Font style options are regular, bold, italic, strikethrough, underline. Horizontal alignment options are left (default), center, or right. Vertical alignment options are bottom (default), middle, or top. Alignment options specify which ends of the text will be drawn at the x and y coordinates. For pixel-perfect font look, make sure to disable aspect ratio correction. Note that all text drawing occurs after all other drawing occurs (e.g. after gui.drawRectangle() regardless of call order).")]
[LuaMethod("drawString", "Draws the given message in the emulator screen space (like all draw functions) at the given x,y coordinates and the given color. The default color is white. A fontfamily can be specified and is monospace generic if none is specified (font family options are the same as the .NET FontFamily class). The fontsize default is 12. The default font style is regular. Font style options are regular, bold, italic, strikethrough, underline. Horizontal alignment options are left (default), center, or right. Vertical alignment options are bottom (default), middle, or top. Alignment options specify which ends of the text will be drawn at the x and y coordinates. For pixel-perfect font look, make sure to disable aspect ratio correction.")]
public void DrawString(
int x,
int y,
@ -305,7 +305,7 @@ namespace BizHawk.Client.Common
surfaceName: surfaceName);
[LuaMethodExample("gui.pixelText( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, \"Arial Narrow\" );")]
[LuaMethod("pixelText", "Draws the given message in the emulator screen space (like all draw functions) at the given x,y coordinates and the given color. The default color is white. Two font families are available, \"fceux\" and \"gens\" (or \"0\" and \"1\" respectively), both are monospace and have the same size as in the emulators they've been taken from. If no font family is specified, it uses \"gens\" font, unless that's overridden via gui.defaultPixelFont(). Note that all text drawing occurs after all other drawing occurs (e.g. after gui.drawRectangle() regardless of call order).")]
[LuaMethod("pixelText", "Draws the given message in the emulator screen space (like all draw functions) at the given x,y coordinates and the given color. The default color is white. Two font families are available, \"fceux\" and \"gens\" (or \"0\" and \"1\" respectively), both are monospace and have the same size as in the emulators they've been taken from. If no font family is specified, it uses \"gens\" font, unless that's overridden via gui.defaultPixelFont().")]
public void PixelText(
int x,
int y,
@ -317,7 +317,7 @@ namespace BizHawk.Client.Common
=> APIs.Gui.PixelText(x, y, message, _th.SafeParseColor(forecolor), _th.SafeParseColor(backcolor) ?? APIs.Gui.GetDefaultTextBackground(), fontfamily, surfaceID: UseOrFallback(surfaceName));
[LuaMethodExample("gui.text( 16, 32, \"Some message\", 0x7F0000FF, \"bottomleft\" );")]
[LuaMethod("text", "Displays the given text on the screen at the given coordinates. Optional Foreground color. The optional anchor flag anchors the text to one of the four corners. Anchor flag parameters: topleft, topright, bottomleft, bottomright. This function is generally much faster than other text drawing functions, at the cost of customization. Note that all text drawing occurs after all other drawing occurs (e.g. after gui.drawRectangle() regardless of call order).")]
[LuaMethod("text", "Displays the given text on the screen at the given coordinates. Optional Foreground color. The optional anchor flag anchors the text to one of the four corners. Anchor flag parameters: topleft, topright, bottomleft, bottomright. This function is generally much faster than other text drawing functions, at the cost of customization.")]
public void Text(
int x,
int y,