//TODO //we could flag textures as 'actually' render targets (keep a reference to the render target?) which could allow us to convert between them more quickly in some cases using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Windows.Forms; using BizHawk.Bizware.BizwareGL; using BizHawk.Bizware.BizwareGL.Drivers.SlimDX; using BizHawk.Bizware.BizwareGL.Drivers.OpenTK; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk.FilterManager; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Sony.PSX; using OpenTK; namespace BizHawk.Client.EmuHawk { /// /// A DisplayManager is destined forevermore to drive the PresentationPanel it gets initialized with. /// Its job is to receive OSD and emulator outputs, and produce one single buffer (BitmapBuffer? Texture2d?) for display by the PresentationPanel. /// Details TBD /// public class DisplayManager : IDisposable { private class DisplayManagerRenderTargetProvider : IRenderTargetProvider { private readonly Func _callback; RenderTarget IRenderTargetProvider.Get(Size size) { return _callback(size); } public DisplayManagerRenderTargetProvider(Func callback) { _callback = callback; } } public DisplayManager(PresentationPanel presentationPanel) { GL = GlobalWin.GL; GLManager = GlobalWin.GLManager; this.presentationPanel = presentationPanel; GraphicsControl = this.presentationPanel.GraphicsControl; CR_GraphicsControl = GLManager.GetContextForGraphicsControl(GraphicsControl); // it's sort of important for these to be initialized to something nonzero currEmuWidth = currEmuHeight = 1; Renderer = GL.CreateRenderer(); VideoTextureFrugalizer = new TextureFrugalizer(GL); ShaderChainFrugalizers = new RenderTargetFrugalizer[16]; // hacky hardcoded limit.. need some other way to manage these for (int i = 0; i < 16; i++) { ShaderChainFrugalizers[i] = new RenderTargetFrugalizer(GL); } using (var xml = typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Client.EmuHawk.Resources.courier16px.fnt")) { using var tex = typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Client.EmuHawk.Resources.courier16px_0.png"); TheOneFont = new StringRenderer(GL, xml, tex); } using (var gens = typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Client.EmuHawk.Resources.gens.ttf")) { LoadCustomFont(gens); } using (var fceux = typeof(Program).Assembly.GetManifestResourceStream("BizHawk.Client.EmuHawk.Resources.fceux.ttf")) { LoadCustomFont(fceux); } if (GL is IGL_TK || GL is IGL_SlimDX9) { var fiHq2x = new FileInfo(Path.Combine(PathManager.GetExeDirectoryAbsolute(), "Shaders/BizHawk/hq2x.cgp")); if (fiHq2x.Exists) { using var stream = fiHq2x.OpenRead(); ShaderChain_hq2x = new Filters.RetroShaderChain(GL, new Filters.RetroShaderPreset(stream), Path.Combine(PathManager.GetExeDirectoryAbsolute(), "Shaders/BizHawk")); } var fiScanlines = new FileInfo(Path.Combine(PathManager.GetExeDirectoryAbsolute(), "Shaders/BizHawk/BizScanlines.cgp")); if (fiScanlines.Exists) { using var stream = fiScanlines.OpenRead(); ShaderChain_scanlines = new Filters.RetroShaderChain(GL, new Filters.RetroShaderPreset(stream), Path.Combine(PathManager.GetExeDirectoryAbsolute(), "Shaders/BizHawk")); } string bicubicPath = "Shaders/BizHawk/bicubic-fast.cgp"; if (GL is IGL_SlimDX9) { bicubicPath = "Shaders/BizHawk/bicubic-normal.cgp"; } var fiBicubic = new FileInfo(Path.Combine(PathManager.GetExeDirectoryAbsolute(), bicubicPath)); if (fiBicubic.Exists) { using var stream = fiBicubic.Open(FileMode.Open, FileAccess.Read, FileShare.Read); ShaderChain_bicubic = new Filters.RetroShaderChain(GL, new Filters.RetroShaderPreset(stream), Path.Combine(PathManager.GetExeDirectoryAbsolute(), "Shaders/BizHawk")); } } LuaSurfaceSets["emu"] = new SwappableDisplaySurfaceSet(); LuaSurfaceSets["native"] = new SwappableDisplaySurfaceSet(); LuaSurfaceFrugalizers["emu"] = new TextureFrugalizer(GL); LuaSurfaceFrugalizers["native"] = new TextureFrugalizer(GL); RefreshUserShader(); } public bool Disposed { get; private set; } public void Dispose() { if (Disposed) return; Disposed = true; VideoTextureFrugalizer.Dispose(); foreach (var f in LuaSurfaceFrugalizers.Values) { f.Dispose(); } foreach (var f in ShaderChainFrugalizers) { f?.Dispose(); } foreach (var s in new[] { ShaderChain_hq2x, ShaderChain_scanlines, ShaderChain_bicubic, ShaderChain_user }) { s?.Dispose(); } TheOneFont.Dispose(); Renderer.Dispose(); } // rendering resources: private readonly IGL GL; private readonly GLManager GLManager; private readonly StringRenderer TheOneFont; private readonly IGuiRenderer Renderer; // layer resources private readonly PresentationPanel presentationPanel; // well, its the final layer's target, at least private readonly GraphicsControl GraphicsControl; // well, its the final layer's target, at least private readonly GLManager.ContextRef CR_GraphicsControl; private FilterProgram _currentFilterProgram; /// /// these variables will track the dimensions of the last frame's (or the next frame? this is confusing) emulator native output size /// THIS IS OLD JUNK. I should get rid of it, I think. complex results from the last filter ingestion should be saved instead. /// int currEmuWidth, currEmuHeight; /// /// additional pixels added at the unscaled level for the use of lua drawing. essentially increases the input video provider dimensions /// public Padding GameExtraPadding { get; set; } /// /// additional pixels added at the native level for the use of lua drawing. essentially just gets tacked onto the final calculated window sizes. /// public Padding ClientExtraPadding { get; set; } /// /// custom fonts that don't need to be installed on the user side /// public System.Drawing.Text.PrivateFontCollection CustomFonts = new System.Drawing.Text.PrivateFontCollection(); private readonly TextureFrugalizer VideoTextureFrugalizer; private readonly Dictionary LuaSurfaceFrugalizers = new Dictionary(); private readonly RenderTargetFrugalizer[] ShaderChainFrugalizers; private readonly Filters.RetroShaderChain ShaderChain_hq2x, ShaderChain_scanlines, ShaderChain_bicubic; private Filters.RetroShaderChain ShaderChain_user; public void RefreshUserShader() { ShaderChain_user?.Dispose(); if (File.Exists(Global.Config.DispUserFilterPath)) { var fi = new FileInfo(Global.Config.DispUserFilterPath); using var stream = fi.OpenRead(); ShaderChain_user = new Filters.RetroShaderChain(GL, new Filters.RetroShaderPreset(stream), Path.GetDirectoryName(Global.Config.DispUserFilterPath)); } } System.Windows.Forms.Padding CalculateCompleteContentPadding(bool user, bool source) { var padding = new System.Windows.Forms.Padding(); if (user) { padding += GameExtraPadding; } // an experimental feature if (source && Global.Emulator is Octoshock psx) { var corePadding = psx.VideoProvider_Padding; padding.Left += corePadding.Width / 2; padding.Right += corePadding.Width - corePadding.Width / 2; padding.Top += corePadding.Height / 2; padding.Bottom += corePadding.Height - corePadding.Height / 2; } // apply user's crop selections as a negative padding (believe it or not, this largely works) // is there an issue with the aspect ratio? I don't know--but if there is, there would be with the padding too padding.Left -= Global.Config.DispCropLeft; padding.Right -= Global.Config.DispCropRight; padding.Top -= Global.Config.DispCropTop; padding.Bottom -= Global.Config.DispCropBottom; return padding; } FilterProgram BuildDefaultChain(Size chainInSize, Size chainOutSize, bool includeOSD) { // select user special FX shader chain var selectedChainProperties = new Dictionary(); Filters.RetroShaderChain selectedChain = null; if (Global.Config.TargetDisplayFilter == 1 && ShaderChain_hq2x != null && ShaderChain_hq2x.Available) { selectedChain = ShaderChain_hq2x; } if (Global.Config.TargetDisplayFilter == 2 && ShaderChain_scanlines != null && ShaderChain_scanlines.Available) { selectedChain = ShaderChain_scanlines; selectedChainProperties["uIntensity"] = 1.0f - Global.Config.TargetScanlineFilterIntensity / 256.0f; } if (Global.Config.TargetDisplayFilter == 3 && ShaderChain_user != null && ShaderChain_user.Available) { selectedChain = ShaderChain_user; } var fPresent = new Filters.FinalPresentation(chainOutSize); var fInput = new Filters.SourceImage(chainInSize); var fOSD = new Filters.OSD(); fOSD.RenderCallback = () => { if (!includeOSD) { return; } var size = fOSD.FindInput().SurfaceFormat.Size; Renderer.Begin(size.Width, size.Height); var myBlitter = new MyBlitter(this) { ClipBounds = new Rectangle(0, 0, size.Width, size.Height) }; Renderer.SetBlendState(GL.BlendNormal); GlobalWin.OSD.Begin(myBlitter); GlobalWin.OSD.DrawScreenInfo(myBlitter); GlobalWin.OSD.DrawMessages(myBlitter); Renderer.End(); }; var chain = new FilterProgram(); //add the first filter, encompassing output from the emulator core chain.AddFilter(fInput, "input"); // if a non-zero padding is required, add a filter to allow for that // note, we have two sources of padding right now.. one can come from the VideoProvider and one from the user. // we're combining these now and just using black, for sake of being lean, despite the discussion below: // keep in mind, the VideoProvider design in principle might call for another color. // we haven't really been using this very hard, but users will probably want black there (they could fill it to another color if needed tho) var padding = CalculateCompleteContentPadding(true, true); if (padding.Vertical != 0 || padding.Horizontal != 0) { // TODO - add another filter just for this, its cumbersome to use final presentation... I think. but maybe there's enough similarities to justify it. Size size = chainInSize; size.Width += padding.Horizontal; size.Height += padding.Vertical; Filters.FinalPresentation fPadding = new Filters.FinalPresentation(size); chain.AddFilter(fPadding, "padding"); fPadding.GuiRenderer = Renderer; fPadding.GL = GL; fPadding.Config_PadOnly = true; fPadding.Padding = padding; } //add lua layer 'emu' AppendLuaLayer(chain, "emu"); if (Global.Config.DispPrescale != 1) { var fPrescale = new Filters.PrescaleFilter() { Scale = Global.Config.DispPrescale }; chain.AddFilter(fPrescale, "user_prescale"); } // add user-selected retro shader if (selectedChain != null) AppendRetroShaderChain(chain, "retroShader", selectedChain, selectedChainProperties); // AutoPrescale makes no sense for a None final filter if (Global.Config.DispAutoPrescale && Global.Config.DispFinalFilter != (int)Filters.FinalPresentation.eFilterOption.None) { var apf = new Filters.AutoPrescaleFilter(); chain.AddFilter(apf, "auto_prescale"); } //choose final filter var finalFilter = Filters.FinalPresentation.eFilterOption.None; if (Global.Config.DispFinalFilter == 1) { finalFilter = Filters.FinalPresentation.eFilterOption.Bilinear; } if (Global.Config.DispFinalFilter == 2) { finalFilter = Filters.FinalPresentation.eFilterOption.Bicubic; } //if bicubic is selected and unavailable, don't use it. use bilinear instead I guess if (finalFilter == Filters.FinalPresentation.eFilterOption.Bicubic) { if (ShaderChain_bicubic == null || !ShaderChain_bicubic.Available) { finalFilter = Filters.FinalPresentation.eFilterOption.Bilinear; } } fPresent.FilterOption = finalFilter; // now if bicubic is chosen, insert it if (finalFilter == Filters.FinalPresentation.eFilterOption.Bicubic) { AppendRetroShaderChain(chain, "bicubic", ShaderChain_bicubic, null); } // add final presentation chain.AddFilter(fPresent, "presentation"); //add lua layer 'native' AppendLuaLayer(chain, "native"); // and OSD goes on top of that // TODO - things break if this isn't present (the final presentation filter gets messed up when used with prescaling) // so, always include it (we'll handle this flag in the callback to do no rendering) //if (includeOSD) chain.AddFilter(fOSD, "osd"); return chain; } void AppendRetroShaderChain(FilterProgram program, string name, Filters.RetroShaderChain retroChain, Dictionary properties) { for (int i = 0; i < retroChain.Passes.Length; i++) { var pass = retroChain.Passes[i]; var rsp = new Filters.RetroShaderPass(retroChain, i); string fname = $"{name}[{i}]"; program.AddFilter(rsp, fname); rsp.Parameters = properties; } } void AppendLuaLayer(FilterProgram chain, string name) { var luaNativeSurface = LuaSurfaceSets[name].GetCurrent(); if (luaNativeSurface == null) { return; } Texture2d luaNativeTexture = LuaSurfaceFrugalizers[name].Get(luaNativeSurface); var fLuaLayer = new Filters.LuaLayer(); fLuaLayer.SetTexture(luaNativeTexture); chain.AddFilter(fLuaLayer, name); } /// /// Using the current filter program, turn a mouse coordinate from window space to the original emulator screen space. /// public Point UntransformPoint(Point p) { // first, turn it into a window coordinate p = presentationPanel.Control.PointToClient(p); // now, if there's no filter program active, just give up if (_currentFilterProgram == null) return p; // otherwise, have the filter program untransform it Vector2 v = new Vector2(p.X, p.Y); v = _currentFilterProgram.UntransformPoint("default", v); return new Point((int)v.X, (int)v.Y); } /// /// Using the current filter program, turn a emulator screen space coordinate to a window coordinate (suitable for lua layer drawing) /// public Point TransformPoint(Point p) { //now, if there's no filter program active, just give up if (_currentFilterProgram == null) { return p; } // otherwise, have the filter program untransform it Vector2 v = new Vector2(p.X, p.Y); v = _currentFilterProgram.TransformPoint("default", v); return new Point((int)v.X, (int)v.Y); } /// /// This will receive an emulated output frame from an IVideoProvider and run it through the complete frame processing pipeline /// Then it will stuff it into the bound PresentationPanel. /// --- /// If the int[] is size=1, then it contains an openGL texture ID (and the size should be as specified from videoProvider) /// Don't worry about the case where the frontend isnt using opengl; DisplayManager deals with it /// public void UpdateSource(IVideoProvider videoProvider) { bool displayNothing = Global.Config.DispSpeedupFeatures == 0; var job = new JobInfo { VideoProvider = videoProvider, Simulate = displayNothing, ChainOutsize = GraphicsControl.Size, IncludeOSD = true, }; UpdateSourceInternal(job); } public BitmapBuffer RenderVideoProvider(IVideoProvider videoProvider) { // TODO - we might need to gather more Global.Config.DispXXX properties here, so they can be overridden var targetSize = new Size(videoProvider.BufferWidth, videoProvider.BufferHeight); var padding = CalculateCompleteContentPadding(true,true); targetSize.Width += padding.Horizontal; targetSize.Height += padding.Vertical; var job = new JobInfo { VideoProvider = videoProvider, Simulate = false, ChainOutsize = targetSize, Offscreen = true, IncludeOSD = false }; UpdateSourceInternal(job); return job.OffscreenBb; } /// /// Does the entire display process to an offscreen buffer, suitable for a 'client' screenshot. /// public BitmapBuffer RenderOffscreen(IVideoProvider videoProvider, bool includeOSD) { var job = new JobInfo { VideoProvider = videoProvider, Simulate = false, ChainOutsize = GraphicsControl.Size, Offscreen = true, IncludeOSD = includeOSD }; UpdateSourceInternal(job); return job.OffscreenBb; } private class FakeVideoProvider : IVideoProvider { public int[] GetVideoBuffer() => new int[] {}; public int VirtualWidth { get; set; } public int VirtualHeight { get; set; } public int BufferWidth { get; set; } public int BufferHeight { get; set; } public int BackgroundColor { get; set; } public int VsyncNumerator => throw new InvalidOperationException(); public int VsyncDenominator => throw new InvalidOperationException(); } void FixRatio(float x, float y, int inw, int inh, out int outW, out int outH) { float ratio = x / y; if (ratio <= 1) { // taller. weird. expand height. outW = inw; outH = (int)(inw / ratio); } else { // wider. normal. expand width. outW = (int)(inh * ratio); outH = inh; } } /// /// Attempts to calculate a good client size with the given zoom factor, considering the user's DisplayManager preferences /// TODO - this needs to be redone with a concept different from zoom factor. /// basically, each increment of a 'zoom-like' factor should definitely increase the viewable area somehow, even if it isnt strictly by an entire zoom level. /// public Size CalculateClientSize(IVideoProvider videoProvider, int zoom) { bool arActive = Global.Config.DispFixAspectRatio; bool arSystem = Global.Config.DispManagerAR == Config.EDispManagerAR.System; bool arCustom = Global.Config.DispManagerAR == Config.EDispManagerAR.Custom; bool arCustomRatio = Global.Config.DispManagerAR == Config.EDispManagerAR.CustomRatio; bool arCorrect = arSystem || arCustom || arCustomRatio; bool arInteger = Global.Config.DispFixScaleInteger; int bufferWidth = videoProvider.BufferWidth; int bufferHeight = videoProvider.BufferHeight; int virtualWidth = videoProvider.VirtualWidth; int virtualHeight = videoProvider.VirtualHeight; if (arCustom) { virtualWidth = Global.Config.DispCustomUserARWidth; virtualHeight = Global.Config.DispCustomUserARHeight; } if (arCustomRatio) { FixRatio(Global.Config.DispCustomUserARX, Global.Config.DispCustomUserARY, videoProvider.BufferWidth, videoProvider.BufferHeight, out virtualWidth, out virtualHeight); } var padding = CalculateCompleteContentPadding(true, false); virtualWidth += padding.Horizontal; virtualHeight += padding.Vertical; padding = CalculateCompleteContentPadding(true, true); bufferWidth += padding.Horizontal; bufferHeight += padding.Vertical; // old stuff var fvp = new FakeVideoProvider { BufferWidth = bufferWidth, BufferHeight = bufferHeight, VirtualWidth = virtualWidth, VirtualHeight = virtualHeight }; Size chainOutsize; if (arActive) { if (arCorrect) { if (arInteger) { // ALERT COPYPASTE LAUNDROMAT Vector2 VS = new Vector2(virtualWidth, virtualHeight); Vector2 BS = new Vector2(bufferWidth, bufferHeight); Vector2 AR = Vector2.Divide(VS, BS); float targetPar = AR.X / AR.Y; // this would malfunction for AR <= 0.5 or AR >= 2.0 // EDIT - in fact, we have AR like that coming from PSX, sometimes, so maybe we should solve this better Vector2 PS = new Vector2(1, 1); // here's how we define zooming, in this case: // make sure each step is an increment of zoom for at least one of the dimensions (or maybe both of them) // look for the increment which helps the AR the best //TODO - this cant possibly support scale factors like 1.5x //TODO - also, this might be messing up zooms and stuff, we might need to run this on the output size of the filter chain for (int i = 1; i < zoom;i++) { //would not be good to run this per frame, but it seems to only run when the resolution changes, etc. Vector2[] trials = { PS + new Vector2(1, 0), PS + new Vector2(0, 1), PS + new Vector2(1, 1) }; int bestIndex = -1; float bestValue = 1000.0f; for (int t = 0; t < trials.Length; t++) { //I. float testAr = trials[t].X / trials[t].Y; // II. //Vector2 calc = Vector2.Multiply(trials[t], VS); //float test_ar = calc.X / calc.Y; // not clear which approach is superior float deviationLinear = Math.Abs(testAr - targetPar); float deviationGeom = testAr / targetPar; if (deviationGeom < 1) { deviationGeom = 1.0f / deviationGeom; } float value = deviationLinear; if (value < bestValue) { bestIndex = t; bestValue = value; } } // is it possible to get here without selecting one? doubtful. // EDIT: YES IT IS. it happened with an 0,0 buffer size. of course, that was a mistake, but we shouldn't crash if (bestIndex != -1) // so, what now? well, this will result in 0,0 getting picked, so that's probably all we can do { PS = trials[bestIndex]; } } chainOutsize = new Size((int)(bufferWidth * PS.X), (int)(bufferHeight * PS.Y)); } else { // obey the AR, but allow free scaling: just zoom the virtual size chainOutsize = new Size(virtualWidth * zoom, virtualHeight * zoom); } } else { // ar_unity: // just choose to zoom the buffer (make no effort to incorporate AR) chainOutsize = new Size(bufferWidth * zoom, bufferHeight * zoom); } } else { // !ar_active: // just choose to zoom the buffer (make no effort to incorporate AR) chainOutsize = new Size(bufferWidth * zoom, bufferHeight * zoom); } chainOutsize.Width += ClientExtraPadding.Horizontal; chainOutsize.Height += ClientExtraPadding.Vertical; var job = new JobInfo { VideoProvider = fvp, Simulate = true, ChainOutsize = chainOutsize, }; var filterProgram = UpdateSourceInternal(job); // this only happens when we're forcing the client to size itself with autoload and the core says 0x0.... // we need some other more sensible client size. if (filterProgram == null) { return new Size(256, 192); } var size = filterProgram.Filters[filterProgram.Filters.Count - 1].FindOutput().SurfaceFormat.Size; return size; } private class JobInfo { public IVideoProvider VideoProvider; public bool Simulate; public Size ChainOutsize; public bool Offscreen; public BitmapBuffer OffscreenBb; public bool IncludeOSD; } private FilterProgram UpdateSourceInternal(JobInfo job) { //no drawing actually happens. it's important not to begin drawing on a control if (!job.Simulate && !job.Offscreen) { GLManager.Activate(CR_GraphicsControl); if (job.ChainOutsize.Width == 0 || job.ChainOutsize.Height == 0) { // this has to be a NOP, because lots of stuff will malfunction on a 0-sized viewport if (_currentFilterProgram != null) { UpdateSourceDrawingWork(job); //but we still need to do this, because of vsync } return null; } } IVideoProvider videoProvider = job.VideoProvider; bool simulate = job.Simulate; Size chainOutsize = job.ChainOutsize; //simulate = true; int vw = videoProvider.BufferWidth; int vh = videoProvider.BufferHeight; if (Global.Config.DispFixAspectRatio) { if (Global.Config.DispManagerAR == Config.EDispManagerAR.System) { vw = videoProvider.VirtualWidth; vh = videoProvider.VirtualHeight; } if (Global.Config.DispManagerAR == Config.EDispManagerAR.Custom) { vw = Global.Config.DispCustomUserARWidth; vh = Global.Config.DispCustomUserARHeight; } if (Global.Config.DispManagerAR == Config.EDispManagerAR.CustomRatio) { FixRatio(Global.Config.DispCustomUserARX, Global.Config.DispCustomUserARY, videoProvider.BufferWidth, videoProvider.BufferHeight, out vw, out vh); } } var padding = CalculateCompleteContentPadding(true,false); vw += padding.Horizontal; vh += padding.Vertical; int[] videoBuffer = videoProvider.GetVideoBuffer(); int bufferWidth = videoProvider.BufferWidth; int bufferHeight = videoProvider.BufferHeight; bool isGlTextureId = videoBuffer.Length == 1; BitmapBuffer bb = null; Texture2d videoTexture = null; if (!simulate) { if (isGlTextureId) { //FYI: this is a million years from happening on n64, since it's all geriatric non-FBO code //is it workable for saturn? videoTexture = GL.WrapGLTexture2d(new IntPtr(videoBuffer[0]), bufferWidth, bufferHeight); } else { //wrap the VideoProvider data in a BitmapBuffer (no point to refactoring that many IVideoProviders) bb = new BitmapBuffer(bufferWidth, bufferHeight, videoBuffer); bb.DiscardAlpha(); //now, acquire the data sent from the videoProvider into a texture videoTexture = VideoTextureFrugalizer.Get(bb); // lets not use this. lets define BizwareGL to make clamp by default (TBD: check opengl) //GL.SetTextureWrapMode(videoTexture, true); } } // record the size of what we received, since lua and stuff is gonna want to draw onto it currEmuWidth = bufferWidth; currEmuHeight = bufferHeight; //build the default filter chain and set it up with services filters will need Size chainInsize = new Size(bufferWidth, bufferHeight); var filterProgram = BuildDefaultChain(chainInsize, chainOutsize, job.IncludeOSD); filterProgram.GuiRenderer = Renderer; filterProgram.GL = GL; //setup the source image filter Filters.SourceImage fInput = filterProgram["input"] as Filters.SourceImage; fInput.Texture = videoTexture; //setup the final presentation filter Filters.FinalPresentation fPresent = filterProgram["presentation"] as Filters.FinalPresentation; fPresent.VirtualTextureSize = new Size(vw, vh); fPresent.TextureSize = new Size(bufferWidth, bufferHeight); fPresent.BackgroundColor = videoProvider.BackgroundColor; fPresent.GuiRenderer = Renderer; fPresent.Flip = isGlTextureId; fPresent.Config_FixAspectRatio = Global.Config.DispFixAspectRatio; fPresent.Config_FixScaleInteger = Global.Config.DispFixScaleInteger; fPresent.Padding = ClientExtraPadding; fPresent.AutoPrescale = Global.Config.DispAutoPrescale; fPresent.GL = GL; filterProgram.Compile("default", chainInsize, chainOutsize, !job.Offscreen); if (simulate) { } else { _currentFilterProgram = filterProgram; UpdateSourceDrawingWork(job); } // cleanup: bb?.Dispose(); return filterProgram; } public void Blank() { GLManager.Activate(CR_GraphicsControl); GL.BeginScene(); GL.BindRenderTarget(null); GL.SetClearColor(Color.Black); GL.Clear(OpenTK.Graphics.OpenGL.ClearBufferMask.ColorBufferBit); GL.EndScene(); presentationPanel.GraphicsControl.SwapBuffers(); } void UpdateSourceDrawingWork(JobInfo job) { bool alternateVsync = false; // only used by alternate vsync IGL_SlimDX9 dx9 = null; if (!job.Offscreen) { //apply the vsync setting (should probably try to avoid repeating this) var vsync = Global.Config.VSyncThrottle || Global.Config.VSync; //ok, now this is a bit undesirable. //maybe the user wants vsync, but not vsync throttle. //this makes sense... but we don't have the infrastructure to support it now (we'd have to enable triple buffering or something like that) //so what we're gonna do is disable vsync no matter what if throttling is off, and maybe nobody will notice. //update 26-mar-2016: this upsets me. When fast-forwarding and skipping frames, vsync should still work. But I'm not changing it yet if (Global.DisableSecondaryThrottling) vsync = false; //for now, it's assumed that the presentation panel is the main window, but that may not always be true if (vsync && Global.Config.DispAlternateVsync && Global.Config.VSyncThrottle) { dx9 = GL as IGL_SlimDX9; if (dx9 != null) { alternateVsync = true; //unset normal vsync if we've chosen the alternate vsync vsync = false; } } //TODO - whats so hard about triple buffering anyway? just enable it always, and change api to SetVsync(enable,throttle) //maybe even SetVsync(enable,throttlemethod) or just SetVsync(enable,throttle,advanced) if (LastVsyncSetting != vsync || LastVsyncSettingGraphicsControl != presentationPanel.GraphicsControl) { if (LastVsyncSetting == null && vsync) { // Workaround for vsync not taking effect at startup (Intel graphics related?) presentationPanel.GraphicsControl.SetVsync(false); } presentationPanel.GraphicsControl.SetVsync(vsync); LastVsyncSettingGraphicsControl = presentationPanel.GraphicsControl; LastVsyncSetting = vsync; } } // begin rendering on this context // should this have been done earlier? // do i need to check this on an intel video card to see if running excessively is a problem? (it used to be in the FinalTarget command below, shouldn't be a problem) //GraphicsControl.Begin(); // CRITICAL POINT for yabause+GL //TODO - auto-create and age these (and dispose when old) int rtCounter = 0; _currentFilterProgram.RenderTargetProvider = new DisplayManagerRenderTargetProvider(size => ShaderChainFrugalizers[rtCounter++].Get(size)); GL.BeginScene(); // run filter chain Texture2d texCurr = null; RenderTarget rtCurr = null; bool inFinalTarget = false; foreach (var step in _currentFilterProgram.Program) { switch (step.Type) { case FilterProgram.ProgramStepType.Run: { int fi = (int)step.Args; var f = _currentFilterProgram.Filters[fi]; f.SetInput(texCurr); f.Run(); var orec = f.FindOutput(); if (orec != null) { if (orec.SurfaceDisposition == SurfaceDisposition.Texture) { texCurr = f.GetOutput(); rtCurr = null; } } break; } case FilterProgram.ProgramStepType.NewTarget: { var size = (Size)step.Args; rtCurr = ShaderChainFrugalizers[rtCounter++].Get(size); rtCurr.Bind(); _currentFilterProgram.CurrRenderTarget = rtCurr; break; } case FilterProgram.ProgramStepType.FinalTarget: { var size = (Size)step.Args; inFinalTarget = true; rtCurr = null; _currentFilterProgram.CurrRenderTarget = null; GL.BindRenderTarget(null); break; } } } GL.EndScene(); if (job.Offscreen) { job.OffscreenBb = rtCurr.Texture2d.Resolve(); job.OffscreenBb.DiscardAlpha(); } else { Debug.Assert(inFinalTarget); //wait for vsync to begin if (alternateVsync) dx9.AlternateVsyncPass(0); //present and conclude drawing presentationPanel.GraphicsControl.SwapBuffers(); //wait for vsync to end if (alternateVsync) dx9.AlternateVsyncPass(1); //nope. don't do this. workaround for slow context switching on intel GPUs. just switch to another context when necessary before doing anything //presentationPanel.GraphicsControl.End(); } } private void LoadCustomFont(Stream fontStream) { IntPtr data = System.Runtime.InteropServices.Marshal.AllocCoTaskMem((int)fontStream.Length); byte[] fontData = new byte[fontStream.Length]; fontStream.Read(fontData, 0, (int)fontStream.Length); System.Runtime.InteropServices.Marshal.Copy(fontData, 0, data, (int)fontStream.Length); CustomFonts.AddMemoryFont(data, fontData.Length); fontStream.Close(); System.Runtime.InteropServices.Marshal.FreeCoTaskMem(data); } private bool? LastVsyncSetting; private GraphicsControl LastVsyncSettingGraphicsControl; private Dictionary MapNameToLuaSurface = new Dictionary(); private Dictionary MapLuaSurfaceToName = new Dictionary(); private Dictionary LuaSurfaceSets = new Dictionary(); /// /// Peeks a locked lua surface, or returns null if it isn't locked /// public DisplaySurface PeekLockedLuaSurface(string name) { if (MapNameToLuaSurface.ContainsKey(name)) return MapNameToLuaSurface[name]; return null; } /// /// Locks the requested lua surface name /// public DisplaySurface LockLuaSurface(string name, bool clear=true) { if (MapNameToLuaSurface.ContainsKey(name)) { throw new InvalidOperationException($"Lua surface is already locked: {name}"); } if (!LuaSurfaceSets.TryGetValue(name, out var sdss)) { sdss = new SwappableDisplaySurfaceSet(); LuaSurfaceSets.Add(name, sdss); } // placeholder logic for more abstracted surface definitions from filter chain int currNativeWidth = presentationPanel.NativeSize.Width; int currNativeHeight = presentationPanel.NativeSize.Height; currNativeWidth += ClientExtraPadding.Horizontal; currNativeHeight += ClientExtraPadding.Vertical; int width,height; if(name == "emu") { width = currEmuWidth; height = currEmuHeight; width += GameExtraPadding.Horizontal; height += GameExtraPadding.Vertical; } else if(name == "native") { width = currNativeWidth; height = currNativeHeight; } else throw new InvalidOperationException($"Unknown lua surface name: {name}"); DisplaySurface ret = sdss.AllocateSurface(width, height, clear); MapNameToLuaSurface[name] = ret; MapLuaSurfaceToName[ret] = name; return ret; } public void ClearLuaSurfaces() { foreach (var kvp in LuaSurfaceSets) { try { var surf = PeekLockedLuaSurface(kvp.Key); DisplaySurface surfLocked = null; if (surf == null) { surfLocked = LockLuaSurface(kvp.Key,true); } if (surfLocked != null) { UnlockLuaSurface(surfLocked); } LuaSurfaceSets[kvp.Key].SetPending(null); } catch (InvalidOperationException) { } } } /// /// Unlocks this DisplaySurface which had better have been locked as a lua surface /// public void UnlockLuaSurface(DisplaySurface surface) { if (!MapLuaSurfaceToName.ContainsKey(surface)) { throw new InvalidOperationException("Surface was not locked as a lua surface"); } string name = MapLuaSurfaceToName[surface]; MapLuaSurfaceToName.Remove(surface); MapNameToLuaSurface.Remove(name); LuaSurfaceSets[name].SetPending(surface); } // helper classes: private class MyBlitter : IBlitter { private readonly DisplayManager _owner; public MyBlitter(DisplayManager dispManager) { _owner = dispManager; } private class FontWrapper : IBlitterFont { public FontWrapper(StringRenderer font) { Font = font; } public readonly StringRenderer Font; } IBlitterFont IBlitter.GetFontType(string fontType) { return new FontWrapper(_owner.TheOneFont); } void IBlitter.DrawString(string s, IBlitterFont font, Color color, float x, float y) { var stringRenderer = ((FontWrapper)font).Font; _owner.Renderer.SetModulateColor(color); stringRenderer.RenderString(_owner.Renderer, x, y, s); _owner.Renderer.SetModulateColorWhite(); } SizeF IBlitter.MeasureString(string s, IBlitterFont font) { var stringRenderer = ((FontWrapper)font).Font; return stringRenderer.Measure(s); } public Rectangle ClipBounds { get; set; } } } }