-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
37 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,6 @@ Concurrent software rendering, triangle rasterization and pixel buffer manipulat | |
|
||
![software rendered duck with glitch effect](img/glitch.png) ![software rendered beveled cube](img/cube.png) | ||
|
||
|
||
## Features and limitations | ||
|
||
* Can draw software-rendered triangles concurrently, using goroutines. The work of drawing the triangles is divided on the available CPU cores. | ||
|
@@ -22,180 +21,69 @@ Concurrent software rendering, triangle rasterization and pixel buffer manipulat | |
* Tested together with SDL2, but can be used with any graphics library that can output pixels from a pixel buffer. | ||
* The software rendering of 3D graphics in the screenshot above is provided by [fauxgl](https://github.com/fogleman/fauxgl). The outputs from this can be combined with effects from `pixelpusher`. | ||
|
||
Requires Go 1.3 or later, and SDL2. | ||
## Getting started | ||
|
||
## Example, using pixelpusher and [SDL2](https://github.com/veandco/go-sdl2): | ||
A single red pixel in the upper left corner: | ||
|
||
```go | ||
``` | ||
package main | ||
import ( | ||
"fmt" | ||
"image/color" | ||
"math/rand" | ||
"os" | ||
"runtime" | ||
"time" | ||
|
||
"github.com/veandco/go-sdl2/sdl" | ||
"github.com/xyproto/pixelpusher" | ||
"github.com/xyproto/sdl2utils" | ||
) | ||
const ( | ||
// Size of "worldspace pixels", measured in "screenspace pixels" | ||
pixelscale = 4 | ||
func onDraw(gfx *pixelpusher.Config) error { | ||
// x, y, r, g, b | ||
return pixelpusher.Plot(gfx, 0, 0, 255, 0, 0) | ||
} | ||
// The resolution (worldspace) | ||
width = 320 | ||
height = 200 | ||
func main() { | ||
gfx := pixelpusher.New("Red Pixel") | ||
gfx.Run(onDraw, nil, nil, nil) | ||
} | ||
``` | ||
|
||
// The width of the pixel buffer, used when calculating where to place pixels (y*pitch+x) | ||
pitch = width | ||
A very simple drawing program: | ||
|
||
// Target framerate | ||
frameRate = 60 | ||
```go | ||
package main | ||
|
||
// Alpha value for opaque colors | ||
opaque = 255 | ||
) | ||
import ( | ||
"errors" | ||
|
||
var ( | ||
// Convenience functions for returning random numbers | ||
rw = func() int32 { return rand.Int31n(width) } | ||
rh = func() int32 { return rand.Int31n(height) } | ||
rb = func() uint8 { return uint8(rand.Intn(255)) } | ||
"github.com/xyproto/pixelpusher" | ||
) | ||
|
||
// DrawAll fills the pixel buffer with pixels. | ||
// "cores" is how many CPU cores should be targeted when drawing triangles, | ||
// by launching the same number of goroutines. | ||
func DrawAll(pixels []uint32, cores int) { | ||
|
||
// Draw a triangle, concurrently | ||
pixelpusher.Triangle(cores, pixels, rw(), rh(), rw(), rh(), rw(), rh(), color.RGBA{rb(), rb(), rb(), opaque}, pitch) | ||
var x, y = 160, 100 | ||
|
||
// Draw a line and a red pixel, without caring about which order they appear in, or if they will complete before the next frame is drawn | ||
go pixelpusher.Line(pixels, rw(), rh(), rw(), rh(), color.RGBA{0xff, 0xff, 0, opaque}, pitch) | ||
go pixelpusher.Pixel(pixels, rw(), rh(), color.RGBA{0xff, 0x0, 0x0, opaque}, pitch) | ||
func onDraw(gfx *pixelpusher.Config) error { | ||
return pixelpusher.Plot(gfx, x, y, 255, 0, 0) | ||
} | ||
|
||
func run() int { | ||
|
||
sdl.Init(uint32(sdl.INIT_VIDEO)) | ||
|
||
var ( | ||
window *sdl.Window | ||
renderer *sdl.Renderer | ||
err error | ||
) | ||
|
||
window, err = sdl.CreateWindow("Pixels!", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, int32(width*pixelscale), int32(height*pixelscale), sdl.WINDOW_SHOWN) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Failed to create window: %s\n", err) | ||
return 1 | ||
func onPress(left, right, up, down, space, enter, esc bool) error { | ||
if up { | ||
y-- | ||
} else if down { | ||
y++ | ||
} | ||
defer window.Destroy() | ||
|
||
renderer, err = sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Failed to create renderer: %s\n", err) | ||
return 1 | ||
if left { | ||
x-- | ||
} else if right { | ||
x++ | ||
} | ||
defer renderer.Destroy() | ||
|
||
// Fill the render buffer with color #FF00CC | ||
renderer.SetDrawColor(0xff, 0, 0xcc, opaque) | ||
renderer.Clear() | ||
|
||
texture, err := renderer.CreateTexture(sdl.PIXELFORMAT_ARGB8888, sdl.TEXTUREACCESS_STREAMING, width, height) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
texture.SetBlendMode(sdl.BLENDMODE_BLEND) // sdl.BLENDMODE_ADD is also possible | ||
|
||
rand.Seed(time.Now().UnixNano()) | ||
|
||
var ( | ||
pixels = make([]uint32, width*height) | ||
cores = runtime.NumCPU() | ||
event sdl.Event | ||
quit bool | ||
pause bool | ||
) | ||
|
||
// Innerloop | ||
for !quit { | ||
|
||
if !pause { | ||
// Draw to pixel buffer | ||
DrawAll(pixels, cores) | ||
|
||
// Draw pixel buffer to screen | ||
texture.UpdateRGBA(nil, pixels, width) | ||
|
||
// Clear the render buffer between each frame | ||
renderer.Clear() | ||
|
||
renderer.Copy(texture, nil, nil) | ||
renderer.Present() | ||
} | ||
|
||
// Check for events | ||
for event = sdl.PollEvent(); event != nil; event = sdl.PollEvent() { | ||
switch event.(type) { | ||
case *sdl.QuitEvent: | ||
quit = true | ||
case *sdl.KeyboardEvent: | ||
ke := event.(*sdl.KeyboardEvent) | ||
if ke.Type == sdl.KEYDOWN { | ||
ks := ke.Keysym | ||
switch ks.Sym { | ||
case sdl.K_ESCAPE: | ||
quit = true | ||
case sdl.K_q: | ||
quit = true | ||
case sdl.K_RETURN: | ||
altHeldDown := ks.Mod == sdl.KMOD_LALT || ks.Mod == sdl.KMOD_RALT | ||
if !altHeldDown { | ||
// alt+enter is not pressed | ||
break | ||
} | ||
// alt+enter is pressed | ||
fallthrough | ||
case sdl.K_f, sdl.K_F11: | ||
sdl2utils.ToggleFullscreen(window) | ||
case sdl.K_SPACE, sdl.K_p: | ||
pause = !pause | ||
case sdl.K_s: | ||
ctrlHeldDown := ks.Mod == sdl.KMOD_LCTRL || ks.Mod == sdl.KMOD_RCTRL | ||
if !ctrlHeldDown { | ||
// ctrl+s is not pressed | ||
break | ||
} | ||
// ctrl+s is pressed | ||
fallthrough | ||
case sdl.K_F12: | ||
// screenshot | ||
sdl2utils.Screenshot(renderer, "screenshot.png", true) | ||
} | ||
} | ||
} | ||
} | ||
sdl.Delay(1000 / frameRate) | ||
if esc { | ||
return errors.New("quit") | ||
} | ||
return 0 | ||
return nil | ||
} | ||
|
||
func main() { | ||
// This is to allow the deferred functions in run() to kick in at exit | ||
os.Exit(run()) | ||
pixelpusher.New("Simple Draw").Run(onDraw, onPress, nil, nil) | ||
} | ||
``` | ||
|
||
# General information | ||
|
||
* Version: 1.0.1 | ||
* License: 3-clause BSD | ||
* Author: Alexander F. Rødseth <[email protected]> | ||
* Version: 1.1.0 | ||
* License: BSD-3 | ||
* Author: Alexander F. Rødseth <[email protected]> |