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:
JetSetIlly 2020-05-19 22:19:28 +01:00
parent 9bc2b3cb08
commit b92a8202ce
30 changed files with 836 additions and 520 deletions

View file

@ -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)

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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)

View file

@ -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

View file

@ -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
)

View file

@ -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:

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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
View 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))
}

View file

@ -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() {

View file

@ -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; */
/* } */
/* } */
}

View file

@ -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; */
/* } */
/* } */
}
`

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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))