using System;
using System.Numerics;
using AcDream.App.Rendering.Wb;
using AcDream.App.UI;
using Silk.NET.OpenGL;
namespace AcDream.App.Studio;
///
/// Renders a into an off-screen FBO each frame and
/// returns the color texture handle for display in ImGui.
///
/// Pattern lifted verbatim from :
/// RGBA8 color texture + Depth24Stencil8 renderbuffer, lazily (re)created on
/// size change. The entire 2-D UI pass is sealed in a
/// so it cannot disturb the surrounding ImGui GL state.
///
/// FBO origin is bottom-left (GL convention). The caller must flip V when
/// displaying the texture in ImGui (pass uv0=(0,1), uv1=(1,0) to ImGui.Image)
/// so the image appears right-side-up in ImGui's top-left coordinate system.
///
public sealed unsafe class PanelFbo : IDisposable
{
private readonly GL _gl;
// Off-screen target — lazily (re)created when the requested size changes.
private uint _fbo;
private uint _colorTex;
private uint _depthRbo;
private int _fbW;
private int _fbH;
public PanelFbo(GL gl)
{
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
}
///
/// Render (a full draw pass) into a
/// private FBO at × pixels.
/// Returns the GL color texture handle (0 on failure). The texture is valid until
/// the next call to with a different size, or until .
///
public uint Render(int width, int height, UiHost host)
{
if (width <= 0 || height <= 0 || host is null) return 0u;
EnsureFramebuffer(width, height);
if (_fbo == 0) return 0u;
// Seal the entire pass: GLStateScope saves + restores every GL state the
// UI draw touches (viewport, blend, FBO binding, etc.) so ImGui's own state
// — set up by BeginFrame and expected intact by Render — is untouched.
using var scope = new GLStateScope(_gl);
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _fbo);
_gl.Viewport(0, 0, (uint)width, (uint)height);
_gl.Disable(EnableCap.ScissorTest);
_gl.ClearColor(0.18f, 0.18f, 0.18f, 1f); // opaque dark-grey canvas background (the FBO IS the canvas)
_gl.ClearDepth(1.0);
_gl.DepthMask(true);
_gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
host.Draw(new Vector2(width, height));
// FBO stays bound here; GLStateScope.Dispose() restores the previous binding.
return _colorTex;
}
// ── FBO lifecycle (mirrors PaperdollViewportRenderer.EnsureFramebuffer) ──────
private void EnsureFramebuffer(int width, int height)
{
if (_fbo != 0 && width == _fbW && height == _fbH) return;
DeleteFramebuffer();
_fbW = width;
_fbH = height;
_colorTex = _gl.GenTexture();
_gl.BindTexture(TextureTarget.Texture2D, _colorTex);
_gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8,
(uint)width, (uint)height, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, (void*)0);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
(int)TextureMinFilter.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
(int)TextureMinFilter.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,
(int)TextureWrapMode.ClampToEdge);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,
(int)TextureWrapMode.ClampToEdge);
_gl.BindTexture(TextureTarget.Texture2D, 0);
_depthRbo = _gl.GenRenderbuffer();
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _depthRbo);
_gl.RenderbufferStorage(RenderbufferTarget.Renderbuffer,
InternalFormat.Depth24Stencil8, (uint)width, (uint)height);
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
_fbo = _gl.GenFramebuffer();
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _fbo);
_gl.FramebufferTexture2D(FramebufferTarget.Framebuffer,
FramebufferAttachment.ColorAttachment0,
TextureTarget.Texture2D, _colorTex, 0);
_gl.FramebufferRenderbuffer(FramebufferTarget.Framebuffer,
FramebufferAttachment.DepthStencilAttachment,
RenderbufferTarget.Renderbuffer, _depthRbo);
var status = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
if (status != GLEnum.FramebufferComplete)
{
Console.WriteLine($"[studio] PanelFbo incomplete: {status} ({width}x{height})");
DeleteFramebuffer();
}
}
private void DeleteFramebuffer()
{
if (_fbo != 0) { _gl.DeleteFramebuffer(_fbo); _fbo = 0; }
if (_colorTex != 0) { _gl.DeleteTexture(_colorTex); _colorTex = 0; }
if (_depthRbo != 0) { _gl.DeleteRenderbuffer(_depthRbo); _depthRbo = 0; }
_fbW = _fbH = 0;
}
public void Dispose() => DeleteFramebuffer();
}