Skip to content

Instantly share code, notes, and snippets.

@nwidger
Created January 13, 2015 00:19
Show Gist options
  • Save nwidger/690b33f04f67736a0627 to your computer and use it in GitHub Desktop.
Save nwidger/690b33f04f67736a0627 to your computer and use it in GitHub Desktop.
// +build !sdl
package nes
import (
"image"
"image/color"
"math"
"azul3d.org/gfx.v1"
"azul3d.org/gfx/window.v2"
"azul3d.org/keyboard.v1"
"azul3d.org/lmath.v1"
)
var Azul3DPalette []color.RGBA = []color.RGBA{
color.RGBA{0x66, 0x66, 0x66, 0xff},
color.RGBA{0x00, 0x2A, 0x88, 0xff},
color.RGBA{0x14, 0x12, 0xA7, 0xff},
color.RGBA{0x3B, 0x00, 0xA4, 0xff},
color.RGBA{0x5C, 0x00, 0x7E, 0xff},
color.RGBA{0x6E, 0x00, 0x40, 0xff},
color.RGBA{0x6C, 0x06, 0x00, 0xff},
color.RGBA{0x56, 0x1D, 0x00, 0xff},
color.RGBA{0x33, 0x35, 0x00, 0xff},
color.RGBA{0x0B, 0x48, 0x00, 0xff},
color.RGBA{0x00, 0x52, 0x00, 0xff},
color.RGBA{0x00, 0x4F, 0x08, 0xff},
color.RGBA{0x00, 0x40, 0x4D, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0xAD, 0xAD, 0xAD, 0xff},
color.RGBA{0x15, 0x5F, 0xD9, 0xff},
color.RGBA{0x42, 0x40, 0xFF, 0xff},
color.RGBA{0x75, 0x27, 0xFE, 0xff},
color.RGBA{0xA0, 0x1A, 0xCC, 0xff},
color.RGBA{0xB7, 0x1E, 0x7B, 0xff},
color.RGBA{0xB5, 0x31, 0x20, 0xff},
color.RGBA{0x99, 0x4E, 0x00, 0xff},
color.RGBA{0x6B, 0x6D, 0x00, 0xff},
color.RGBA{0x38, 0x87, 0x00, 0xff},
color.RGBA{0x0C, 0x93, 0x00, 0xff},
color.RGBA{0x00, 0x8F, 0x32, 0xff},
color.RGBA{0x00, 0x7C, 0x8D, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0xFF, 0xFE, 0xFF, 0xff},
color.RGBA{0x64, 0xB0, 0xFF, 0xff},
color.RGBA{0x92, 0x90, 0xFF, 0xff},
color.RGBA{0xC6, 0x76, 0xFF, 0xff},
color.RGBA{0xF3, 0x6A, 0xFF, 0xff},
color.RGBA{0xFE, 0x6E, 0xCC, 0xff},
color.RGBA{0xFE, 0x81, 0x70, 0xff},
color.RGBA{0xEA, 0x9E, 0x22, 0xff},
color.RGBA{0xBC, 0xBE, 0x00, 0xff},
color.RGBA{0x88, 0xD8, 0x00, 0xff},
color.RGBA{0x5C, 0xE4, 0x30, 0xff},
color.RGBA{0x45, 0xE0, 0x82, 0xff},
color.RGBA{0x48, 0xCD, 0xDE, 0xff},
color.RGBA{0x4F, 0x4F, 0x4F, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0xFF, 0xFE, 0xFF, 0xff},
color.RGBA{0xC0, 0xDF, 0xFF, 0xff},
color.RGBA{0xD3, 0xD2, 0xFF, 0xff},
color.RGBA{0xE8, 0xC8, 0xFF, 0xff},
color.RGBA{0xFB, 0xC2, 0xFF, 0xff},
color.RGBA{0xFE, 0xC4, 0xEA, 0xff},
color.RGBA{0xFE, 0xCC, 0xC5, 0xff},
color.RGBA{0xF7, 0xD8, 0xA5, 0xff},
color.RGBA{0xE4, 0xE5, 0x94, 0xff},
color.RGBA{0xCF, 0xEF, 0x96, 0xff},
color.RGBA{0xBD, 0xF4, 0xAB, 0xff},
color.RGBA{0xB3, 0xF3, 0xCC, 0xff},
color.RGBA{0xB5, 0xEB, 0xF2, 0xff},
color.RGBA{0xB8, 0xB8, 0xB8, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
}
type Azul3DVideo struct {
input chan []uint8
width, height int
palette []color.RGBA
events chan Event
overscan bool
caption string
}
func NewVideo(caption string, events chan Event) (video *Azul3DVideo, err error) {
video = &Azul3DVideo{
input: make(chan []uint8, 128),
events: events,
palette: Azul3DPalette,
overscan: true,
caption: caption,
}
return
}
func convertColor(c color.Color) gfx.Color {
r, g, b, a := c.RGBA()
return gfx.Color{
float32(r) / float32(math.MaxUint16),
float32(g) / float32(math.MaxUint16),
float32(b) / float32(math.MaxUint16),
float32(a) / float32(math.MaxUint16),
}
}
func (video *Azul3DVideo) Events() chan Event {
return video.events
}
func (video *Azul3DVideo) Input() chan []uint8 {
return video.input
}
func (video *Azul3DVideo) frameWidth() int {
width := 256
if video.overscan {
width -= 16
}
return width
}
func (video *Azul3DVideo) frameHeight() int {
height := 240
if video.overscan {
height -= 16
}
return height
}
var glslVert = []byte(`
#version 120
attribute vec3 Vertex;
attribute vec2 TexCoord0;
uniform mat4 MVP;
varying vec2 tc0;
void main()
{
tc0 = TexCoord0;
gl_Position = MVP * vec4(Vertex, 1.0);
}
`)
var glslFrag = []byte(`
#version 120
varying vec2 tc0;
uniform sampler2D Texture0;
uniform vec4 palette[64];
uniform vec3 scale;
uniform vec3 shift;
void main() {
vec2 tc = scale.xy * tc0;
tc += shift.xy;
vec4 t = texture2D(Texture0, tc);
int i = int(t.r * 256.0);
gl_FragColor = palette[i];
}
`)
func (video *Azul3DVideo) handleInput(ev keyboard.StateEvent, w *window.Window) (running bool) {
var event Event
setSize := func(width, height int) {
props := (*w).Props()
props.SetSize(width, height)
(*w).Request(props)
}
running = true
if ev.State == keyboard.Down {
switch ev.Key {
case keyboard.Tilde:
video.overscan = !video.overscan
case keyboard.One:
setSize(256, 240)
case keyboard.Two:
setSize(512, 480)
case keyboard.Three:
setSize(768, 720)
case keyboard.Four:
setSize(1024, 960)
case keyboard.Five:
setSize(2560, 1440)
case keyboard.P:
event = &PauseEvent{}
case keyboard.N:
event = &FrameStepEvent{}
case keyboard.Q:
running = false
event = &QuitEvent{}
case keyboard.L:
event = &SavePatternTablesEvent{}
case keyboard.R:
event = &ResetEvent{}
case keyboard.S:
event = &RecordEvent{}
case keyboard.D:
event = &StopEvent{}
case keyboard.NumAdd:
event = &AudioRecordEvent{}
case keyboard.NumSubtract:
event = &AudioStopEvent{}
case keyboard.O:
event = &CPUDecodeEvent{}
case keyboard.I:
event = &PPUDecodeEvent{}
case keyboard.Nine:
event = &ShowBackgroundEvent{}
case keyboard.Zero:
event = &ShowSpritesEvent{}
case keyboard.F1:
event = &SaveStateEvent{}
case keyboard.F5:
event = &LoadStateEvent{}
case keyboard.F8:
event = &FastForwardEvent{}
case keyboard.F9:
event = &FPS100Event{}
case keyboard.F10:
event = &FPS75Event{}
case keyboard.F11:
event = &FPS50Event{}
case keyboard.F12:
event = &FPS25Event{}
case keyboard.NumZero:
event = &MuteEvent{}
case keyboard.NumOne:
event = &MutePulse1Event{}
case keyboard.NumTwo:
event = &MutePulse2Event{}
case keyboard.NumThree:
event = &MuteTriangleEvent{}
case keyboard.NumFour:
event = &MuteNoiseEvent{}
}
}
if event == nil {
button := One
switch ev.Key {
case keyboard.Z:
button = A
case keyboard.X:
button = B
case keyboard.Enter:
button = Start
case keyboard.RightShift:
button = Select
case keyboard.ArrowUp:
button = Up
case keyboard.ArrowDown:
button = Down
case keyboard.ArrowLeft:
button = Left
case keyboard.ArrowRight:
button = Right
}
event = &ControllerEvent{
button: button,
down: ev.State == keyboard.Down,
}
}
if event != nil {
video.events <- event
}
return
}
func (video *Azul3DVideo) Run() {
colors := []uint8{}
running := true
gfxLoop := func(w window.Window, r gfx.Renderer) {
r.Clock().SetMaxFrameRate(DEFAULT_FPS)
// Create a simple shader.
shader := gfx.NewShader("SimpleShader")
shader.GLSLVert = glslVert
shader.GLSLFrag = glslFrag
// Setup a camera using an orthographic projection.
camera := gfx.NewCamera()
camNear := 0.01
camFar := 1000.0
camera.SetOrtho(r.Bounds(), camNear, camFar)
// Move the camera back two units away from the card.
camera.SetPos(lmath.Vec3{0, -2, 0})
// Create a card mesh.
cardMesh := gfx.NewMesh()
cardMesh.Vertices = []gfx.Vec3{
// Left triangle.
{-1, 0, 1}, // Left-Top
{-1, 0, -1}, // Left-Bottom
{1, 0, -1}, // Right-Bottom
// Right triangle.
{-1, 0, 1}, // Left-Top
{1, 0, -1}, // Right-Bottom
{1, 0, 1}, // Right-Top
}
cardMesh.TexCoords = []gfx.TexCoordSet{
{
Slice: []gfx.TexCoord{
// Left triangle.
{0, 0},
{0, 1},
{1, 1},
// Right triangle.
{0, 0},
{1, 1},
{1, 0},
},
},
}
// Create a card object.
card := gfx.NewObject()
card.Shader = shader
card.Textures = []*gfx.Texture{nil}
card.Meshes = []*gfx.Mesh{cardMesh}
img := image.NewRGBA(image.Rect(0, 0, 256, 240))
palette := []gfx.Color{}
for _, c := range Azul3DPalette {
palette = append(palette, gfx.ColorModel.Convert(c).(gfx.Color))
}
updateTex := func() {
for i, c := range colors {
img.Pix[i<<2] = c
}
scale := gfx.Vec3{1.0, 1.0, 0.0}
shift := gfx.Vec3{0, 0, 0}
if video.overscan {
var cropPx float32 = 8.0
nx := 1.0 / float32(img.Bounds().Dx())
ny := 1.0 / float32(img.Bounds().Dy())
scale = gfx.Vec3{
X: 1.0 - (nx * cropPx * 2),
Y: 1.0 - (ny * cropPx * 2),
}
shift = gfx.Vec3{
X: nx * cropPx,
Y: ny * cropPx,
}
}
shader.Inputs["scale"] = scale
shader.Inputs["shift"] = shift
shader.Inputs["palette"] = palette
// Create new texture and ask the renderer to load it. We don't use DXT
// compression because those textures cannot be downloaded.
tex := gfx.NewTexture()
tex.Source = img
tex.MinFilter = gfx.Nearest
tex.MagFilter = gfx.Nearest
onLoad := make(chan *gfx.Texture, 1)
r.LoadTexture(tex, onLoad)
<-onLoad
// Swap the texture with the old one on the card.
card.Lock()
card.Textures[0] = tex
card.Unlock()
}
updateTex()
go func() {
// Create an event mask for the events we are interested in.
evMask := window.KeyboardStateEvents
// Create a channel of events.
events := make(chan window.Event, 256)
// Have the window notify our channel whenever events occur.
w.Notify(events, evMask)
for running {
select {
case colors = <-video.input:
// We drop any pending frames and grab the most recent one. This is
// because frame display is tied to the runProcessors loop and can
// cause audio stuttering.
frameDrop:
for {
select {
case colors = <-video.input:
default:
break frameDrop
}
}
// Update the texture using the most recent frame.
updateTex()
case e := <-events:
switch ev := e.(type) {
case keyboard.StateEvent:
running = video.handleInput(ev, &w)
}
}
}
}()
for running {
// Center the card in the window.
b := r.Bounds()
camera.SetOrtho(b, camNear, camFar)
card.SetPos(lmath.Vec3{float64(b.Dx()) / 2.0, 0, float64(b.Dy()) / 2.0})
// Scale the card to fit the window, we divide by two because the
// card is two units wide.
var s float64
if b.Dy() > b.Dx() {
s = float64(b.Dx()) / 2.0
} else {
s = float64(b.Dy()) / 2.0
}
card.SetScale(lmath.Vec3{s, s, s})
// clear the entire area (empty rectangle means "the whole area").
r.Clear(image.Rect(0, 0, 0, 0), gfx.Color{0, 0, 0, 1})
r.ClearDepth(image.Rect(0, 0, 0, 0), 1.0)
// Draw the card to the screen.
r.Draw(image.Rect(0, 0, 0, 0), card, camera)
// Render the whole frame.
r.Render()
}
w.Close()
}
props := window.NewProps()
props.SetSize(512, 480)
props.SetTitle("nintengo - " + video.caption + " - {FPS}")
window.Run(gfxLoop, props)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment