4 Commits

Author SHA1 Message Date
540c8ba9d4 added Zyko0/Ebiary/asset for live reloading 2024-03-25 18:03:02 +01:00
e42df9080f various changes:
- renamed example shaders to .kage
- added --map-* options to map builtin uniforms to custom names
- added ebitengine sample shader with mapping (run `make shader-ebiten`)
- fixed bug: shaders with 0 images are allowed now
2024-03-25 15:41:51 +01:00
8cd2d74a8b check max images, use copy 2024-03-25 12:36:37 +01:00
490afb1d76 error 2024-03-25 12:21:53 +01:00
26 changed files with 134 additions and 65 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
releases
kage-viewer

View File

@@ -40,6 +40,12 @@ install: buildlocal
clean:
rm -rf $(tool) coverage.out testdata t/out
shader-destruct: buildlocal
./$(tool) -g 32x32 -i example/wall.png -i example/damage.png --map-ticks Time -s example/destruct.kage
shader-ebiten: buildlocal
./$(tool) -g 640x480 --map-ticks Time --map-mouse Cursor -s example/ebiten.kage
test: clean
mkdir -p t/out
go test ./... $(ARGS)

View File

@@ -3,7 +3,13 @@
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/kage-viewer/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/kage-viewer)](https://goreportcard.com/report/github.com/tlinden/kage-viewer)
This little tool can be used to test shaders written in [Kage](https://ebitengine.org/en/documents/shader.html), a shader meta language for [Ebitengine](https://github.com/hajimehoshi/ebiten).
This little tool can be used to test shaders written in
[Kage](https://ebitengine.org/en/documents/shader.html), a shader meta
language for
[Ebitengine](https://github.com/hajimehoshi/ebiten). kage-viewer
reloads changed assets, which allows you to develop your shader and
see live, how it responds to your changes. If loading fails, an error
will be printed to STDOUT. The same applies for images.
## Installation
@@ -61,13 +67,17 @@ Usage: kage-viewer [-vd] [-c <config file>] [-g geom] [-p geom] \
-i <image0.png> -i <image1.png> -s <shader.kage>
Options:
-c --config <toml file> Config file to use (optional)
-i --image <png file> Image to load (multiple times allowed, up to 4)
-s --shader <kage file> Shader to run
-g --geometry <WIDTHxHEIGHT> Window size
-p --position <XxY> Position of image0
-d --debug Show debugging output
-v --version Show program version
-c --config <toml file> Config file to use (optional)
-i --image <png file> Image to load (multiple times allowed, up to 4)
-s --shader <kage file> Shader to run
-g --geometry <WIDTHxHEIGHT> Window size
-p --position <XxY> Position of image0
--map-flag <name> Map Flag uniform to <name>
--map-ticks <name> Map Flag uniform to <name>
--map-slider <name> Map Flag uniform to <name>
--map-mouse <name> Map Flag uniform to <name>
-d --debug Show debugging output
-v --version Show program version
```
Example usage using the provided example:
@@ -90,9 +100,19 @@ Uniforms supported so far:
`SPACE` or pusing the left mouse button
- `var Slider float`: a normalized float value, you can increment it
with `UP` or `DOWN`
- `var Ticks int`: the time the game runs (ticks, not seconds!)
- `var Ticks float`: the time the game runs (ticks, not seconds!)
- `var Mouse vec2`: the current mouse position
If you want to test an existing shader and don't want to rename the
uniforms, you can map the ones provided by **kage-viewer** to custom
names using the `--map-*` options. For example:
```shell
kage-viewer -g 640x480 --map-ticks Time --map-mouse Cursor examples/shader/default.go
```
This executes the example shader in the ebitenging source repository.
# Config File
You can use a config file to store your own codes, once you found one
@@ -115,7 +135,7 @@ Possible parameters equal the long command line options.
- [X] Implement loading of images and shader files
- [X] Implement basic shader rendering and user input
- [ ] Add custom uniforms (maybe using lua code?)
- [ ] Provide a way to respond live to shader code changes (use lua as
- [x] Provide a way to respond live to shader code changes (use lua as
well?)
# Report bugs

View File

@@ -32,20 +32,24 @@ import (
)
const (
VERSION string = "0.0.1"
VERSION string = "0.0.3"
Usage string = `This is kage-viewer, a shader viewer.
Usage: kage-viewer [-vd] [-c <config file>] [-g geom] [-p geom] \
-i <image0.png> -i <image1.png> -s <shader.kage>
Options:
-c --config <toml file> Config file to use (optional)
-i --image <png file> Image to load (multiple times allowed, up to 4)
-s --shader <kage file> Shader to run
-g --geometry <WIDTHxHEIGHT> Window size
-p --position <XxY> Position of image0
-d --debug Show debugging output
-v --version Show program version
-c --config <toml file> Config file to use (optional)
-i --image <png file> Image to load (multiple times allowed, up to 4)
-s --shader <kage file> Shader to run
-g --geometry <WIDTHxHEIGHT> Window size
-p --position <XxY> Position of image0
--map-flag <name> Map Flag uniform to <name>
--map-ticks <name> Map Flag uniform to <name>
--map-slider <name> Map Flag uniform to <name>
--map-mouse <name> Map Flag uniform to <name>
-d --debug Show debugging output
-v --version Show program version
`
)
@@ -57,6 +61,10 @@ type Config struct {
Shader string `koanf:"shader"` // -s
Geo string `koanf:"geometry"` // -g
Posision string `koanf:"position"` // -p
Flag string `koanf:"map-flag"`
Ticks string `koanf:"map-ticks"`
Mouse string `koanf:"map-mouse"`
Slider string `koanf:"map-slider"`
X, Y, Width, Height int // feed from -g + -p
}
@@ -64,14 +72,6 @@ type Config struct {
func InitConfig() (*Config, error) {
var kloader = koanf.New(".")
// Load default values using the confmap provider.
/* not needed yet
if err := kloader.Load(confmap.Provider(map[string]interface{}{
}, "."), nil); err != nil {
return nil, fmt.Errorf("failed to load default values into koanf: %w", err)
}
*/
// setup custom usage
flagset := flag.NewFlagSet("config", flag.ContinueOnError)
flagset.Usage = func() {
@@ -87,6 +87,10 @@ func InitConfig() (*Config, error) {
flagset.StringP("position", "p", "0x0", "position of shader")
flagset.StringArrayP("image", "i", nil, "image file")
flagset.StringP("shader", "s", "", "shader file")
flagset.StringP("map-flag", "", "Flag", "map flag uniform")
flagset.StringP("map-ticks", "", "Ticks", "map ticks uniform")
flagset.StringP("map-mouse", "", "Mouse", "map mouse uniform")
flagset.StringP("map-slider", "", "Slider", "map slider uniform")
if err := flagset.Parse(os.Args[1:]); err != nil {
return nil, fmt.Errorf("failed to parse program arguments: %w", err)
@@ -140,8 +144,8 @@ func InitConfig() (*Config, error) {
}
func SanitiyCheck(conf *Config) error {
if len(conf.Image) < 1 {
return fmt.Errorf("at least 1 image must be specified")
if len(conf.Image) > 4 {
return fmt.Errorf("only 4 images can be specified")
}
if conf.Shader == "" {

View File

@@ -20,7 +20,7 @@ package main
var Flag int
var Slider float
var Time int
var Time float
var Mouse vec2
func Fragment(_ vec4, texCoord vec2, _ vec4) vec4 {

33
example/ebiten.kage Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ignore
//kage:unit pixels
package main
var Time float
var Cursor vec2
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
pos := (dstPos.xy - imageDstOrigin()) / imageDstSize()
pos += Cursor / imageDstSize() / 4
clr := 0.0
clr += sin(pos.x*cos(Time/15)*80) + cos(pos.y*cos(Time/15)*10)
clr += sin(pos.y*sin(Time/10)*40) + cos(pos.x*sin(Time/25)*40)
clr += sin(pos.x*sin(Time/5)*10) + sin(pos.y*sin(Time/35)*80)
clr *= sin(Time/10) * 0.5
return vec4(clr, clr*0.5, sin(clr+Time/3)*0.75, 1)
}

54
game.go
View File

@@ -24,14 +24,16 @@ import (
"log/slog"
"os"
"github.com/Zyko0/Ebiary/asset"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
type Game struct {
Conf *Config
Images []*ebiten.Image
Shader *ebiten.Shader
Images []*asset.LiveAsset[*ebiten.Image]
Shader *asset.LiveAsset[*ebiten.Shader]
Cursor []float64
Ticks int
Slider float64
Flag int
@@ -55,24 +57,20 @@ func LoadImage(name string) (*ebiten.Image, error) {
func (game *Game) Init() error {
for _, image := range game.Conf.Image {
slog.Debug("Loading images", "image", image)
img, err := LoadImage(image)
//img, err := LoadImage(image)
img, err := asset.NewLiveAsset[*ebiten.Image](image)
if err != nil {
return err
return fmt.Errorf("failed to load image %s: %s", image, err)
}
game.Images = append(game.Images, img)
}
data, err := os.ReadFile(game.Conf.Shader)
shader, err := asset.NewLiveAsset[*ebiten.Shader](game.Conf.Shader)
if err != nil {
return fmt.Errorf("failed to load shader %s: %s", game.Conf.Shader, err)
}
shader, err := ebiten.NewShader(data)
if err != nil {
return fmt.Errorf("failed to create new shader %s: %s", game.Conf.Shader, err)
}
game.Shader = shader
return nil
@@ -120,10 +118,28 @@ func (g *Game) Down() {
}
func (game *Game) Update() error {
if game.CheckInput() {
slog.Debug("Key pressed", "Slider", game.Slider, "Flag", game.Flag)
for _, image := range game.Images {
if image.Error() != nil {
fmt.Println("warn: image reloading error:", image.Error())
}
}
if game.Shader.Error() != nil {
fmt.Println("warn: shader reloading error:", game.Shader.Error())
}
if game.CheckInput() {
slog.Debug("Key pressed",
game.Conf.Flag, game.Flag,
game.Conf.Slider, game.Slider,
game.Conf.Ticks, fmt.Sprintf("%.02f", float64(game.Ticks)/60),
game.Conf.Mouse, fmt.Sprintf("%.02f, %.02f", game.Cursor[0], game.Cursor[1]),
)
}
mousex, mousey := ebiten.CursorPosition()
game.Cursor = []float64{float64(mousex), float64(mousey)}
game.Ticks++
return nil
@@ -132,22 +148,20 @@ func (game *Game) Update() error {
func (game *Game) Draw(screen *ebiten.Image) {
op := &ebiten.DrawRectShaderOptions{}
mousex, mousey := ebiten.CursorPosition()
op.Uniforms = map[string]any{
"Flag": game.Flag,
"Slider": game.Slider,
"Ticks": game.Ticks,
"Mouse": []float64{float64(mousex), float64(mousey)},
game.Conf.Flag: game.Flag,
game.Conf.Slider: game.Slider,
game.Conf.Ticks: float64(game.Ticks) / 60,
game.Conf.Mouse: game.Cursor,
}
for idx, image := range game.Images {
op.Images[idx] = image
op.Images[idx] = image.Value()
}
op.GeoM.Translate(float64(game.Conf.X), float64(game.Conf.Y))
screen.DrawRectShader(game.Conf.Width, game.Conf.Height, game.Shader, op)
screen.DrawRectShader(game.Conf.Width, game.Conf.Height, game.Shader.Value(), op)
}
func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {

3
go.mod
View File

@@ -3,9 +3,10 @@ module github.com/TLINDEN/kage-viewer
go 1.22
require (
github.com/Zyko0/Ebiary/asset v0.0.0-20240304185439-be56fe8a2a6a // indirect
github.com/ebitengine/purego v0.6.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/hajimehoshi/ebiten/v2 v2.6.7 // indirect
github.com/jezek/xgb v1.1.0 // indirect

4
go.sum
View File

@@ -1,9 +1,13 @@
github.com/Zyko0/Ebiary/asset v0.0.0-20240304185439-be56fe8a2a6a h1:kn4fhGvVA6T1lK7qWujIj3m7e9imCZe4MHBuBeflKgU=
github.com/Zyko0/Ebiary/asset v0.0.0-20240304185439-be56fe8a2a6a/go.mod h1:4CqqwHRUbvGBpBd5ye4MxDA4k/XtZqrAD1sg9uxmcYI=
github.com/ebitengine/purego v0.6.0 h1:Yo9uBc1x+ETQbfEaf6wcBsjrQfCEnh/gaGUg7lguEJY=
github.com/ebitengine/purego v0.6.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/hajimehoshi/ebiten/v2 v2.6.7 h1:rxlMxu487wZN/JteykmuGdO1qotOolL8vJDU85lPh7A=

Binary file not shown.

View File

@@ -21,7 +21,6 @@ import (
"fmt"
"log"
"os"
"runtime/debug"
"log/slog"
@@ -46,7 +45,6 @@ func main() {
if conf.Debug {
logLevel := &slog.LevelVar{}
// we're using a more verbose logger in debug mode
buildInfo, _ := debug.ReadBuildInfo()
opts := &yadu.Options{
Level: logLevel,
AddSource: true,
@@ -55,12 +53,7 @@ func main() {
logLevel.Set(slog.LevelDebug)
handler := yadu.NewHandler(os.Stdout, opts)
debuglogger := slog.New(handler).With(
slog.Group("program_info",
slog.Int("pid", os.Getpid()),
slog.String("go_version", buildInfo.GoVersion),
),
)
debuglogger := slog.New(handler)
slog.SetDefault(debuglogger)
}

View File

@@ -1 +0,0 @@
f73335dc3e4e65b089624e9580b02d38926597c59127ca507be03148ab229b4f

View File

@@ -1 +0,0 @@
d7726c68b43c550c27a42f7586a418915d042ed9a444f651cb5159e37bdea4ab

View File

@@ -1 +0,0 @@
91b57cfbaa1ec63b797fbb4c4b3892c2e462ec7668207a3c645917af9c7ac3d1

View File

@@ -1 +0,0 @@
197bfab6dda912f8ed8f8d1b7afb1946d1c7640ab84ccb4a48c8d5d7c62fe8ea

View File

@@ -1 +0,0 @@
c69df9ea174b69fb17e5f1e359106bce6105f6185e09531912e04398a4b72eff

View File

@@ -1 +0,0 @@
5c61f307b7b576f10d4a5ae276dbb8070053efd060bbfda93f8535658478b2cf

View File

@@ -1 +0,0 @@
df707023f0bd50fe1ff55e466f212c5668d6c488644b25d5880e64ca21c4f073