Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
xyproto committed Jan 4, 2024
1 parent 8d30ad8 commit 0281ef5
Showing 1 changed file with 37 additions and 149 deletions.
186 changes: 37 additions & 149 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]>

0 comments on commit 0281ef5

Please sign in to comment.