Skip to content
This repository was archived by the owner on Aug 15, 2023. It is now read-only.

Commit 341cc3d

Browse files
authored
Add GL blender (#100)
* Add GL blender * Add Width() and Height() to AcceleratedImage * Add test verifying if Modify overrides pixels modified in RAM * Disable checkptr in tests * Add usage to FloatVertexBuffer * Make Color pre-multiplied
1 parent 12e5f4a commit 341cc3d

32 files changed

+1363
-202
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ lint:
33
golangci-lint run -E goimports,unconvert,misspell,maligned,gocyclo,gocritic,gochecknoinits
44

55
test:
6-
go test -race -v ./...
6+
go test -race -v -gcflags=all=-d=checkptr=0 ./...
77

88
xvfb-test:
99
xvfb-run go test -race -v ./...

examples/image/modify/opengl/draw/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func makeVertexBuffer(context *gl.Context) *gl.FloatVertexBuffer {
4949
1, -1, 0, 0, 1, // bottom-right -> blue
5050
-1, -1, 1, 1, 1, // bottom-left -> white
5151
}
52-
buffer := context.NewFloatVertexBuffer(len(vertices))
52+
buffer := context.NewFloatVertexBuffer(len(vertices), gl.StaticDraw)
5353
buffer.Upload(0, vertices)
5454
return buffer
5555
}

examples/image/modify/opengl/selection/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ func makeVertexArray(context *gl.Context, buffer *gl.FloatVertexBuffer) *gl.Vert
4646
return array
4747
}
4848

49-
func makeVertexBuffer(gl *gl.Context) *gl.FloatVertexBuffer {
49+
func makeVertexBuffer(ctx *gl.Context) *gl.FloatVertexBuffer {
5050
// xy -> st
5151
vertices := []float32{
5252
-1, 1, 0, 1, // top-left
5353
1, 1, 1, 1, // top-right
5454
1, -1, 1, 0, // bottom-right
5555
-1, -1, 0, 0, // bottom-left
5656
}
57-
buffer := gl.NewFloatVertexBuffer(len(vertices))
57+
buffer := ctx.NewFloatVertexBuffer(len(vertices), gl.StaticDraw)
5858
buffer.Upload(0, vertices)
5959
return buffer
6060
}

examples/tools/blend/blend.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/jacekolszak/pixiq/loop"
88
"github.com/jacekolszak/pixiq/tools/blend"
99
"github.com/jacekolszak/pixiq/tools/clear"
10+
"github.com/jacekolszak/pixiq/tools/glblend"
1011
)
1112

1213
func main() {
@@ -19,7 +20,15 @@ func main() {
1920
face := face(gl)
2021

2122
sourceBlender := blend.NewSource()
23+
glSourceBlender, err := glblend.NewSource(gl.Context())
24+
if err != nil {
25+
panic(err)
26+
}
2227
sourceOverBlender := blend.NewSourceOver()
28+
glSourceOverBlender, err := glblend.NewSourceOver(gl.Context())
29+
if err != nil {
30+
panic(err)
31+
}
2332

2433
clearTool := clear.New()
2534
clearTool.SetColor(colornames.Aliceblue)
@@ -31,11 +40,18 @@ func main() {
3140

3241
// source blending overrides the target with source colors
3342
// fully transparent pixels (RGBA 0x00000000) are rendered as black on the screen.
34-
sourceBlender.BlendSourceToTarget(face, screen.Selection(15, 7))
43+
sourceBlender.BlendSourceToTarget(face, screen.Selection(10, 7))
44+
45+
// similar source blending using video card
46+
glSourceBlender.BlendSourceToTarget(face, screen.Selection(20, 7))
47+
3548
// source-over blending mixes source and target colors together taking
3649
// into account alpha channels of both. In places where source has
3750
// transparent pixels the original target colors are preserved.
38-
sourceOverBlender.BlendSourceToTarget(face, screen.Selection(15, 24))
51+
sourceOverBlender.BlendSourceToTarget(face, screen.Selection(10, 24))
52+
53+
// similar source-over blending using video card
54+
glSourceOverBlender.BlendSourceToTarget(face, screen.Selection(20, 24))
3955

4056
if window.ShouldClose() {
4157
frame.StopLoopEventually()

gl/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ type API interface {
114114
GetError() uint32
115115
// ReadPixels reads a block of pixels from the frame buffer
116116
ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer)
117+
// BlendFunc specifies pixel arithmetic
118+
BlendFunc(sfactor uint32, dfactor uint32)
117119
// Finish blocks until all GL execution is complete
118120
Finish()
119121
// Ptr takes a slice or pointer (to a singular scalar value or the first

gl/command.go

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,20 @@ type Command interface {
1313
RunGL(renderer *Renderer, selections []image.AcceleratedImageSelection)
1414
}
1515

16-
// Renderer is an API for drawing primitives
16+
// Renderer is an API for drawing primitives.
17+
//
18+
// It is using blending with formula:
19+
// R = S*sf + D*df
20+
// where S is source color component, sf is a source factor, D is destination color
21+
// component, df is a destination factor.
22+
// By default sf is 1 and df is 0 (gl.SourceBlendFactors), which means that formula is
23+
// R = S
24+
// BlendFactors can be changed by calling SetBlendFactors method.
1725
type Renderer struct {
18-
program *Program
19-
api API
20-
allImages allImages
26+
program *Program
27+
api API
28+
allImages allImages
29+
blendFactors BlendFactors
2130
}
2231

2332
// BindTexture assigns image.AcceleratedImage to a given textureUnit and uniform attribute.
@@ -146,12 +155,48 @@ var (
146155
Triangles = Mode{glMode: triangles}
147156
)
148157

158+
// BlendFactor is source or destination factor
159+
type BlendFactor uint32
160+
161+
const (
162+
// Zero is GL_ZERO. Multiplies all components by 0.
163+
Zero = BlendFactor(0)
164+
// One is GL_ONE. Multiplies all components by 1.
165+
One = BlendFactor(1)
166+
// OneMinusSrcAlpha is GL_ONE_MINUS_SRC_ALPHA. Multiplies all components by 1 minus
167+
// the source alpha value.
168+
OneMinusSrcAlpha = BlendFactor(0x0303)
169+
// SrcAlpha is GL_SRC_ALPHA. Multiplies all components by the source alpha value.
170+
SrcAlpha = BlendFactor(0x0302)
171+
// DstAlpha is GL_DST_ALPHA. Multiplies all components by the destination alpha value.
172+
DstAlpha = BlendFactor(0x0304)
173+
// OneMinusDstAlpha is GL_ONE_MINUS_DST_ALPHA. Multiplies all components by 1 minus
174+
// the destination alpha value.
175+
OneMinusDstAlpha = BlendFactor(0x0305)
176+
)
177+
178+
// BlendFactors contains source and destination factors used by blending formula
179+
// R = S*sf + D*df
180+
type BlendFactors struct {
181+
SrcFactor, DstFactor BlendFactor
182+
}
183+
184+
// SourceBlendFactors is default BlendFactors used by AcceleratedCommand.
185+
var SourceBlendFactors = BlendFactors{SrcFactor: One, DstFactor: Zero}
186+
187+
// SetBlendFactors sets source and dest factors for blending formula:
188+
// R = S*sf + D*df
189+
func (r *Renderer) SetBlendFactors(factors BlendFactors) {
190+
r.blendFactors = factors
191+
}
192+
149193
// DrawArrays draws primitives (such as triangles) using vertices defined in VertexArray.
150194
//
151195
// Before primitive is drawn this method validates if
152196
func (r *Renderer) DrawArrays(array *VertexArray, mode Mode, first, count int) {
153197
r.validateAttributeTypes(array)
154198
r.api.BindVertexArray(array.id)
199+
r.api.BlendFunc(uint32(r.blendFactors.SrcFactor), uint32(r.blendFactors.DstFactor))
155200
r.api.DrawArrays(mode.glMode, int32(first), int32(count))
156201
}
157202

@@ -216,32 +261,27 @@ func (c *AcceleratedCommand) Run(output image.AcceleratedImageSelection, selecti
216261
y := int32(loc.Y)
217262
w := int32(loc.Width)
218263
h := int32(loc.Height)
219-
if x < 0 {
220-
w += x
221-
x = 0
222-
}
223264
if x+w > int32(img.width) {
224265
w = int32(img.width) - x
225266
}
226-
if y < 0 {
227-
h += y
228-
y = 0
229-
}
230267
if y+h > int32(img.height) {
231268
h = int32(img.height) - y
232269
}
233270
y = int32(img.height) - h - y
234271

235272
c.program.use()
236273
c.api.Enable(scissorTest)
274+
c.api.Enable(blend)
237275
c.api.BindFramebuffer(framebuffer, img.frameBufferID)
238276
c.api.Scissor(x, y, w, h)
239277
c.api.Viewport(x, y, w, h)
240278

241279
renderer := &Renderer{
242-
program: c.program,
243-
api: c.api,
244-
allImages: c.allImages,
280+
program: c.program,
281+
api: c.api,
282+
allImages: c.allImages,
283+
blendFactors: SourceBlendFactors,
245284
}
285+
246286
c.command.RunGL(renderer, selections)
247287
}

gl/consts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package gl
33
// Camel-cased GL constants
44
const (
55
arrayBuffer = 0x8892
6+
streamDraw = 0x88E0
67
staticDraw = 0x88E4
8+
dynamicDraw = 0x88E8
79
float = 0x1406
810
floatVec2 = 0x8B50
911
floatVec3 = 0x8B51
@@ -37,6 +39,10 @@ const (
3739
textureMinFilter = 0x2801
3840
textureMagFilter = 0x2800
3941
nearest = 0x2600
42+
textureWrapS = 0x2802
43+
textureWrapT = 0x2803
44+
clampToBorder = 0x812D
4045
noError = 0
4146
outOfMemory = 0x0505
47+
blend = 0x0BE2
4248
)

gl/context.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,38 @@ func (c *Context) Error() error {
6666
return glError(code)
6767
}
6868

69-
// NewFloatVertexBuffer creates an OpenGL's Vertex Buffer Object (VBO) containing only float32 numbers.
70-
func (c *Context) NewFloatVertexBuffer(size int) *FloatVertexBuffer {
69+
// Usage defines how Vertex Buffer will be used. This is a hint for OpenGL implementation.
70+
type Usage struct {
71+
glUsage uint32
72+
}
73+
74+
var (
75+
// StreamDraw tells OpenGL implementation that vertex buffer will be modified once
76+
// and used at most a few times.
77+
// Vertex Buffer is modified by the application, and used as the source
78+
// for GL drawing and image specification commands.
79+
StreamDraw = Usage{glUsage: streamDraw}
80+
// StaticDraw tells OpenGL implementation that vertex buffer will be modified once
81+
// and used many times.
82+
// Vertex Buffer is modified by the application, and used as the source
83+
// for GL drawing and image specification commands.
84+
StaticDraw = Usage{glUsage: staticDraw}
85+
// DynamicDraw tells OpenGL implementation that vertex buffer will be modified
86+
// repeatedly and used many times.
87+
// Vertex Buffer is modified by the application, and used as the source
88+
// for GL drawing and image specification commands.
89+
DynamicDraw = Usage{glUsage: dynamicDraw}
90+
)
91+
92+
// NewFloatVertexBuffer creates an OpenGL's Vertex Buffer Object (VBO) containing only float32 numb)ers.
93+
func (c *Context) NewFloatVertexBuffer(size int, usage Usage) *FloatVertexBuffer {
7194
if size < 0 {
7295
panic("negative size")
7396
}
7497
var id uint32
7598
c.api.GenBuffers(1, &id)
7699
c.api.BindBuffer(arrayBuffer, id)
77-
c.api.BufferData(arrayBuffer, size*4, c.api.Ptr(nil), staticDraw) // FIXME: Parametrize usage
100+
c.api.BufferData(arrayBuffer, size*4, c.api.Ptr(nil), usage.glUsage)
78101
vb := &FloatVertexBuffer{
79102
id: id,
80103
size: size,

gl/gl_test.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestContext_NewFloatVertexBuffer(t *testing.T) {
4343
context := gl.NewContext(apiStub{})
4444
// when
4545
assert.Panics(t, func() {
46-
context.NewFloatVertexBuffer(size)
46+
context.NewFloatVertexBuffer(size, gl.StaticDraw)
4747
})
4848
})
4949
}
@@ -57,7 +57,7 @@ func TestContext_NewFloatVertexBuffer(t *testing.T) {
5757
t.Run(name, func(t *testing.T) {
5858
context := gl.NewContext(apiStub{})
5959
// when
60-
buffer := context.NewFloatVertexBuffer(size)
60+
buffer := context.NewFloatVertexBuffer(size, gl.StaticDraw)
6161
// then
6262
assert.NotNil(t, buffer)
6363
// and
@@ -94,7 +94,7 @@ func TestFloatVertexBuffer_Upload(t *testing.T) {
9494
for name, test := range tests {
9595
t.Run(name, func(t *testing.T) {
9696
context := gl.NewContext(apiStub{})
97-
buffer := context.NewFloatVertexBuffer(test.size)
97+
buffer := context.NewFloatVertexBuffer(test.size, gl.StaticDraw)
9898
assert.Panics(t, func() {
9999
// when
100100
buffer.Upload(test.offset, test.data)
@@ -104,7 +104,7 @@ func TestFloatVertexBuffer_Upload(t *testing.T) {
104104
})
105105
t.Run("should panic when offset is negative", func(t *testing.T) {
106106
context := gl.NewContext(apiStub{})
107-
buffer := context.NewFloatVertexBuffer(1)
107+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
108108
assert.Panics(t, func() {
109109
// when
110110
buffer.Upload(-1, []float32{1})
@@ -115,7 +115,7 @@ func TestFloatVertexBuffer_Upload(t *testing.T) {
115115
func TestFloatVertexBuffer_Download(t *testing.T) {
116116
t.Run("should panic when offset is negative", func(t *testing.T) {
117117
context := gl.NewContext(apiStub{})
118-
buffer := context.NewFloatVertexBuffer(1)
118+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
119119
defer buffer.Delete()
120120
output := make([]float32, 1)
121121
assert.Panics(t, func() {
@@ -125,7 +125,7 @@ func TestFloatVertexBuffer_Download(t *testing.T) {
125125
})
126126
t.Run("should panic when buffer has been deleted", func(t *testing.T) {
127127
context := gl.NewContext(apiStub{})
128-
buffer := context.NewFloatVertexBuffer(1)
128+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
129129
buffer.Delete()
130130
output := make([]float32, 1)
131131
// when
@@ -167,7 +167,7 @@ func TestVertexArray_Set(t *testing.T) {
167167
context := gl.NewContext(apiStub{})
168168
vao := context.NewVertexArray(gl.VertexLayout{gl.Float})
169169
defer vao.Delete()
170-
buffer := context.NewFloatVertexBuffer(1)
170+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
171171
pointer := gl.VertexBufferPointer{
172172
Buffer: buffer,
173173
Offset: -1,
@@ -181,7 +181,7 @@ func TestVertexArray_Set(t *testing.T) {
181181
t.Run("should panic when stride is negative", func(t *testing.T) {
182182
context := gl.NewContext(apiStub{})
183183
vao := context.NewVertexArray(gl.VertexLayout{gl.Float})
184-
buffer := context.NewFloatVertexBuffer(1)
184+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
185185
pointer := gl.VertexBufferPointer{
186186
Buffer: buffer,
187187
Offset: 0,
@@ -195,7 +195,7 @@ func TestVertexArray_Set(t *testing.T) {
195195
t.Run("should panic when location is negative", func(t *testing.T) {
196196
context := gl.NewContext(apiStub{})
197197
vao := context.NewVertexArray(gl.VertexLayout{gl.Float})
198-
buffer := context.NewFloatVertexBuffer(1)
198+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
199199
pointer := gl.VertexBufferPointer{
200200
Buffer: buffer,
201201
Offset: 0,
@@ -209,7 +209,7 @@ func TestVertexArray_Set(t *testing.T) {
209209
t.Run("should panic when location is higher than number of arguments", func(t *testing.T) {
210210
context := gl.NewContext(apiStub{})
211211
vao := context.NewVertexArray(gl.VertexLayout{gl.Float})
212-
buffer := context.NewFloatVertexBuffer(1)
212+
buffer := context.NewFloatVertexBuffer(1, gl.StaticDraw)
213213
pointer := gl.VertexBufferPointer{
214214
Buffer: buffer,
215215
Offset: 0,
@@ -298,6 +298,14 @@ func TestContext_NewAcceleratedImage(t *testing.T) {
298298
context.NewAcceleratedImage(1, capabilities.MaxTextureSize()+1)
299299
})
300300
})
301+
t.Run("should create AcceleratedImage", func(t *testing.T) {
302+
context := gl.NewContext(apiStub{})
303+
// when
304+
img := context.NewAcceleratedImage(4, 2)
305+
// then
306+
assert.Equal(t, 4, img.Width())
307+
assert.Equal(t, 2, img.Height())
308+
})
301309
}
302310
func TestProgram_AcceleratedCommand(t *testing.T) {
303311
t.Run("should return command", func(t *testing.T) {
@@ -539,6 +547,7 @@ func (a apiStub) GetTexImage(target uint32, level int32, format uint32, xtype ui
539547
func (a apiStub) GetError() uint32 { return 0 }
540548
func (a apiStub) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
541549
}
542-
func (a apiStub) Finish() {}
543-
func (a apiStub) Ptr(data interface{}) unsafe.Pointer { return nil }
544-
func (a apiStub) PtrOffset(offset int) unsafe.Pointer { return nil }
550+
func (a apiStub) BlendFunc(sfactor uint32, dfactor uint32) {}
551+
func (a apiStub) Finish() {}
552+
func (a apiStub) Ptr(data interface{}) unsafe.Pointer { return nil }
553+
func (a apiStub) PtrOffset(offset int) unsafe.Pointer { return nil }

0 commit comments

Comments
 (0)