mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 20:18:20 -04:00
deprecated old gui interfaces
playmode now uses the sdlimgui playmode, rather than a separate pacakge. this removes a lot of duplicate code but it should also make it easier to add support to flip between debugger and playmode on the fly. bumped imgui-go version to 2.40
This commit is contained in:
parent
9bc2b3cb08
commit
b92a8202ce
|
@ -48,9 +48,10 @@ func (dbg *Debugger) guiEventHandler(ev gui.Event) error {
|
|||
if !handled {
|
||||
if ev.Down && ev.Mod == gui.KeyModNone {
|
||||
switch ev.Key {
|
||||
// debugging helpers
|
||||
case "F12":
|
||||
// toggle croppint
|
||||
err = dbg.scr.SetFeature(gui.ReqToggleCropping)
|
||||
|
||||
case "F11":
|
||||
// toggle debugging colours
|
||||
err = dbg.scr.SetFeature(gui.ReqToggleAltColors)
|
||||
|
@ -58,8 +59,10 @@ func (dbg *Debugger) guiEventHandler(ev gui.Event) error {
|
|||
// toggle overlay
|
||||
err = dbg.scr.SetFeature(gui.ReqToggleOverlay)
|
||||
|
||||
// screen scaling
|
||||
case "=":
|
||||
fallthrough // equal sign is the same as plus, for convenience
|
||||
// equal sign is the same as plus, for convenience
|
||||
fallthrough
|
||||
case "+":
|
||||
// increase scaling
|
||||
err = dbg.scr.SetFeature(gui.ReqIncScale)
|
||||
|
|
|
@ -133,7 +133,7 @@ const (
|
|||
WavWriter = "wav writer: %v"
|
||||
|
||||
// gui
|
||||
UnsupportedGUIRequest = "gui error: unsupported request (%v)"
|
||||
UnsupportedGUIRequest = "unsupported request (%v)"
|
||||
SDLDebug = "sdldebug: %v"
|
||||
SDLPlay = "sdlplay: %v"
|
||||
SDLImgui = "sdlimgui: %v"
|
||||
|
|
2
go.mod
2
go.mod
|
@ -10,7 +10,7 @@ require (
|
|||
github.com/go-audio/audio v1.0.0
|
||||
github.com/go-audio/wav v1.0.0
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
|
||||
github.com/inkyblackness/imgui-go/v2 v2.3.0
|
||||
github.com/inkyblackness/imgui-go/v2 v2.4.0
|
||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942
|
||||
github.com/veandco/go-sdl2 v0.4.1
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -11,6 +11,8 @@ github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluN
|
|||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/inkyblackness/imgui-go/v2 v2.3.0 h1:AK37pUuPO3Uh+/5Kln5DZKGGApMgrqw8vyt0R2r0ork=
|
||||
github.com/inkyblackness/imgui-go/v2 v2.3.0/go.mod h1:cwQKd6U2onyuT5qwSC3DKCRsUE1SQ58TSVVpLoW/pDs=
|
||||
github.com/inkyblackness/imgui-go/v2 v2.4.0 h1:Trb694GPM+ImDgJaOoEXNWk0ofvI1R05JFGTB6PLFH4=
|
||||
github.com/inkyblackness/imgui-go/v2 v2.4.0/go.mod h1:cwQKd6U2onyuT5qwSC3DKCRsUE1SQ58TSVVpLoW/pDs=
|
||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0=
|
||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
|
|
@ -35,9 +35,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/errors"
|
||||
"github.com/jetsetilly/gopher2600/gui"
|
||||
"github.com/jetsetilly/gopher2600/gui/deprecated/sdldebug"
|
||||
"github.com/jetsetilly/gopher2600/gui/deprecated/sdlplay"
|
||||
"github.com/jetsetilly/gopher2600/gui/sdlimgui"
|
||||
"github.com/jetsetilly/gopher2600/gui/sdlimgui_play"
|
||||
"github.com/jetsetilly/gopher2600/hiscore"
|
||||
"github.com/jetsetilly/gopher2600/modalflag"
|
||||
"github.com/jetsetilly/gopher2600/paths"
|
||||
|
@ -240,8 +238,6 @@ func play(md *modalflag.Modes, sync *mainSync) error {
|
|||
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
||||
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL")
|
||||
scaling := md.AddFloat64("scale", 3.0, "television scaling")
|
||||
stable := md.AddBool("stable", true, "wait for stable frame before opening display")
|
||||
crt := md.AddBool("crt", true, "apply CRT effects")
|
||||
fpsCap := md.AddBool("fpscap", true, "cap fps to specification")
|
||||
record := md.AddBool("record", false, "record user input to a file")
|
||||
wav := md.AddString("wav", "", "record audio to wav file")
|
||||
|
@ -281,14 +277,8 @@ func play(md *modalflag.Modes, sync *mainSync) error {
|
|||
}
|
||||
|
||||
// create gui
|
||||
if *crt {
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlimgui_play.NewSdlImguiPlay(tv)
|
||||
}
|
||||
} else {
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlplay.NewSdlPlay(tv, float32(*scaling))
|
||||
}
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlimgui.NewSdlImgui(tv, true)
|
||||
}
|
||||
|
||||
// wait for creator result
|
||||
|
@ -296,6 +286,10 @@ func play(md *modalflag.Modes, sync *mainSync) error {
|
|||
select {
|
||||
case g := <-sync.creation:
|
||||
scr = g.(gui.GUI)
|
||||
err = scr.SetFeature(gui.ReqSetPlaymode, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case err := <-sync.creationError:
|
||||
return errors.New(errors.PlayError, err)
|
||||
}
|
||||
|
@ -310,7 +304,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = playmode.Play(tv, scr, *stable, *record, cartload, *patchFile, *hiscore)
|
||||
err = playmode.Play(tv, scr, *record, cartload, *patchFile, *hiscore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -353,13 +347,12 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
|
|||
|
||||
var term terminal.Terminal
|
||||
|
||||
// decide which gui to use
|
||||
// create gui
|
||||
if *termType == "IMGUI" {
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlimgui.NewSdlImgui(tv)
|
||||
return sdlimgui.NewSdlImgui(tv, false)
|
||||
}
|
||||
} else {
|
||||
|
||||
// notify main thread of new gui creator
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdldebug.NewSdlDebug(tv, 2.0)
|
||||
|
@ -510,8 +503,7 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
|
|||
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
||||
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL")
|
||||
display := md.AddBool("display", false, "display TV output")
|
||||
scaling := md.AddFloat64("scale", 3.0, "display scaling (only valid if -display=true")
|
||||
crt := md.AddBool("crt", true, "apply CRT effects")
|
||||
scaling := md.AddFloat64("scale", 2.0, "display scaling (only valid if -display=true")
|
||||
fpsCap := md.AddBool("fpscap", true, "cap FPS to specification (only valid if -display=true)")
|
||||
duration := md.AddString("duration", "5s", "run duration (note: there is a 2s overhead)")
|
||||
profile := md.AddBool("profile", false, "produce cpu and memory profiling reports")
|
||||
|
@ -540,14 +532,8 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
|
|||
|
||||
if *display {
|
||||
// create gui
|
||||
if *crt {
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlimgui_play.NewSdlImguiPlay(tv)
|
||||
}
|
||||
} else {
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlplay.NewSdlPlay(tv, float32(*scaling))
|
||||
}
|
||||
sync.creator <- func() (GuiCreator, error) {
|
||||
return sdlimgui.NewSdlImgui(tv, true)
|
||||
}
|
||||
|
||||
// wait for creator result
|
||||
|
@ -564,12 +550,6 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// show gui
|
||||
err = scr.SetFeature(gui.ReqSetVisibility, true)
|
||||
if err != nil {
|
||||
return errors.New(errors.PerformanceError, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = performance.Check(md.Output, *profile, tv, *duration, cartload)
|
||||
|
|
|
@ -101,8 +101,11 @@ func (img *SdlImguiPlay) serviceFeatureRequests(request featureRequest) {
|
|||
img.screen.scaling = request.args[0].(float32)
|
||||
img.plt.fitDisplaySize()
|
||||
|
||||
case gui.ReqSetPlaymode:
|
||||
// set playmode is implicit in for this gui implementation
|
||||
|
||||
default:
|
||||
err = errors.New(errors.UnsupportedGUIRequest, request)
|
||||
err = errors.New(errors.UnsupportedGUIRequest, request.request)
|
||||
}
|
||||
|
||||
img.featureErr <- err
|
|
@ -28,9 +28,7 @@ type FeatureReq string
|
|||
// probably crash.
|
||||
//
|
||||
// Note that, like the name suggests, these are requests, they may or may not
|
||||
// be satisifed depending other conditions in the GUI. For example, in the
|
||||
// imgui gui implementation, a capture mouse request will only be honoured if
|
||||
// the "TV screen" window is active.
|
||||
// be satisifed depending other conditions in the GUI.
|
||||
const (
|
||||
ReqSetVisibility FeatureReq = "ReqSetVisibility" // bool
|
||||
ReqToggleVisibility FeatureReq = "ReqToggleVisibility" // none
|
||||
|
@ -54,4 +52,9 @@ const (
|
|||
// own go routine but regardless, the event channel is used for this
|
||||
// purpose.
|
||||
ReqSetEventChan FeatureReq = "ReqSetEventChan" // chan gui.Event()
|
||||
|
||||
// playmode is called whenever the play/debugger looper is changed. like
|
||||
// all other requests this may not do anything, depending on the GUI
|
||||
// specifics
|
||||
ReqSetPlaymode FeatureReq = "ReqSetPlaymode" // bool
|
||||
)
|
||||
|
|
|
@ -171,8 +171,8 @@ func (rnd *glsl) render(displaySize [2]float32, framebufferSize [2]float32, draw
|
|||
gl.EnableVertexAttribArray(uint32(rnd.attribUV))
|
||||
gl.EnableVertexAttribArray(uint32(rnd.attribColor))
|
||||
vertexSize, vertexOffsetPos, vertexOffsetUv, vertexOffsetCol := imgui.VertexBufferLayout()
|
||||
gl.VertexAttribPointer(uint32(rnd.attribPosition), 2, gl.FLOAT, false, int32(vertexSize), unsafe.Pointer(uintptr(vertexOffsetPos)))
|
||||
gl.VertexAttribPointer(uint32(rnd.attribUV), 2, gl.FLOAT, false, int32(vertexSize), unsafe.Pointer(uintptr(vertexOffsetUv)))
|
||||
gl.VertexAttribPointer(uint32(rnd.attribPosition), 2, gl.FLOAT, false, int32(vertexSize), unsafe.Pointer(uintptr(vertexOffsetPos)))
|
||||
gl.VertexAttribPointer(uint32(rnd.attribColor), 4, gl.UNSIGNED_BYTE, true, int32(vertexSize), unsafe.Pointer(uintptr(vertexOffsetCol)))
|
||||
indexSize := imgui.IndexBufferLayout()
|
||||
drawType := gl.UNSIGNED_SHORT
|
||||
|
@ -202,8 +202,8 @@ func (rnd *glsl) render(displaySize [2]float32, framebufferSize [2]float32, draw
|
|||
// critical section
|
||||
rnd.img.screen.crit.section.Lock()
|
||||
|
||||
vertScaling := rnd.img.wm.dbgScr.vertScaling()
|
||||
horizScaling := rnd.img.wm.dbgScr.horizScaling()
|
||||
vertScaling := rnd.img.wm.dbgScr.getScaling(false)
|
||||
horizScaling := rnd.img.wm.dbgScr.getScaling(true)
|
||||
|
||||
// pixel perfect rendering or whether to apply the CRT
|
||||
// filters
|
||||
|
@ -214,8 +214,8 @@ func (rnd *glsl) render(displaySize [2]float32, framebufferSize [2]float32, draw
|
|||
}
|
||||
|
||||
// the resolution information is used to scale the Last
|
||||
gl.Uniform2f(rnd.attribDim, rnd.img.wm.dbgScr.scaledWidth(), rnd.img.wm.dbgScr.scaledHeight())
|
||||
gl.Uniform2f(rnd.attribCropDim, rnd.img.wm.dbgScr.scaledCroppedWidth(), rnd.img.wm.dbgScr.scaledCroppedHeight())
|
||||
gl.Uniform2f(rnd.attribDim, rnd.img.wm.dbgScr.getScaledWidth(false), rnd.img.wm.dbgScr.getScaledHeight(false))
|
||||
gl.Uniform2f(rnd.attribCropDim, rnd.img.wm.dbgScr.getScaledWidth(true), rnd.img.wm.dbgScr.getScaledHeight(true))
|
||||
|
||||
// screen geometry
|
||||
gl.Uniform1f(rnd.attribHblank, television.HorizClksHBlank*horizScaling)
|
||||
|
@ -257,6 +257,8 @@ func (rnd *glsl) render(displaySize [2]float32, framebufferSize [2]float32, draw
|
|||
switch textureID {
|
||||
case rnd.img.wm.dbgScr.overlayTexture:
|
||||
gl.Uniform1i(rnd.attribImageType, 2)
|
||||
case rnd.img.wm.playScr.screenTexture:
|
||||
gl.Uniform1i(rnd.attribImageType, 3)
|
||||
case rnd.img.wm.dbgScr.screenTexture:
|
||||
gl.Uniform1i(rnd.attribImageType, 1)
|
||||
default:
|
||||
|
|
|
@ -145,7 +145,7 @@ func (plt *platform) newFrame() {
|
|||
// deactivated when the "invisible" mouse is outside the tv screen bounds.
|
||||
//
|
||||
// TODO: roll mouse updates into service loop
|
||||
if plt.img.wm.dbgScr.isCaptured && !plt.img.wm.dbgScr.isHovered {
|
||||
if plt.img.isCaptured() && !plt.img.wm.dbgScr.isHovered {
|
||||
state = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,99 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// *** NOTE: all historical versions of this file, as found in any
|
||||
// git repository, are also covered by the licence, even when this
|
||||
// notice is not present ***
|
||||
package sdlimgui
|
||||
|
||||
type prefValues struct {
|
||||
windowWidth int32
|
||||
windowHeight int32
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/errors"
|
||||
"github.com/jetsetilly/gopher2600/paths"
|
||||
"github.com/jetsetilly/gopher2600/prefs"
|
||||
)
|
||||
|
||||
// the acceptable preferencegroups provided to initPrefs()
|
||||
type prefGroup string
|
||||
|
||||
const (
|
||||
prefsGrpDebugger prefGroup = "sdlimgui.debugger"
|
||||
prefsGrpPlaymode prefGroup = "sdlimgui.playmode"
|
||||
)
|
||||
|
||||
// preferences change subtly when switching between debugger and play modes
|
||||
func (img *SdlImgui) initPrefs(group prefGroup) error {
|
||||
// setup preferences
|
||||
pth, err := paths.ResourcePath("", prefs.DefaultPrefsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.prefs, err = prefs.NewDisk(pth)
|
||||
|
||||
err = img.prefs.Add(fmt.Sprintf("%s.windowsize", group), prefs.NewGeneric(
|
||||
func(s string) error {
|
||||
var w, h int32
|
||||
_, err := fmt.Sscanf(s, "%d,%d", &w, &h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.plt.window.SetSize(w, h)
|
||||
return nil
|
||||
},
|
||||
func() string {
|
||||
w, h := img.plt.window.GetSize()
|
||||
return fmt.Sprintf("%d,%d", w, h)
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = img.prefs.Add(fmt.Sprintf("%s.windowpos", group), prefs.NewGeneric(
|
||||
func(s string) error {
|
||||
var x, y int32
|
||||
_, err := fmt.Sscanf(s, "%d,%d", &x, &y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// !TODO: SetPosition doesn't seem to set window position as you
|
||||
// might expect. On XWindow with Cinnamon WM, it seems to place the
|
||||
// window top to the window further down and slightly to the right
|
||||
// of where it should be. This means that the window "drifts" down
|
||||
// the screen on subsequent loads
|
||||
img.plt.window.SetPosition(x, y)
|
||||
return nil
|
||||
},
|
||||
func() string {
|
||||
x, y := img.plt.window.GetPosition()
|
||||
return fmt.Sprintf("%d,%d", x, y)
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// load preferences from disk
|
||||
err = img.prefs.Load()
|
||||
if err != nil {
|
||||
// ignore missing prefs file errors
|
||||
if !errors.Is(err, errors.PrefsNoFile) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ func (img *SdlImgui) serviceFeatureRequests(request featureRequest) {
|
|||
// lazy (but clear) handling of type assertion errors
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
img.featureErr <- errors.New(errors.PanicError, "sdl.SetFeature()", r)
|
||||
img.featureErr <- errors.New(errors.PanicError, "sdlImgui.serviceFeatureRequests()", r)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -57,8 +57,10 @@ func (img *SdlImgui) serviceFeatureRequests(request featureRequest) {
|
|||
case gui.ReqSetVisibleOnStable:
|
||||
|
||||
case gui.ReqSetVisibility:
|
||||
img.wm.dbgScr.setOpen(request.args[0].(bool))
|
||||
|
||||
case gui.ReqToggleVisibility:
|
||||
img.wm.dbgScr.setOpen(!img.wm.dbgScr.isOpen())
|
||||
|
||||
case gui.ReqSetAltColors:
|
||||
img.wm.dbgScr.useAltPixels = request.args[0].(bool)
|
||||
|
@ -79,17 +81,13 @@ func (img *SdlImgui) serviceFeatureRequests(request featureRequest) {
|
|||
img.wm.dbgScr.setOverlay(!img.wm.dbgScr.overlay)
|
||||
|
||||
case gui.ReqIncScale:
|
||||
if img.wm.dbgScr.scaling < 4.0 {
|
||||
img.wm.dbgScr.scaling += 0.1
|
||||
}
|
||||
img.setScale(0.1, true)
|
||||
|
||||
case gui.ReqDecScale:
|
||||
if img.wm.dbgScr.scaling > 0.5 {
|
||||
img.wm.dbgScr.scaling -= 0.1
|
||||
}
|
||||
img.setScale(-0.1, true)
|
||||
|
||||
case gui.ReqSetScale:
|
||||
img.wm.dbgScr.scaling = request.args[0].(float32)
|
||||
img.setScale(request.args[0].(float32), false)
|
||||
|
||||
case gui.ReqSetPause:
|
||||
img.pause(request.args[0].(bool))
|
||||
|
@ -103,9 +101,16 @@ func (img *SdlImgui) serviceFeatureRequests(request featureRequest) {
|
|||
case gui.ReqAddDisasm:
|
||||
img.lz.Dsm = request.args[0].(*disassembly.Disassembly)
|
||||
|
||||
case gui.ReqSetPlaymode:
|
||||
err = img.setPlaymode(request.args[0].(bool))
|
||||
|
||||
default:
|
||||
err = errors.New(errors.UnsupportedGUIRequest, request)
|
||||
}
|
||||
|
||||
img.featureErr <- err
|
||||
if err == nil {
|
||||
img.featureErr <- nil
|
||||
} else {
|
||||
img.featureErr <- errors.New(errors.SDLImgui, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
|
||||
"github.com/jetsetilly/gopher2600/reflection"
|
||||
"github.com/jetsetilly/gopher2600/television"
|
||||
"github.com/jetsetilly/gopher2600/test"
|
||||
)
|
||||
|
||||
// textureRenderers should consider that the timing of the VCS produces
|
||||
|
@ -184,8 +183,6 @@ func (scr *screen) NewScanline(scanline int) error {
|
|||
|
||||
// SetPixel implements the television.PixelRenderer interface
|
||||
func (scr *screen) SetPixel(x int, y int, red byte, green byte, blue byte, vblank bool) error {
|
||||
test.AssertNonMainThread()
|
||||
|
||||
scr.crit.section.Lock()
|
||||
defer scr.crit.section.Unlock()
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package sdlimgui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/debugger/terminal"
|
||||
|
@ -94,7 +93,7 @@ type SdlImgui struct {
|
|||
// NewSdlImgui is the preferred method of initialisation for type SdlImgui
|
||||
//
|
||||
// MUST ONLY be called from the #mainthread
|
||||
func NewSdlImgui(tv television.Television) (*SdlImgui, error) {
|
||||
func NewSdlImgui(tv television.Television, playmode bool) (*SdlImgui, error) {
|
||||
img := &SdlImgui{
|
||||
context: imgui.CreateContext(nil),
|
||||
io: imgui.CurrentIO(),
|
||||
|
@ -141,6 +140,7 @@ func NewSdlImgui(tv television.Television) (*SdlImgui, error) {
|
|||
// connect pixel renderer to television and texture renderer to pixel renderer
|
||||
tv.AddPixelRenderer(img.screen)
|
||||
img.screen.addTextureRenderer(img.wm.dbgScr)
|
||||
img.screen.addTextureRenderer(img.wm.playScr)
|
||||
|
||||
// this audio mixer produces the sound. there is another AudioMixer
|
||||
// implementation in winAudio which visualises the sound
|
||||
|
@ -150,65 +150,10 @@ func NewSdlImgui(tv television.Television) (*SdlImgui, error) {
|
|||
}
|
||||
tv.AddAudioMixer(img.audio)
|
||||
|
||||
// setup preferences
|
||||
pth, err := paths.ResourcePath("", prefs.DefaultPrefsFile)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.SDLImgui, err)
|
||||
}
|
||||
img.prefs, err = prefs.NewDisk(pth)
|
||||
|
||||
err = img.prefs.Add("sdlimgui.debugger.windowsize", prefs.NewGeneric(
|
||||
func(s string) error {
|
||||
var w, h int32
|
||||
_, err := fmt.Sscanf(s, "%d,%d", &w, &h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.plt.window.SetSize(w, h)
|
||||
return nil
|
||||
},
|
||||
func() string {
|
||||
w, h := img.plt.window.GetSize()
|
||||
return fmt.Sprintf("%d,%d", w, h)
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = img.prefs.Add("sdlimgui.debugger.windowpos", prefs.NewGeneric(
|
||||
func(s string) error {
|
||||
var x, y int32
|
||||
_, err := fmt.Sscanf(s, "%d,%d", &x, &y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// !TODO: SetPosition doesn't seem to set window position as you
|
||||
// might expect. On XWindow with Cinnamon WM, it seems to place the
|
||||
// window top to the window further down and slightly to the right
|
||||
// of where it should be. This means that the window "drifts" down
|
||||
// the screen on subsequent loads
|
||||
img.plt.window.SetPosition(x, y)
|
||||
return nil
|
||||
},
|
||||
func() string {
|
||||
x, y := img.plt.window.GetPosition()
|
||||
return fmt.Sprintf("%d,%d", x, y)
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.SDLImgui, err)
|
||||
}
|
||||
|
||||
// load preferences from disk
|
||||
err = img.prefs.Load()
|
||||
if err != nil {
|
||||
// ignore missing prefs file errors
|
||||
if !errors.Is(err, errors.PrefsNoFile) {
|
||||
return nil, errors.New(errors.SDLImgui, err)
|
||||
}
|
||||
}
|
||||
// set playmode according to the playmode argument
|
||||
img.setPlaymode(playmode)
|
||||
|
||||
// open container window
|
||||
img.plt.window.Show()
|
||||
|
||||
return img, nil
|
||||
|
@ -240,9 +185,7 @@ func (img *SdlImgui) pause(set bool) {
|
|||
}
|
||||
|
||||
func (img *SdlImgui) draw() {
|
||||
if img.lz.Dbg != nil {
|
||||
img.wm.draw()
|
||||
}
|
||||
img.wm.draw()
|
||||
}
|
||||
|
||||
// GetTerminal implements terminal.Broker interface
|
||||
|
@ -254,3 +197,89 @@ func (img *SdlImgui) GetTerminal() terminal.Terminal {
|
|||
func (img *SdlImgui) GetReflectionRenderer() reflection.Renderer {
|
||||
return img.screen
|
||||
}
|
||||
|
||||
// the following functions are used to differentiate play-mode from debug-mode.
|
||||
// any operation that is dependent on playmode state should be abstracted to a
|
||||
// function and placed below.
|
||||
//
|
||||
// for simplicity, play-mode is defined as being on when playScr is open
|
||||
|
||||
func (img *SdlImgui) isPlaymode() bool {
|
||||
return img.wm != nil && img.wm.playScr.isOpen()
|
||||
}
|
||||
|
||||
// set playmode and handle the changeover gracefully. this includes the saving
|
||||
// and loading of preference groups
|
||||
func (img *SdlImgui) setPlaymode(set bool) error {
|
||||
if set {
|
||||
if !img.isPlaymode() {
|
||||
if img.prefs != nil {
|
||||
if err := img.prefs.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
img.initPrefs(prefsGrpPlaymode)
|
||||
img.wm.playScr.setOpen(true)
|
||||
}
|
||||
} else {
|
||||
if img.isPlaymode() {
|
||||
if img.prefs != nil {
|
||||
if err := img.prefs.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
img.initPrefs(prefsGrpDebugger)
|
||||
img.wm.playScr.setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *SdlImgui) isCaptured() bool {
|
||||
if img.isPlaymode() {
|
||||
return img.wm.playScr.isCaptured
|
||||
}
|
||||
return img.wm.dbgScr.isCaptured
|
||||
}
|
||||
|
||||
func (img *SdlImgui) setCapture(set bool) {
|
||||
if img.isPlaymode() {
|
||||
img.wm.playScr.isCaptured = set
|
||||
return
|
||||
}
|
||||
img.wm.dbgScr.isCaptured = set
|
||||
}
|
||||
|
||||
func (img *SdlImgui) isHovered() bool {
|
||||
if img.isPlaymode() {
|
||||
return true
|
||||
}
|
||||
return img.wm.dbgScr.isHovered && !img.wm.dbgScr.isPopup
|
||||
}
|
||||
|
||||
// scaling of the tv screen also depends on whether playmode is active
|
||||
|
||||
type scalingScreen interface {
|
||||
getScaling(horiz bool) float32
|
||||
setScaling(scaling float32)
|
||||
}
|
||||
|
||||
func (img *SdlImgui) setScale(scaling float32, adjust bool) {
|
||||
var scr scalingScreen
|
||||
|
||||
if img.isPlaymode() {
|
||||
scr = img.wm.playScr
|
||||
} else {
|
||||
scr = img.wm.dbgScr
|
||||
}
|
||||
|
||||
if adjust {
|
||||
scale := scr.getScaling(false)
|
||||
if scale > 0.5 && scale < 4.0 {
|
||||
scr.setScaling(scale + scaling)
|
||||
}
|
||||
} else {
|
||||
scr.setScaling(scaling)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,12 +62,12 @@ func (img *SdlImgui) Service() {
|
|||
img.events <- gui.EventQuit{}
|
||||
|
||||
case *sdl.TextInputEvent:
|
||||
if !img.wm.dbgScr.isCaptured {
|
||||
if !img.isCaptured() {
|
||||
img.io.AddInputCharacters(string(ev.Text[:]))
|
||||
}
|
||||
|
||||
case *sdl.KeyboardEvent:
|
||||
if img.wm.dbgScr.isCaptured {
|
||||
if img.isPlaymode() || img.isCaptured() {
|
||||
mod := gui.KeyModNone
|
||||
|
||||
if sdl.GetModState()&sdl.KMOD_LALT == sdl.KMOD_LALT ||
|
||||
|
@ -112,19 +112,11 @@ func (img *SdlImgui) Service() {
|
|||
// the button event to send
|
||||
var button gui.MouseButton
|
||||
|
||||
// mouse events are swallowed by the service loop
|
||||
// if they've been handled
|
||||
var swallow bool
|
||||
|
||||
switch ev.Button {
|
||||
case sdl.BUTTON_LEFT:
|
||||
if img.wm.dbgScr.isCaptured {
|
||||
button = gui.MouseButtonLeft
|
||||
} else if img.wm.dbgScr.isHovered && !img.wm.dbgScr.isPopup {
|
||||
// left mouse button should capture mouse if
|
||||
// not already done so.
|
||||
swallow = true
|
||||
img.wm.dbgScr.isCaptured = true
|
||||
button = gui.MouseButtonLeft
|
||||
if img.isHovered() {
|
||||
img.setCapture(true)
|
||||
err := sdl.CaptureMouse(true)
|
||||
if err == nil {
|
||||
img.plt.window.SetGrab(true)
|
||||
|
@ -136,9 +128,8 @@ func (img *SdlImgui) Service() {
|
|||
button = gui.MouseButtonRight
|
||||
|
||||
// right mouse button releases a captured mouse
|
||||
if img.wm.dbgScr.isCaptured {
|
||||
swallow = true
|
||||
img.wm.dbgScr.isCaptured = false
|
||||
if img.isCaptured() {
|
||||
img.setCapture(false)
|
||||
err := sdl.CaptureMouse(false)
|
||||
if err == nil {
|
||||
img.plt.window.SetGrab(false)
|
||||
|
@ -147,7 +138,7 @@ func (img *SdlImgui) Service() {
|
|||
}
|
||||
}
|
||||
|
||||
if !swallow {
|
||||
if img.isCaptured() {
|
||||
img.events <- gui.EventMouseButton{
|
||||
Button: button,
|
||||
Down: ev.Type == sdl.MOUSEBUTTONDOWN}
|
||||
|
@ -171,7 +162,7 @@ func (img *SdlImgui) Service() {
|
|||
}
|
||||
|
||||
// mouse motion
|
||||
if img.wm.dbgScr.isCaptured {
|
||||
if img.isCaptured() {
|
||||
mx, my, _ := sdl.GetMouseState()
|
||||
if mx != img.mx || my != img.my {
|
||||
w, h := img.plt.window.GetSize()
|
||||
|
@ -192,7 +183,9 @@ func (img *SdlImgui) Service() {
|
|||
}
|
||||
|
||||
// update late values
|
||||
img.lz.Update()
|
||||
if !img.isPlaymode() {
|
||||
img.lz.Update()
|
||||
}
|
||||
|
||||
// Signal start of a new frame
|
||||
img.plt.newFrame()
|
||||
|
@ -216,8 +209,10 @@ func (img *SdlImgui) Service() {
|
|||
default:
|
||||
}
|
||||
|
||||
// sleep to help avoid 100% CPU usage. apply this delay even if emulation
|
||||
// is running (compare to sdldebug which ddoes not apply the delay when
|
||||
// emulation is running)
|
||||
<-time.After(time.Millisecond * 25)
|
||||
// sleep to help avoid 100% CPU usage when in non-play mode. when in
|
||||
// playmode we want all the cycles as is necessary - excess cycles will be
|
||||
// "given back" by the frame limiter
|
||||
if !img.isPlaymode() {
|
||||
<-time.After(time.Millisecond * 25)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,6 @@ type winDbgScr struct {
|
|||
// (re)create textures on next render()
|
||||
createTextures bool
|
||||
|
||||
// the basic amount by which the image should be scaled. image width
|
||||
// is also scaled by pixelWidth and aspectBias value
|
||||
scaling float32
|
||||
|
||||
// is screen currently pointed at
|
||||
isHovered bool
|
||||
|
||||
|
@ -76,7 +72,22 @@ type winDbgScr struct {
|
|||
// additional padding for the image so that it is centered in its content space
|
||||
imagePadding imgui.Vec2
|
||||
|
||||
windowContentDimen imgui.Vec2
|
||||
// size of window and content area in which to center the image. we need
|
||||
// both depending on how we set the scaling from the screen. when resizing
|
||||
// the window, we use contentDim (the area inside the window) to figure out
|
||||
// the scaling value. when resizing numerically (with the getScale()
|
||||
// function) on the other hand, we scale the entire window accordingly
|
||||
winDim imgui.Vec2
|
||||
contentDim imgui.Vec2
|
||||
|
||||
// when set the scale value numerically (with the getScale() function) we
|
||||
// need to alter how we set the window size for the first frame afterwards.
|
||||
// the rescaled bool helps us do this.
|
||||
rescaled bool
|
||||
|
||||
// the basic amount by which the image should be scaled. horizontal scaling
|
||||
// is slightly different (see horizScaling() function)
|
||||
scaling float32
|
||||
}
|
||||
|
||||
func newWinDbgScr(img *SdlImgui) (managedWindow, error) {
|
||||
|
@ -123,15 +134,21 @@ func (win *winDbgScr) draw() {
|
|||
// actual display
|
||||
var w, h float32
|
||||
if win.cropped {
|
||||
w = win.scaledCroppedWidth()
|
||||
h = win.scaledCroppedHeight()
|
||||
w = win.getScaledWidth(true)
|
||||
h = win.getScaledHeight(true)
|
||||
} else {
|
||||
w = win.scaledWidth()
|
||||
h = win.scaledHeight()
|
||||
w = win.getScaledWidth(false)
|
||||
h = win.getScaledHeight(false)
|
||||
}
|
||||
|
||||
imgui.SetNextWindowPosV(imgui.Vec2{8, 28}, imgui.ConditionFirstUseEver, imgui.Vec2{0, 0})
|
||||
imgui.SetNextWindowSizeV(imgui.Vec2{611, 470}, imgui.ConditionFirstUseEver)
|
||||
|
||||
if win.rescaled {
|
||||
imgui.SetNextWindowSize(win.winDim)
|
||||
win.rescaled = false
|
||||
} else {
|
||||
imgui.SetNextWindowSizeV(imgui.Vec2{611, 470}, imgui.ConditionFirstUseEver)
|
||||
}
|
||||
|
||||
// if isCaptured flag is set then change the title and border colors of the
|
||||
// TV Screen window.
|
||||
|
@ -144,8 +161,9 @@ func (win *winDbgScr) draw() {
|
|||
// we don't want to ever show scrollbars
|
||||
imgui.BeginV(winDbgScrTitle, &win.open, imgui.WindowFlagsNoScrollbar)
|
||||
|
||||
// note size of window
|
||||
win.windowContentDimen = imgui.ContentRegionAvail()
|
||||
// note size of window and content area
|
||||
win.winDim = imgui.WindowSize()
|
||||
win.contentDim = imgui.ContentRegionAvail()
|
||||
|
||||
// add horiz/vert padding around screen image
|
||||
imgui.SetCursorPos(imgui.CursorPos().Plus(win.imagePadding))
|
||||
|
@ -164,6 +182,7 @@ func (win *winDbgScr) draw() {
|
|||
imgui.SetCursorPos(imgui.CursorPos().Plus(win.imagePadding))
|
||||
|
||||
// popup menu on right mouse button
|
||||
// !TODO: RMB to release captured window causes popup to immediately open
|
||||
win.isPopup = imgui.BeginPopupContextItem()
|
||||
if win.isPopup {
|
||||
imgui.Text("Break")
|
||||
|
@ -188,22 +207,18 @@ func (win *winDbgScr) draw() {
|
|||
// *** CRIT SECTION
|
||||
win.scr.crit.section.RLock()
|
||||
|
||||
// get mouse position and transform it so it relates to the underlying
|
||||
// image
|
||||
// get mouse position and transform
|
||||
mp := imgui.MousePos().Minus(mouseOrigin)
|
||||
mp.X = mp.X / win.scaledCroppedWidth()
|
||||
mp.Y = mp.Y / win.scaledCroppedHeight()
|
||||
|
||||
imageSz := win.scr.crit.cropPixels.Bounds().Size()
|
||||
|
||||
if win.cropped {
|
||||
mp.X *= float32(imageSz.X)
|
||||
sz := win.scr.crit.cropPixels.Bounds().Size()
|
||||
mp.X = mp.X / win.getScaledWidth(true) * float32(sz.X)
|
||||
mp.Y = mp.Y / win.getScaledHeight(true) * float32(sz.Y)
|
||||
mp.X += float32(television.HorizClksHBlank)
|
||||
mp.Y *= float32(imageSz.Y)
|
||||
mp.Y += float32(win.scr.crit.topScanline)
|
||||
} else {
|
||||
mp.X *= float32(imageSz.X)
|
||||
mp.Y *= float32(imageSz.Y)
|
||||
sz := win.scr.crit.pixels.Bounds().Size()
|
||||
mp.X = mp.X / win.getScaledWidth(false) * float32(sz.X)
|
||||
mp.Y = mp.Y / win.getScaledHeight(false) * float32(sz.Y)
|
||||
}
|
||||
|
||||
win.horizPos = int(mp.X)
|
||||
|
@ -314,56 +329,6 @@ func (win *winDbgScr) draw() {
|
|||
imgui.End()
|
||||
}
|
||||
|
||||
func (win *winDbgScr) setScaleFromWindow(sz imgui.Vec2) {
|
||||
sz.Y -= win.toolBarHeight
|
||||
winAspectRatio := sz.X / sz.Y
|
||||
|
||||
var imageH float32
|
||||
var imageW float32
|
||||
if win.cropped {
|
||||
imageW = float32(win.scr.crit.cropPixels.Bounds().Size().X)
|
||||
imageH = float32(win.scr.crit.cropPixels.Bounds().Size().Y)
|
||||
} else {
|
||||
imageW = float32(win.scr.crit.pixels.Bounds().Size().X)
|
||||
imageH = float32(win.scr.crit.pixels.Bounds().Size().Y)
|
||||
}
|
||||
imageW *= pixelWidth * win.scr.aspectBias
|
||||
|
||||
aspectRatio := imageW / imageH
|
||||
|
||||
if aspectRatio < winAspectRatio {
|
||||
win.scaling = sz.Y / imageH
|
||||
win.imagePadding = imgui.Vec2{X: float32(int((sz.X - (imageW * win.scaling)) / 2))}
|
||||
} else {
|
||||
win.scaling = sz.X / imageW
|
||||
win.imagePadding = imgui.Vec2{Y: float32(int((sz.Y - (imageH * win.scaling)) / 2))}
|
||||
}
|
||||
}
|
||||
|
||||
func (win *winDbgScr) scaledWidth() float32 {
|
||||
return float32(win.scr.crit.pixels.Bounds().Size().X) * win.horizScaling()
|
||||
}
|
||||
|
||||
func (win *winDbgScr) scaledHeight() float32 {
|
||||
return float32(win.scr.crit.pixels.Bounds().Size().Y) * win.vertScaling()
|
||||
}
|
||||
|
||||
func (win *winDbgScr) scaledCroppedWidth() float32 {
|
||||
return float32(win.scr.crit.cropPixels.Bounds().Size().X) * win.horizScaling()
|
||||
}
|
||||
|
||||
func (win *winDbgScr) scaledCroppedHeight() float32 {
|
||||
return float32(win.scr.crit.cropPixels.Bounds().Size().Y) * win.vertScaling()
|
||||
}
|
||||
|
||||
func (win *winDbgScr) horizScaling() float32 {
|
||||
return float32(pixelWidth * win.scr.aspectBias * win.scaling)
|
||||
}
|
||||
|
||||
func (win *winDbgScr) vertScaling() float32 {
|
||||
return win.scaling
|
||||
}
|
||||
|
||||
func (win *winDbgScr) setOverlay(set bool) {
|
||||
win.overlay = set
|
||||
}
|
||||
|
@ -436,5 +401,59 @@ func (win *winDbgScr) render() {
|
|||
}
|
||||
|
||||
// set screen image scaling (and image padding) based on the current window size
|
||||
win.setScaleFromWindow(win.windowContentDimen)
|
||||
win.setScaleFromWindow(win.contentDim)
|
||||
}
|
||||
|
||||
func (win *winDbgScr) getScaledWidth(cropped bool) float32 {
|
||||
if cropped {
|
||||
return float32(win.scr.crit.cropPixels.Bounds().Size().X) * win.getScaling(true)
|
||||
}
|
||||
return float32(win.scr.crit.pixels.Bounds().Size().X) * win.getScaling(true)
|
||||
}
|
||||
|
||||
func (win *winDbgScr) getScaledHeight(cropped bool) float32 {
|
||||
if cropped {
|
||||
return float32(win.scr.crit.cropPixels.Bounds().Size().Y) * win.getScaling(false)
|
||||
}
|
||||
return float32(win.scr.crit.pixels.Bounds().Size().Y) * win.getScaling(false)
|
||||
}
|
||||
|
||||
func (win *winDbgScr) setScaleFromWindow(sz imgui.Vec2) {
|
||||
// must be called from with a critical section
|
||||
|
||||
sz.Y -= win.toolBarHeight
|
||||
winAspectRatio := sz.X / sz.Y
|
||||
|
||||
var imageW float32
|
||||
var imageH float32
|
||||
if win.cropped {
|
||||
imageW = float32(win.scr.crit.cropPixels.Bounds().Size().X)
|
||||
imageH = float32(win.scr.crit.cropPixels.Bounds().Size().Y)
|
||||
} else {
|
||||
imageW = float32(win.scr.crit.pixels.Bounds().Size().X)
|
||||
imageH = float32(win.scr.crit.pixels.Bounds().Size().Y)
|
||||
}
|
||||
imageW *= pixelWidth * win.scr.aspectBias
|
||||
|
||||
aspectRatio := imageW / imageH
|
||||
|
||||
if aspectRatio < winAspectRatio {
|
||||
win.scaling = sz.Y / imageH
|
||||
win.imagePadding = imgui.Vec2{X: float32(int((sz.X - (imageW * win.scaling)) / 2))}
|
||||
} else {
|
||||
win.scaling = sz.X / imageW
|
||||
win.imagePadding = imgui.Vec2{Y: float32(int((sz.Y - (imageH * win.scaling)) / 2))}
|
||||
}
|
||||
}
|
||||
|
||||
func (win *winDbgScr) getScaling(horiz bool) float32 {
|
||||
if horiz {
|
||||
return float32(pixelWidth * win.scr.aspectBias * win.scaling)
|
||||
}
|
||||
return win.scaling
|
||||
}
|
||||
|
||||
func (win *winDbgScr) setScaling(scaling float32) {
|
||||
win.rescaled = true
|
||||
win.winDim = win.winDim.Times(scaling / win.scaling)
|
||||
}
|
||||
|
|
198
gui/sdlimgui/win_playscr.go
Normal file
198
gui/sdlimgui/win_playscr.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// *** NOTE: all historical versions of this file, as found in any
|
||||
// git repository, are also covered by the licence, even when this
|
||||
// notice is not present ***
|
||||
|
||||
package sdlimgui
|
||||
|
||||
import (
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/inkyblackness/imgui-go/v2"
|
||||
)
|
||||
|
||||
const winPlayScrTitle = "Atari VCS"
|
||||
|
||||
type winPlayScr struct {
|
||||
windowManagement
|
||||
widgetDimensions
|
||||
|
||||
img *SdlImgui
|
||||
scr *screen
|
||||
|
||||
// textures
|
||||
screenTexture uint32
|
||||
overlayTexture uint32
|
||||
|
||||
// (re)create textures on next render()
|
||||
createTextures bool
|
||||
|
||||
// the tv screen has captured mouse input
|
||||
isCaptured bool
|
||||
|
||||
// additional padding for the image so that it is centered in its content space
|
||||
imagePadding imgui.Vec2
|
||||
|
||||
// size of window and content area in which to center the image
|
||||
winDim imgui.Vec2
|
||||
contentDim imgui.Vec2
|
||||
|
||||
// the basic amount by which the image should be scaled. image width
|
||||
// is also scaled by pixelWidth and aspectBias value.
|
||||
//
|
||||
// use getScaling() and setScaling to access this value
|
||||
scaling float32
|
||||
}
|
||||
|
||||
func newWinPlayScr(img *SdlImgui) (managedWindow, error) {
|
||||
win := &winPlayScr{
|
||||
img: img,
|
||||
scr: img.screen,
|
||||
scaling: 2.0,
|
||||
}
|
||||
|
||||
// set texture, creation of textures will be done after every call to resize()
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.GenTextures(1, &win.screenTexture)
|
||||
gl.BindTexture(gl.TEXTURE_2D, win.screenTexture)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.GenTextures(1, &win.overlayTexture)
|
||||
gl.BindTexture(gl.TEXTURE_2D, win.overlayTexture)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
|
||||
return win, nil
|
||||
}
|
||||
|
||||
func (win *winPlayScr) init() {
|
||||
win.widgetDimensions.init()
|
||||
}
|
||||
|
||||
func (win *winPlayScr) destroy() {
|
||||
}
|
||||
|
||||
func (win *winPlayScr) id() string {
|
||||
return winPlayScrTitle
|
||||
}
|
||||
|
||||
func (win *winPlayScr) draw() {
|
||||
if !win.open {
|
||||
return
|
||||
}
|
||||
|
||||
// actual display
|
||||
w := win.getScaledWidth()
|
||||
h := win.getScaledHeight()
|
||||
|
||||
imgui.SetNextWindowPosV(imgui.Vec2{0, 0}, 0, imgui.Vec2{0, 0})
|
||||
dimen := win.img.plt.displaySize()
|
||||
win.winDim = imgui.Vec2{dimen[0], dimen[1]}
|
||||
imgui.SetNextWindowSizeV(win.winDim, 0)
|
||||
|
||||
// we don't want to ever show scrollbars
|
||||
imgui.BeginV(winPlayScrTitle, &win.open,
|
||||
imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
|
||||
imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoFocusOnAppearing)
|
||||
|
||||
// note size of window
|
||||
win.contentDim = imgui.ContentRegionAvail()
|
||||
|
||||
// add horiz/vert padding around screen image
|
||||
imgui.SetCursorPos(imgui.CursorPos().Plus(win.imagePadding))
|
||||
imgui.Image(imgui.TextureID(win.screenTexture), imgui.Vec2{w, h})
|
||||
imgui.SetCursorPos(imgui.CursorPos().Plus(win.imagePadding))
|
||||
|
||||
imgui.End()
|
||||
}
|
||||
|
||||
func (win *winPlayScr) resize() {
|
||||
win.createTextures = true
|
||||
}
|
||||
|
||||
// render is called by service loop
|
||||
func (win *winPlayScr) render() {
|
||||
win.scr.crit.section.RLock()
|
||||
defer win.scr.crit.section.RUnlock()
|
||||
|
||||
// set screen image scaling (and image padding) based on the current window size
|
||||
win.setScaleFromWindow(win.contentDim)
|
||||
|
||||
pixels := win.scr.crit.cropPixels
|
||||
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, int32(pixels.Stride)/4)
|
||||
defer gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
|
||||
if win.createTextures {
|
||||
gl.BindTexture(gl.TEXTURE_2D, win.screenTexture)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0,
|
||||
gl.RGBA, int32(pixels.Bounds().Size().X), int32(pixels.Bounds().Size().Y), 0,
|
||||
gl.RGBA, gl.UNSIGNED_BYTE,
|
||||
gl.Ptr(pixels.Pix))
|
||||
|
||||
win.createTextures = false
|
||||
|
||||
} else {
|
||||
gl.BindTexture(gl.TEXTURE_2D, win.screenTexture)
|
||||
gl.TexSubImage2D(gl.TEXTURE_2D, 0,
|
||||
0, 0, int32(pixels.Bounds().Size().X), int32(pixels.Bounds().Size().Y),
|
||||
gl.RGBA, gl.UNSIGNED_BYTE,
|
||||
gl.Ptr(pixels.Pix))
|
||||
}
|
||||
}
|
||||
|
||||
func (win *winPlayScr) getScaledWidth() float32 {
|
||||
return float32(win.scr.crit.cropPixels.Bounds().Size().X) * win.getScaling(true)
|
||||
}
|
||||
|
||||
func (win *winPlayScr) getScaledHeight() float32 {
|
||||
return float32(win.scr.crit.cropPixels.Bounds().Size().Y) * win.getScaling(false)
|
||||
}
|
||||
|
||||
func (win *winPlayScr) setScaleFromWindow(sz imgui.Vec2) {
|
||||
// must be called from with a critical section
|
||||
|
||||
winAspectRatio := sz.X / sz.Y
|
||||
|
||||
imageW := float32(win.scr.crit.cropPixels.Bounds().Size().X)
|
||||
imageH := float32(win.scr.crit.cropPixels.Bounds().Size().Y)
|
||||
imageW *= pixelWidth * win.scr.aspectBias
|
||||
aspectRatio := imageW / imageH
|
||||
|
||||
if aspectRatio < winAspectRatio {
|
||||
win.scaling = sz.Y / imageH
|
||||
win.imagePadding = imgui.Vec2{X: float32(int((sz.X - (imageW * win.scaling)) / 2))}
|
||||
} else {
|
||||
win.scaling = sz.X / imageW
|
||||
win.imagePadding = imgui.Vec2{Y: float32(int((sz.Y - (imageH * win.scaling)) / 2))}
|
||||
}
|
||||
}
|
||||
|
||||
func (win *winPlayScr) getScaling(horiz bool) float32 {
|
||||
if horiz {
|
||||
return float32(pixelWidth * win.scr.aspectBias * win.scaling)
|
||||
}
|
||||
return win.scaling
|
||||
}
|
||||
|
||||
func (win *winPlayScr) setScaling(scaling float32) {
|
||||
win.winDim = win.winDim.Times(scaling / win.scaling)
|
||||
win.img.plt.window.SetSize(int32(win.winDim.X), int32(win.winDim.Y))
|
||||
}
|
|
@ -52,8 +52,9 @@ type windowManager struct {
|
|||
windowMenu map[string][]string
|
||||
|
||||
// some windows need to be referenced elsewhere
|
||||
term *winTerm
|
||||
dbgScr *winDbgScr
|
||||
term *winTerm
|
||||
dbgScr *winDbgScr
|
||||
playScr *winPlayScr
|
||||
|
||||
// the position of the screen on the current display. the SDL function
|
||||
// Window.GetPosition() is unsuitable for use in conjunction with imgui
|
||||
|
@ -139,11 +140,6 @@ func newWindowManager(img *SdlImgui) (*windowManager, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// windows called from cartridge specific menus
|
||||
if err := addWindow(newWinStatic, false, windowMenuCart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// associate cartridge types with cartridge specific menus. using cartridge
|
||||
// ID as the key in the windowMenu map
|
||||
wm.windowMenu["DPC"] = append(wm.windowMenu["DPC"], winStaticTitle)
|
||||
|
@ -153,6 +149,14 @@ func newWindowManager(img *SdlImgui) (*windowManager, error) {
|
|||
wm.dbgScr = wm.windows[winDbgScrTitle].(*winDbgScr)
|
||||
wm.term = wm.windows[winTermTitle].(*winTerm)
|
||||
|
||||
// create play window. this is a very special window that never appears
|
||||
// directly in an any menu
|
||||
playWin, err := newWinPlayScr(img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wm.playScr = playWin.(*winPlayScr)
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
|
@ -176,7 +180,6 @@ func (wm *windowManager) destroy() {
|
|||
|
||||
func (wm *windowManager) draw() {
|
||||
if wm.img.lz.VCS != nil && wm.img.lz.Dsm != nil {
|
||||
|
||||
// there's no good place to call the init() function except during a
|
||||
// call to draw. the init() function itself handles
|
||||
wm.init()
|
||||
|
@ -186,6 +189,8 @@ func (wm *windowManager) draw() {
|
|||
wm.windows[w].draw()
|
||||
}
|
||||
}
|
||||
|
||||
wm.playScr.draw()
|
||||
}
|
||||
|
||||
func (wm *windowManager) drawMenu() {
|
||||
|
|
|
@ -28,13 +28,12 @@ const float cursorSize = 2.0;
|
|||
|
||||
void main()
|
||||
{
|
||||
// imgui texture
|
||||
if (ImageType == 0) {
|
||||
// imgui texture
|
||||
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// tv screen texture
|
||||
vec2 coords = Frag_UV.xy;
|
||||
|
||||
|
@ -49,134 +48,136 @@ void main()
|
|||
float lastX;
|
||||
float lastY;
|
||||
|
||||
if (Cropped > 0) {
|
||||
hblank = Hblank / CropDim.x;
|
||||
lastX = LastX / CropDim.x;
|
||||
topScanline = 0;
|
||||
botScanline = (BotScanline - TopScanline) / CropDim.y;
|
||||
|
||||
// the LastY coordinate refers to the full-frame scanline. the cropped
|
||||
// texture however counts from zero at the visible edge so we need to
|
||||
// adjust the lastY value by the TopScanline value.
|
||||
//
|
||||
// note that there's no need to do this for LastX because the
|
||||
// horizontal position is counted from -68 in all instances.
|
||||
lastY = (LastY - TopScanline) / CropDim.y;
|
||||
} else {
|
||||
hblank = Hblank / Dim.x;
|
||||
topScanline = TopScanline / Dim.y;
|
||||
botScanline = BotScanline / Dim.y;
|
||||
lastX = LastX / Dim.x;
|
||||
lastY = LastY / Dim.y;
|
||||
}
|
||||
|
||||
// if the entire frame is being shown then plot the screen guides
|
||||
if (Cropped < 0) {
|
||||
if (isNearEqual(coords.x, hblank, epsilonX) ||
|
||||
isNearEqual(coords.y, topScanline, epsilonY) ||
|
||||
isNearEqual(coords.y, botScanline, epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 0.0;
|
||||
Out_Color.b = 0.0;
|
||||
Out_Color.a = 0.5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// when ShowScreenDraw is true then there's some additional image
|
||||
// processing we need to perform:
|
||||
// - fade anything left over from previous frame
|
||||
// - draw cursor indicator
|
||||
if (ShowScreenDraw > 0) {
|
||||
|
||||
// draw cursor if pixel is at the last x/y position
|
||||
if (isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, lastX, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 1.0;
|
||||
Out_Color.b = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// draw off-screen cursor for HBLANK
|
||||
if (lastX < 0 && isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// for cropped screens there are a few more conditions that we need to
|
||||
// consider for drawing an off-screen cursor
|
||||
if (ImageType == 1 || ImageType == 2) {
|
||||
if (Cropped > 0) {
|
||||
hblank = Hblank / CropDim.x;
|
||||
lastX = LastX / CropDim.x;
|
||||
topScanline = 0;
|
||||
botScanline = (BotScanline - TopScanline) / CropDim.y;
|
||||
|
||||
// when VBLANK is active but HBLANK is off
|
||||
if (isNearEqual(coords.x, lastX, cursorSize * epsilonX)) {
|
||||
// top of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// when HBLANK and VBLANK are both active
|
||||
if (lastX < 0 && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
// top/left corner of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom/left corner of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// the LastY coordinate refers to the full-frame scanline. the cropped
|
||||
// texture however counts from zero at the visible edge so we need to
|
||||
// adjust the lastY value by the TopScanline value.
|
||||
//
|
||||
// note that there's no need to do this for LastX because the
|
||||
// horizontal position is counted from -68 in all instances.
|
||||
lastY = (LastY - TopScanline) / CropDim.y;
|
||||
} else {
|
||||
hblank = Hblank / Dim.x;
|
||||
topScanline = TopScanline / Dim.y;
|
||||
botScanline = BotScanline / Dim.y;
|
||||
lastX = LastX / Dim.x;
|
||||
lastY = LastY / Dim.y;
|
||||
}
|
||||
|
||||
|
||||
// draw pixels with faded alpha. these pixels will be those left over
|
||||
// from the previous frame.
|
||||
//
|
||||
// as a special case, we ignore the first scanline and do not fade the
|
||||
// previous image on a brand new frame. note that we're using the
|
||||
// unadjusted LastY value for this
|
||||
if (LastY > 0) {
|
||||
if (coords.y > lastY || (isNearEqual(coords.y, lastY, epsilonY) && coords.x > lastX)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
// if the entire frame is being shown then plot the screen guides
|
||||
if (Cropped < 0) {
|
||||
if (isNearEqual(coords.x, hblank, epsilonX) ||
|
||||
isNearEqual(coords.y, topScanline, epsilonY) ||
|
||||
isNearEqual(coords.y, botScanline, epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 0.0;
|
||||
Out_Color.b = 0.0;
|
||||
Out_Color.a = 0.5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if this is the overlay texture then we're done
|
||||
if (ImageType == 2) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
// when ShowScreenDraw is true then there's some additional image
|
||||
// processing we need to perform:
|
||||
// - fade anything left over from previous frame
|
||||
// - draw cursor indicator
|
||||
if (ShowScreenDraw > 0) {
|
||||
|
||||
// draw cursor if pixel is at the last x/y position
|
||||
if (isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, lastX, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 1.0;
|
||||
Out_Color.b = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// if pixel-perfect rendering is selected then there's nothing much more to do
|
||||
if (PixelPerfect == 1) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
// draw off-screen cursor for HBLANK
|
||||
if (lastX < 0 && isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// only apply CRT effects on the "cropped" area of the screen. we can think
|
||||
// of the cropped area as the "play" area
|
||||
if (Cropped < 0 && (coords.x < hblank || coords.y < topScanline || coords.y > botScanline)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
// for cropped screens there are a few more conditions that we need to
|
||||
// consider for drawing an off-screen cursor
|
||||
if (Cropped > 0) {
|
||||
|
||||
// when VBLANK is active but HBLANK is off
|
||||
if (isNearEqual(coords.x, lastX, cursorSize * epsilonX)) {
|
||||
// top of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// when HBLANK and VBLANK are both active
|
||||
if (lastX < 0 && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
// top/left corner of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom/left corner of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// draw pixels with faded alpha. these pixels will be those left over
|
||||
// from the previous frame.
|
||||
//
|
||||
// as a special case, we ignore the first scanline and do not fade the
|
||||
// previous image on a brand new frame. note that we're using the
|
||||
// unadjusted LastY value for this
|
||||
if (LastY > 0) {
|
||||
if (coords.y > lastY || (isNearEqual(coords.y, lastY, epsilonY) && coords.x > lastX)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
Out_Color.a = 0.5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if this is the overlay texture then we're done
|
||||
if (ImageType == 2) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
|
||||
// if pixel-perfect rendering is selected then there's nothing much more to do
|
||||
if (PixelPerfect == 1) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
|
||||
// only apply CRT effects on the "cropped" area of the screen. we can think
|
||||
// of the cropped area as the "play" area
|
||||
if (Cropped < 0 && (coords.x < hblank || coords.y < topScanline || coords.y > botScanline)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// the remainder of the shader are the CRT effects and we only apply these
|
||||
|
@ -215,16 +216,18 @@ void main()
|
|||
}
|
||||
|
||||
// bend screen
|
||||
/* float xbend = 6.0; */
|
||||
/* float ybend = 5.0; */
|
||||
/* coords = (coords - 0.5) * 1.85; */
|
||||
/* coords *= 1.11; */
|
||||
/* coords.x *= 1.0 + pow((abs(coords.y) / xbend), 2.0); */
|
||||
/* coords.y *= 1.0 + pow((abs(coords.x) / ybend), 2.0); */
|
||||
/* coords = (coords / 2.05) + 0.5; */
|
||||
/* if (ImageType == 3) { */
|
||||
/* float xbend = 6.0; */
|
||||
/* float ybend = 5.0; */
|
||||
/* coords = (coords - 0.5) * 1.85; */
|
||||
/* coords *= 1.11; */
|
||||
/* coords.x *= 1.0 + pow((abs(coords.y) / xbend), 2.0); */
|
||||
/* coords.y *= 1.0 + pow((abs(coords.x) / ybend), 2.0); */
|
||||
/* coords = (coords / 2.05) + 0.5; */
|
||||
|
||||
/* // crop tiling */
|
||||
/* if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0 ) { */
|
||||
/* discard; */
|
||||
/* // crop tiling */
|
||||
/* if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0 ) { */
|
||||
/* discard; */
|
||||
/* } */
|
||||
/* } */
|
||||
}
|
||||
|
|
|
@ -49,13 +49,12 @@ const float cursorSize = 2.0;
|
|||
|
||||
void main()
|
||||
{
|
||||
// imgui texture
|
||||
if (ImageType == 0) {
|
||||
// imgui texture
|
||||
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// tv screen texture
|
||||
vec2 coords = Frag_UV.xy;
|
||||
|
||||
|
@ -70,134 +69,136 @@ void main()
|
|||
float lastX;
|
||||
float lastY;
|
||||
|
||||
if (Cropped > 0) {
|
||||
hblank = Hblank / CropDim.x;
|
||||
lastX = LastX / CropDim.x;
|
||||
topScanline = 0;
|
||||
botScanline = (BotScanline - TopScanline) / CropDim.y;
|
||||
|
||||
// the LastY coordinate refers to the full-frame scanline. the cropped
|
||||
// texture however counts from zero at the visible edge so we need to
|
||||
// adjust the lastY value by the TopScanline value.
|
||||
//
|
||||
// note that there's no need to do this for LastX because the
|
||||
// horizontal position is counted from -68 in all instances.
|
||||
lastY = (LastY - TopScanline) / CropDim.y;
|
||||
} else {
|
||||
hblank = Hblank / Dim.x;
|
||||
topScanline = TopScanline / Dim.y;
|
||||
botScanline = BotScanline / Dim.y;
|
||||
lastX = LastX / Dim.x;
|
||||
lastY = LastY / Dim.y;
|
||||
}
|
||||
|
||||
// if the entire frame is being shown then plot the screen guides
|
||||
if (Cropped < 0) {
|
||||
if (isNearEqual(coords.x, hblank, epsilonX) ||
|
||||
isNearEqual(coords.y, topScanline, epsilonY) ||
|
||||
isNearEqual(coords.y, botScanline, epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 0.0;
|
||||
Out_Color.b = 0.0;
|
||||
Out_Color.a = 0.5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// when ShowScreenDraw is true then there's some additional image
|
||||
// processing we need to perform:
|
||||
// - fade anything left over from previous frame
|
||||
// - draw cursor indicator
|
||||
if (ShowScreenDraw > 0) {
|
||||
|
||||
// draw cursor if pixel is at the last x/y position
|
||||
if (isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, lastX, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 1.0;
|
||||
Out_Color.b = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// draw off-screen cursor for HBLANK
|
||||
if (lastX < 0 && isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// for cropped screens there are a few more conditions that we need to
|
||||
// consider for drawing an off-screen cursor
|
||||
if (ImageType == 1 || ImageType == 2) {
|
||||
if (Cropped > 0) {
|
||||
hblank = Hblank / CropDim.x;
|
||||
lastX = LastX / CropDim.x;
|
||||
topScanline = 0;
|
||||
botScanline = (BotScanline - TopScanline) / CropDim.y;
|
||||
|
||||
// when VBLANK is active but HBLANK is off
|
||||
if (isNearEqual(coords.x, lastX, cursorSize * epsilonX)) {
|
||||
// top of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// when HBLANK and VBLANK are both active
|
||||
if (lastX < 0 && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
// top/left corner of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom/left corner of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// the LastY coordinate refers to the full-frame scanline. the cropped
|
||||
// texture however counts from zero at the visible edge so we need to
|
||||
// adjust the lastY value by the TopScanline value.
|
||||
//
|
||||
// note that there's no need to do this for LastX because the
|
||||
// horizontal position is counted from -68 in all instances.
|
||||
lastY = (LastY - TopScanline) / CropDim.y;
|
||||
} else {
|
||||
hblank = Hblank / Dim.x;
|
||||
topScanline = TopScanline / Dim.y;
|
||||
botScanline = BotScanline / Dim.y;
|
||||
lastX = LastX / Dim.x;
|
||||
lastY = LastY / Dim.y;
|
||||
}
|
||||
|
||||
|
||||
// draw pixels with faded alpha. these pixels will be those left over
|
||||
// from the previous frame.
|
||||
//
|
||||
// as a special case, we ignore the first scanline and do not fade the
|
||||
// previous image on a brand new frame. note that we're using the
|
||||
// unadjusted LastY value for this
|
||||
if (LastY > 0) {
|
||||
if (coords.y > lastY || (isNearEqual(coords.y, lastY, epsilonY) && coords.x > lastX)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
// if the entire frame is being shown then plot the screen guides
|
||||
if (Cropped < 0) {
|
||||
if (isNearEqual(coords.x, hblank, epsilonX) ||
|
||||
isNearEqual(coords.y, topScanline, epsilonY) ||
|
||||
isNearEqual(coords.y, botScanline, epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 0.0;
|
||||
Out_Color.b = 0.0;
|
||||
Out_Color.a = 0.5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if this is the overlay texture then we're done
|
||||
if (ImageType == 2) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
// when ShowScreenDraw is true then there's some additional image
|
||||
// processing we need to perform:
|
||||
// - fade anything left over from previous frame
|
||||
// - draw cursor indicator
|
||||
if (ShowScreenDraw > 0) {
|
||||
|
||||
// draw cursor if pixel is at the last x/y position
|
||||
if (isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, lastX, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.g = 1.0;
|
||||
Out_Color.b = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// if pixel-perfect rendering is selected then there's nothing much more to do
|
||||
if (PixelPerfect == 1) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
// draw off-screen cursor for HBLANK
|
||||
if (lastX < 0 && isNearEqual(coords.y, lastY, cursorSize*epsilonY) && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// only apply CRT effects on the "cropped" area of the screen. we can think
|
||||
// of the cropped area as the "play" area
|
||||
if (Cropped < 0 && (coords.x < hblank || coords.y < topScanline || coords.y > botScanline)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
// for cropped screens there are a few more conditions that we need to
|
||||
// consider for drawing an off-screen cursor
|
||||
if (Cropped > 0) {
|
||||
|
||||
// when VBLANK is active but HBLANK is off
|
||||
if (isNearEqual(coords.x, lastX, cursorSize * epsilonX)) {
|
||||
// top of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// when HBLANK and VBLANK are both active
|
||||
if (lastX < 0 && isNearEqual(coords.x, 0, cursorSize*epsilonX)) {
|
||||
// top/left corner of screen
|
||||
if (lastY < 0 && isNearEqual(coords.y, 0, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// bottom/left corner of screen
|
||||
if (lastY > botScanline && isNearEqual(coords.y, botScanline, cursorSize*epsilonY)) {
|
||||
Out_Color.r = 1.0;
|
||||
Out_Color.a = AnimTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// draw pixels with faded alpha. these pixels will be those left over
|
||||
// from the previous frame.
|
||||
//
|
||||
// as a special case, we ignore the first scanline and do not fade the
|
||||
// previous image on a brand new frame. note that we're using the
|
||||
// unadjusted LastY value for this
|
||||
if (LastY > 0) {
|
||||
if (coords.y > lastY || (isNearEqual(coords.y, lastY, epsilonY) && coords.x > lastX)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
Out_Color.a = 0.5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if this is the overlay texture then we're done
|
||||
if (ImageType == 2) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
|
||||
// if pixel-perfect rendering is selected then there's nothing much more to do
|
||||
if (PixelPerfect == 1) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
|
||||
// only apply CRT effects on the "cropped" area of the screen. we can think
|
||||
// of the cropped area as the "play" area
|
||||
if (Cropped < 0 && (coords.x < hblank || coords.y < topScanline || coords.y > botScanline)) {
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// the remainder of the shader are the CRT effects and we only apply these
|
||||
|
@ -236,17 +237,19 @@ void main()
|
|||
}
|
||||
|
||||
// bend screen
|
||||
/* float xbend = 6.0; */
|
||||
/* float ybend = 5.0; */
|
||||
/* coords = (coords - 0.5) * 1.85; */
|
||||
/* coords *= 1.11; */
|
||||
/* coords.x *= 1.0 + pow((abs(coords.y) / xbend), 2.0); */
|
||||
/* coords.y *= 1.0 + pow((abs(coords.x) / ybend), 2.0); */
|
||||
/* coords = (coords / 2.05) + 0.5; */
|
||||
/* if (ImageType == 3) { */
|
||||
/* float xbend = 6.0; */
|
||||
/* float ybend = 5.0; */
|
||||
/* coords = (coords - 0.5) * 1.85; */
|
||||
/* coords *= 1.11; */
|
||||
/* coords.x *= 1.0 + pow((abs(coords.y) / xbend), 2.0); */
|
||||
/* coords.y *= 1.0 + pow((abs(coords.x) / ybend), 2.0); */
|
||||
/* coords = (coords / 2.05) + 0.5; */
|
||||
|
||||
/* // crop tiling */
|
||||
/* if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0 ) { */
|
||||
/* discard; */
|
||||
/* // crop tiling */
|
||||
/* if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0 ) { */
|
||||
/* discard; */
|
||||
/* } */
|
||||
/* } */
|
||||
}
|
||||
`
|
||||
|
|
|
@ -142,13 +142,13 @@ func (pf *playfield) pixel() (bool, uint8) {
|
|||
// cycles beyond the trigger point described in the TIA_HW_Notes.txt
|
||||
// document. we believe this has the same effect.
|
||||
switch pf.Region {
|
||||
case 0:
|
||||
case RegionOffScreen:
|
||||
pf.Idx = pf.hsync.Count()
|
||||
pf.currentPixelIsOn = false
|
||||
case 1:
|
||||
case RegionLeft:
|
||||
pf.Idx = pf.hsync.Count() - 17
|
||||
newPixel = true
|
||||
case 2:
|
||||
case RegionRight:
|
||||
pf.Idx = pf.hsync.Count() - 37
|
||||
newPixel = true
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ type playmode struct {
|
|||
}
|
||||
|
||||
// Play is a quick of setting up a playable instance of the emulator.
|
||||
func Play(tv television.Television, scr gui.GUI, showOnStable bool, newRecording bool, cartload cartridgeloader.Loader, patchFile string, hiscoreServer bool) error {
|
||||
func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload cartridgeloader.Loader, patchFile string, hiscoreServer bool) error {
|
||||
var transcript string
|
||||
|
||||
// if supplied cartridge name is actually a playback file then set
|
||||
|
@ -158,11 +158,7 @@ func Play(tv television.Television, scr gui.GUI, showOnStable bool, newRecording
|
|||
}
|
||||
|
||||
// request television visibility
|
||||
request := gui.ReqSetVisibility
|
||||
if showOnStable {
|
||||
request = gui.ReqSetVisibleOnStable
|
||||
}
|
||||
err = scr.SetFeature(request, true)
|
||||
err = scr.SetFeature(gui.ReqSetVisibility, true)
|
||||
if err != nil {
|
||||
return errors.New(errors.PlayError, err)
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (e entryMap) String() string {
|
|||
|
||||
for _, k := range sorted {
|
||||
v := e[k]
|
||||
s.WriteString(fmt.Sprintf("%s%s%#v\n", k, keySep, v))
|
||||
s.WriteString(fmt.Sprintf("%s%s%s\n", k, keySep, v))
|
||||
}
|
||||
|
||||
return s.String()
|
||||
|
@ -151,8 +151,9 @@ func (dsk *Disk) Load() error {
|
|||
}
|
||||
|
||||
// underlying function to load preference value froms disk. the limit boolean
|
||||
// controls whether to load all valid preference values from the file
|
||||
// or to ignore those values not already in the entryMap
|
||||
// controls whether to load all valid preference values from the file or to
|
||||
// ignore those values not already in the entryMap. limit=false is used by the
|
||||
// save() function in order to avoid clobbering unknown entries.
|
||||
func load(path string, entries *entryMap, limit bool) error {
|
||||
// open existing prefs file
|
||||
f, err := os.Open(path)
|
||||
|
|
|
@ -30,7 +30,6 @@ import (
|
|||
// types support by the prefs system must implement the pref interface
|
||||
type pref interface {
|
||||
fmt.Stringer
|
||||
fmt.GoStringer
|
||||
Set(value interface{}) error
|
||||
Get() interface{}
|
||||
}
|
||||
|
@ -45,11 +44,6 @@ func (p Bool) String() string {
|
|||
return fmt.Sprintf("%v", p.value)
|
||||
}
|
||||
|
||||
// GoString implements the fmt.GoStringer interface
|
||||
func (p Bool) GoString() string {
|
||||
return p.String()
|
||||
}
|
||||
|
||||
// Set new value to Bool type. New value must be of type bool or string. A
|
||||
// string value of anything other than "true" (case insensitive) will set the
|
||||
// value to false.
|
||||
|
@ -85,11 +79,6 @@ func (p String) String() string {
|
|||
return p.value
|
||||
}
|
||||
|
||||
// GoString implements the fmt.GoStringer interface
|
||||
func (p String) GoString() string {
|
||||
return p.String()
|
||||
}
|
||||
|
||||
// Set new value to String type. New value must be of type string.
|
||||
func (p *String) Set(v interface{}) error {
|
||||
p.value = fmt.Sprintf("%s", v)
|
||||
|
@ -111,11 +100,6 @@ func (p Int) String() string {
|
|||
return fmt.Sprintf("%d", p.value)
|
||||
}
|
||||
|
||||
// GoInt implements the fmt.GoStringer interface
|
||||
func (p Int) GoString() string {
|
||||
return p.String()
|
||||
}
|
||||
|
||||
// Set new value to Int type. New value can be an int or string.
|
||||
func (p *Int) Set(v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
|
@ -159,11 +143,6 @@ func (p Generic) String() string {
|
|||
return p.get()
|
||||
}
|
||||
|
||||
// GoString implements the fmt.GoStringer interface
|
||||
func (p Generic) GoString() string {
|
||||
return p.String()
|
||||
}
|
||||
|
||||
// Set triggers the set value procedure for the generic type
|
||||
func (p *Generic) Set(v interface{}) error {
|
||||
return p.set(v.(string))
|
||||
|
|
Loading…
Reference in a new issue