From e3bb94ebb08aa70d5a0c93228c88b0eee444a0f6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 20 Jun 2020 23:29:52 +0200 Subject: [PATCH] internal/rendertest: create test suit for drawing operations Uses app/headless to create a set of test cases for drawing operations, including clipping textures and transforms. This commit tests for approximate pixel matches, if future changes affect local drawing operations it will be easy to change the reference images, it thus becomes and should be an intentional operation if changes lead to local changes in drawn results. Ideally we should be able to make the tests check for exact pixel matches down the line to ensure consistent results between platforms. Signed-off-by: Viktor --- internal/rendertest/bench_test.go | 23 ++ internal/rendertest/clip_test.go | 73 +++++++ internal/rendertest/doc.go | 2 + internal/rendertest/refs/TestClipOffset.png | Bin 0 -> 383 bytes .../rendertest/refs/TestClipPaintOffset.png | Bin 0 -> 383 bytes internal/rendertest/refs/TestClipRotate.png | Bin 0 -> 546 bytes internal/rendertest/refs/TestClipScale.png | Bin 0 -> 383 bytes .../refs/TestComplicatedTransform.png | Bin 0 -> 1116 bytes .../rendertest/refs/TestNoClipFromPaint.png | Bin 0 -> 374 bytes .../refs/TestOffsetScaleTexture.png | Bin 0 -> 470 bytes .../rendertest/refs/TestOffsetTexture.png | Bin 0 -> 539 bytes .../rendertest/refs/TestPaintClippedCirle.png | Bin 0 -> 571 bytes .../rendertest/refs/TestPaintClippedRect.png | Bin 0 -> 382 bytes .../refs/TestPaintClippedTexture.png | Bin 0 -> 392 bytes internal/rendertest/refs/TestPaintOffset.png | Bin 0 -> 387 bytes internal/rendertest/refs/TestPaintRect.png | Bin 0 -> 374 bytes internal/rendertest/refs/TestPaintRotate.png | Bin 0 -> 1436 bytes internal/rendertest/refs/TestPaintShear.png | Bin 0 -> 511 bytes internal/rendertest/refs/TestPaintTexture.png | Bin 0 -> 408 bytes .../rendertest/refs/TestRepeatedPaintsZ.png | Bin 0 -> 376 bytes internal/rendertest/refs/TestReuseStencil.png | Bin 0 -> 383 bytes .../rendertest/refs/TestRotateClipTexture.png | Bin 0 -> 1122 bytes .../rendertest/refs/TestRotateTexture.png | Bin 0 -> 864 bytes .../rendertest/refs/TestTransformMacro.png | Bin 0 -> 375 bytes .../rendertest/refs/TestTransformOrder.png | Bin 0 -> 384 bytes internal/rendertest/render_test.go | 144 +++++++++++++ internal/rendertest/transform_test.go | 201 ++++++++++++++++++ internal/rendertest/util_test.go | 27 ++- 28 files changed, 460 insertions(+), 10 deletions(-) create mode 100644 internal/rendertest/clip_test.go create mode 100644 internal/rendertest/doc.go create mode 100644 internal/rendertest/refs/TestClipOffset.png create mode 100644 internal/rendertest/refs/TestClipPaintOffset.png create mode 100644 internal/rendertest/refs/TestClipRotate.png create mode 100644 internal/rendertest/refs/TestClipScale.png create mode 100644 internal/rendertest/refs/TestComplicatedTransform.png create mode 100644 internal/rendertest/refs/TestNoClipFromPaint.png create mode 100644 internal/rendertest/refs/TestOffsetScaleTexture.png create mode 100644 internal/rendertest/refs/TestOffsetTexture.png create mode 100644 internal/rendertest/refs/TestPaintClippedCirle.png create mode 100644 internal/rendertest/refs/TestPaintClippedRect.png create mode 100644 internal/rendertest/refs/TestPaintClippedTexture.png create mode 100644 internal/rendertest/refs/TestPaintOffset.png create mode 100644 internal/rendertest/refs/TestPaintRect.png create mode 100644 internal/rendertest/refs/TestPaintRotate.png create mode 100644 internal/rendertest/refs/TestPaintShear.png create mode 100644 internal/rendertest/refs/TestPaintTexture.png create mode 100644 internal/rendertest/refs/TestRepeatedPaintsZ.png create mode 100644 internal/rendertest/refs/TestReuseStencil.png create mode 100644 internal/rendertest/refs/TestRotateClipTexture.png create mode 100644 internal/rendertest/refs/TestRotateTexture.png create mode 100644 internal/rendertest/refs/TestTransformMacro.png create mode 100644 internal/rendertest/refs/TestTransformOrder.png create mode 100644 internal/rendertest/render_test.go create mode 100644 internal/rendertest/transform_test.go diff --git a/internal/rendertest/bench_test.go b/internal/rendertest/bench_test.go index fbed640b..2d3499cc 100644 --- a/internal/rendertest/bench_test.go +++ b/internal/rendertest/bench_test.go @@ -97,6 +97,29 @@ func BenchmarkDrawUI(b *testing.B) { finishBenchmark(b, w) } +func BenchmarkDrawUITransformed(b *testing.B) { + // Like BenchmarkDraw UI but transformed at every frame + gtx, w, th := setupBenchmark(b) + drawCore(gtx, th) + w.Frame(gtx.Ops) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + resetOps(gtx) + + p := op.Push(gtx.Ops) + angle := float32(math.Mod(float64(i)/1000, 0.05)) + a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle) + op.Affine(a).Add(gtx.Ops) + + drawCore(gtx, th) + + p.Pop() + w.Frame(gtx.Ops) + } + finishBenchmark(b, w) +} + func Benchmark1000Circles(b *testing.B) { // Benchmark1000Shapes draws 1000 individual shapes such that no caching between // shapes will be possible and resets buffers on each operation to prevent caching diff --git a/internal/rendertest/clip_test.go b/internal/rendertest/clip_test.go new file mode 100644 index 00000000..fa8f285f --- /dev/null +++ b/internal/rendertest/clip_test.go @@ -0,0 +1,73 @@ +package rendertest + +import ( + "testing" + + "gioui.org/f32" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "golang.org/x/image/colornames" +) + +func TestPaintRect(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 50, 50)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.Red) + r.expect(49, 0, colornames.Red) + r.expect(50, 0, colornames.White) + r.expect(10, 50, colornames.White) + }) +} + +func TestPaintClippedRect(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + clip.Rect{Rect: f32.Rect(25, 25, 60, 60)}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 50, 50)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.White) + r.expect(24, 35, colornames.White) + r.expect(25, 35, colornames.Red) + r.expect(50, 0, colornames.White) + r.expect(10, 50, colornames.White) + }) +} + +func TestPaintClippedCirle(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + r := float32(10) + clip.Rect{Rect: f32.Rect(20, 20, 40, 40), SE: r, SW: r, NW: r, NE: r}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 30, 50)}.Add(o) + }, func(r result) { + r.expect(21, 21, colornames.White) + r.expect(25, 30, colornames.Red) + r.expect(31, 30, colornames.White) + }) +} + +func TestPaintTexture(t *testing.T) { + run(t, func(o *op.Ops) { + squares.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 80, 80)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.Blue) + r.expect(79, 10, colornames.Green) + r.expect(80, 0, colornames.White) + r.expect(10, 80, colornames.White) + }) +} + +func TestPaintClippedTexture(t *testing.T) { + run(t, func(o *op.Ops) { + squares.Add(o) + clip.Rect{Rect: f32.Rect(0, 0, 40, 40)}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 80, 80)}.Add(o) + }, func(r result) { + r.expect(40, 40, colornames.White) + r.expect(25, 35, colornames.Blue) + }) +} diff --git a/internal/rendertest/doc.go b/internal/rendertest/doc.go new file mode 100644 index 00000000..9f6948e9 --- /dev/null +++ b/internal/rendertest/doc.go @@ -0,0 +1,2 @@ +// Package rendertest is intended for testing of drawing ops only. +package rendertest diff --git a/internal/rendertest/refs/TestClipOffset.png b/internal/rendertest/refs/TestClipOffset.png new file mode 100644 index 0000000000000000000000000000000000000000..59371e421b591a7e1a4519e0c54c2f0bfe826af4 GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V080zaSW-L^XAq;P8LN0hJ&9C z{!h6pQ#P^6=X$*0tjgZsb3S)}zRo{m9YX};2Brko16&QF4B8A~3~PuN_;z&n^{V=^ zGxOi75vldfGrx*6ao@X%RZoIr$Z$V?E$R#m|NqDR;9xq-9nl61SO!m5KbLh*2~7Zf C$ZR|S literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestClipPaintOffset.png b/internal/rendertest/refs/TestClipPaintOffset.png new file mode 100644 index 0000000000000000000000000000000000000000..f9988440947dd18557f702afc1bf83e36afe2b56 GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V080zaSW-L^X8@@*8v3qhJzA+ z{!c&4@ARNc*XVMWf=;;1yW(@M&)3O+C}%pt(!nVptf1!L(=dat%8b#Q$!Jxt)7?%J47rx|RT2rfP0pu`vy85}Sb4q9e0EJU& ASO5S3 literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestClipRotate.png b/internal/rendertest/refs/TestClipRotate.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c0e4e5791c995de879fb8be7e990f3e6641bc7 GIT binary patch literal 546 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V0`K6;uumf=gpl!ug3ulZVyWe zGT%AH#Fj|L_U|@uR#&!ItXaD&{^LA;`5F$1f0GkbZnWp#o@TNC3+I`23=xbQm=ahI za5acBXfuQ{tRYpP%%Abx^QTW!XTFhlll#J1V=;gF@#FjN+uQ0c7q*)p`0g>^k3IiB zfBy8#>U?bhJEPtD{j*m#@_i`%d)I_Fqhaya@?w($#&tl^1&mw;r+*a-Fy_VA$JYN* ze))l6?f>-~8KeGpHv$Rk#Y|iNH7WvGHzqLT=bOu#-Tx(io8kTY+q?2k7ro7n)o(wW zenPguDpv1psjW@Ix$OV_f%{w5o6M;AyRm=aeX|+We;?L&{Joy=>+4bNlY0;5{rak3 zJ;UGkPW_87uXj)XE$C9iwS-0A7;G+ov;RE1tnU7s)Q2zYcmI9Ey!reTckQWN z3RZTO=l;#J{Xg}5+P@PiTr33u9*2p?r(}+3-8JQEWR{8jxc#v?_{r5Uac?mXVenv0V$jFHw3ne9X zq_FbZFr5%BGVZ>~f3Lo*_U;ywFKqoU`mA+i_#kI$FD?GN)AKX)uQPupx*R!a zWp!S^V&&V0pq*1<{L0RkzyG)M_kZ`d>r-Bcer|T&!!N?1HTBdj|Lnb!_nut*gmwPC z*;5|gex{jdnjxYPe@1$ zr}{tcywm;sc>ay)$G7ulYyIyrddu?raoOu%$D@c;C`zY~84A3gnFaraHG zkCrbxW=-EIk$OAH^ylpL+bwSrUM=7+kuDm^lJyZy|c+lhni^J^)VcgOctPdIwHl13r zK(c~kL2g0tyKM^^w=JEm3O7r&%XXxq3kyp*H|$%C^J|LJ7ULvW(J1;|99Ma WAlDx8RUTMsFnGH9xvX<7^XlGbOMd;A#+M&}Il@Si=y(xPc}Do1dFsox6*M h7EsI8`+!0C|9>h2+y5Q=lRg1C44$rjF6*2UngB2EWfA}Y literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestOffsetScaleTexture.png b/internal/rendertest/refs/TestOffsetScaleTexture.png new file mode 100644 index 0000000000000000000000000000000000000000..42ebc4bc1b3aaf4649c07ae168ca11814c6705da GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V4Ume;uumf=gqByzONlb90Gr9 zPyO5VqG3bJrWYz94(x6@k(Eo-V?EAAx<6hXbF+K-PMPByFa8YY|L}sPgHu3QLCwLZ zVFrUCV-nL5guso|m)C#WeSh-$PUQ8Sy}#~+9RIv>fRZwl$${cTBzI|iz{rqJ2 zzvot-yE*+`+dKX8eU8#|UOoB$N>~J|B{_wXO1Bxcr5r5UZoG1O>TT;u#qj a{{I)U(akqJ7On`4F9uInKbLh*2~7a`vZBKP literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestOffsetTexture.png b/internal/rendertest/refs/TestOffsetTexture.png new file mode 100644 index 0000000000000000000000000000000000000000..7f9f029d8aaaae5de06af1f46a1ea5f48270d92e GIT binary patch literal 539 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V0`51;uumf=gl3%tSOEX4G$wd zb(3Vju+4J_TJnKM&RJTw_vx}v5^+x4>k}7SHnf;%*7qBHK78o)qt935oll(pbM*KI z@!Q*Yt^8lvKd4uC@M)OAV91!nbcCgYQ$Scj4I!Xc<}1&~?fgFP_~!o48)vtg{`h)# zN0sH=TXu6ly4~ePR}Rr%cH^tb#}EI??7qA{TTx*rU;VYz?sJ7@-M)E#x%cDF^#2Nf z8+}YS_om#vzW3b!C$4T?e5~;0isEO&A}Dsl9Fz0h=-lBo!Nttj9LUn~wnn_9)_i{D z9+}4#vlh?$C;6S*eeUMDr+1v^zP&?!|Ma-vyE-zZ^)m5xKFiPjN@uTa!eP#B*21DZ ogx6u#;|?4esrbvw!0`WnT8O2X4oBQ}V5Bp6y85}Sb4q9e06hG{;{X5v literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestPaintClippedCirle.png b/internal/rendertest/refs/TestPaintClippedCirle.png new file mode 100644 index 0000000000000000000000000000000000000000..9aa298e91a2e29d0f32ea857d01462be7a8e13c2 GIT binary patch literal 571 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^U}ExgaSW-L^X8V}sYd}K4i9HP zynjw#?A9*Mzzc70>=0rt6&H(IVidJ&vPav?WiuqFls`PM=kwOa<3|sBA2NUV zu)y+Qo#~w3SJfX&K3E^WK=ZHOiq}adzdoz(x2STy$@k*-jhstqD}!$GW!#?O_rL0@ z?1#2ZvKO{xHeXq#bD(a%naA2wd5*rH*RQ_UbnihR%Nm^nInzLbOl!g*YEmYFxjn}> zMl3lSq}s2Pa5UfX{{2FmXJ>+z_FTX4(}u6TrF7L4!3CLhww!s}Bi0Ltt@in7{Gfb& z_?`FvRDVvq!tiU3yPyH{0UicphBFL9mhoGXf#LuEH{F36y}?FPfQf>^)78&qol`;+ E06tdIp#T5? literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestPaintClippedRect.png b/internal/rendertest/refs/TestPaintClippedRect.png new file mode 100644 index 0000000000000000000000000000000000000000..4bd3e5cbbb6c7b40f9e49d9356b1652c33078dfa GIT binary patch literal 382 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V086#aSW-L^XAq;P8UT1)`O*n z|EK)*~{hCtqz z)@6ALsxJ~1{_p+0$ly)p{p2@=IcFbV{JXQ_d;gwS{5EqL3>lM{j<9ra3J5ExIrucp zz$#F@ru$`S-SxYEhwRPpC_^^r#+|;+n$mbpMOKd8-T_mwkCB1l|Nkc)+&`377gqp7 On8DN4&t;ucLK6VM;Big> literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestPaintOffset.png b/internal/rendertest/refs/TestPaintOffset.png new file mode 100644 index 0000000000000000000000000000000000000000..aa5d7d8f3a32a7c0814e4124d5d03027b3cbea96 GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^VD$2IaSW-L^X85r*AWE)hl`~j z|Am{mPcW%G_~-g9o|=0_y>;22lpA0y%m2j#p$% zi01osJdYW_Qo@E2sp9U@HO+a!dxNEMIf_Vg@OliN8L^BE4FCTxZsTBTIvH#S3|t0J LS3j3^P6umHT5ZV8u=!`01N-)|*N6 z_{3(#$RVkDSKMN0+)y~JI*@!K<{=g0w^9q386{^y(UrSie5Gof2EDVeadb%a0u({U}0!A~LWHO@lixL=IlpODZR#mMaHh{lN&9+j7UGvvCJa&u(>HEvMK+MX#Q3*`%dq@N(}C52CYg2M0NVrufPD|(!t8(*`J#LQ(jPLeYV=zb^ zIyk3C6eiBp;5rIMCtTs^)QN?^e4r3G-sFm@i{8b}J{a$^ieKkcNU++_Y$33rJ2)5M zObb)jwo>FV2}Gsy9gXzBk}S4Ke}&#eq+OG~Iub<#=Wu4qOgk?dbnt4nJ*cfMC`5f=R(aSmTF8l}j4T&taTBH#X0p|dz+bP^f0{5Rk2 zV!4#+MPYb0G&Stm^mS1%Hh%j;?ZbfB(lRKd3G#8t$WCH2nl7&J+HbS?`4oduTjxwB zFOvSW#h6fO-~$IpDp!`K87|dx@XN*AuC|9N>b^X;gTrn>U>i+ zT~LVa0gWrs-3RX31ru!^-nuAYG;b8Q`gRF6z%&De0&`$|NAP@ a005Z`5lZT15yMS(s0$DYB$oy!YRW&pIfuOf literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestPaintShear.png b/internal/rendertest/refs/TestPaintShear.png new file mode 100644 index 0000000000000000000000000000000000000000..0a7d553c420c4f289899b03b95cfe328a39d45c6 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^U_9pO;uumf=gsBVtlJI}4uRn% z=^r;u-MW*7D|eQ~M4huS&gW|{oZnM(SmxRD+*tkVuT$$@S5;PWKC_EFeOq%)w!zP; zy?get70+Mu^zFd~*+2iL32goIGbLclp5Ms^d!OECWh?&8a3CRXKBK{m8?p>+n|JXa zNbrhhGLX1m&cMUAow4EIiEWGq64%lg*phECA4qVpW|Ww7w~m2r`8NTE=Fe3k4F^4C z84?QIjx$Keyfa{EPM*tn;DLiWqd~=%&kPbaS5`9cd@DC&Xr9Zk5cn<1!0`XS(9|V=>|_(FfDy~!>FVdQ&MBb@05$Kk Ap#T5? literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestPaintTexture.png b/internal/rendertest/refs/TestPaintTexture.png new file mode 100644 index 0000000000000000000000000000000000000000..33feacd3819325766bb54ed0fe02e8528372c623 GIT binary patch literal 408 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V2t;4aSW-L^X9f=SBnFW!^NTx z_a2{J{r-aEyx#wBRS&7J4tO3_UHjIG?f$Htzv~UYv(^7%zqgJdf^h>=0_y>;22loW zhA@UTSOt<}o7Z2AEjKE+f30$dErXD9h=FtWS6r7pJ9kg_K1XT1rX%dXW}Uc(mw;Pf ys;$3W?pvaNnT??1VH$9IkAR9B>I@A3|1Ucy^|d$2;x{nF89ZJ6T-G@yGywpG_I=F& literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestRepeatedPaintsZ.png b/internal/rendertest/refs/TestRepeatedPaintsZ.png new file mode 100644 index 0000000000000000000000000000000000000000..1da622452b0c735191ec80a978f0ff6d8936ad26 GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V6^viaSW-L^X85rSA&57Yv9y7 z|4)5nyWQb4@%t^4#)sQ?e>2^AJbYjEEB-lt4Ko-F8Izcfuyk+=2rH;Dq;EzOaqyv7 wVB5EO`Dq{Zxgl0i!9X=ynQ=p!f#LuEPqwTKGq1(I28Jkur>mdKI;Vst0I|(z*8l(j literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestReuseStencil.png b/internal/rendertest/refs/TestReuseStencil.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea05a002ade8facefd7ef5a07cb1867c6c48d3e GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V080zaSW-L^XAS*-XjVE303%^*|#|c__r&W6z7FAsAKIxk8)FQ{UDB8jH&4K`z z0Ev$aUY~d~Yi;8{<#ZKfmsl-8uK>*+b`*S5<^2K1@t6+mXFP=0{$} zUcKY}-U~1MvYa>}Bc#dkV@1jPvho6@1FOS@`0{TFah;s1UG!?okD6WOc~)<~Gn}53 zv#mDSPXF=aq6ZBslZ}j(&u(4zE>pt$x8#N$8tnX9?meQ%{o?!OyXRXJB^4wDyiVa+ zVte53?xLTYid-(G{(pb>@m|3lh0k6{{-1FFX;J3fsg_^ZR)jshptv$*O7o<*$E}`q z$J&$`nh3r6{^j-T*@|D-D!%!gn-|*mW$pz-C9k1uFn6LOLamnFgqyA$I{`+c`?nS&-+I+V(?Y($c zyLy|@;rFHgf2YNI3qGBHdgae6_xIoC-S@|}>-Yc1#xCxQXFW?3iDfLh|9snxs&}%M z+y6OV3c6Dloj=uf;RBC=%XfMfJr%kVk-Ow`=GBgCrJi43y?*r~)IZ*Tjaa57^D^;e z%j;$b^=|0j_xJt7_~`UwX%G1iS*fKd=rYRHetuEkqxWw5qPvgJcV9X?arVCQ>eQ+0 zJ~MjRPgR?y9-Pt5?ZPIpko+s3KmUAl{pRnfzu(pe@B8twDmBlK ztvb^;>T%MX)JX5s-sksM*R3vC{pokS_M%e^FSo?(T9RqiR`f{uWY*eWRo_a}w2XgV z&7EnpMq=j%g}VLkUd}k$$*}ENRPi_V@0XWP^2)VU5Sv{6eaY=```eb{3C=rpcFwu8 zQ~BqgWp8)RnPc|xVLBGzjyyx*2wA{XbbX;+cTk& zac2RewIELo!|c?rcdp(~J2kzy`tu8Ekp=073=+%@LyY(<$iVRb|Fy7h#U_D|O@UXe zzo_fG7;1e|Xoz ztLsAN{?j=fAR--q=z7v+IfIpTp*Q)>L?#4f#g)~5wT@!f-#tg1BVXk~#NTCAYn^QO zJ-*`lHOf@qcN^a}fr6EF?(ZM$nY_6<>Fs*kwf~+U*&T6jx@%hVr(L!WEMAFSz5Moz z!`xfBK3_|2o4DrPO>kwLer9tuNB-~Ajr+bhv*-WbP<_Hsd}*nLOi+(z%fW9u_)8_W zmPE_ma&Dd`al8I}ODKyBUwh`)YWDxvD@rD`w!FUIw#A725Xnf{5ed|5HY{}L6a|-UASD77W+@_~~&g{dPDra-G?^pl-d6GZh zw^p8^*q6~YaLV`8J>Aon>1FsS{WmQ1+x+s<$@~AF%U|nQn9FkOc#sh5{d zer)$lprvP_!Rj8yw|iIc81_ZC&rCm|GyB+$y(@lRE1zea&3njI(2eictMkhiAHH#L z=P&Kp>(i6(-n?+RIFNtI&G8|5a&C!oL&&r$3(q z?=AOvXJfnCo?R#9^3s;`jWGzqC(o zd~;h)YpaR`rjS cf#LuE;7vPyxu>|h0&@<7r>mdKI;Vst0MVvvWB>pF literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestTransformMacro.png b/internal/rendertest/refs/TestTransformMacro.png new file mode 100644 index 0000000000000000000000000000000000000000..07570593132d2989da68a6ba5826a9c922e89c19 GIT binary patch literal 375 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V6^jeaSW-L^XB$O-Ub5!7DtD7 z`=2gnPT$a&9;7}kO#EN*L?Hz=2cL!+42FzJOh;HcI0b|iwx#{9`TuQVa3s2f8bkVK zRHYbt@e8b-`FE+o{Y?~^0dgUJTd1L8FCzoP|NqZpSQ)r3Nk;+$l)=;0&t;ucLK6Ub CjAq^d literal 0 HcmV?d00001 diff --git a/internal/rendertest/refs/TestTransformOrder.png b/internal/rendertest/refs/TestTransformOrder.png new file mode 100644 index 0000000000000000000000000000000000000000..27c91fb0fdaef6bce02a091a66c5bb4031962e5e GIT binary patch literal 384 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V08C%aSW-L^XBeF&I1ZO3>TyB z)Ti!tc@VhA+(Dr459iJ2bJWkRmM_@Nl)!p`t3i}On<0#04MPOu2D}0}{k~DzIal;~ z{w~jA#;=sHVf0av^KRaU3$=G|Z($_lIW+ftV`X6Y|NrGK4yNOcq04|F%i!ti=d#Wz Gp$P!N)oXwN literal 0 HcmV?d00001 diff --git a/internal/rendertest/render_test.go b/internal/rendertest/render_test.go new file mode 100644 index 00000000..fe0e78be --- /dev/null +++ b/internal/rendertest/render_test.go @@ -0,0 +1,144 @@ +package rendertest + +import ( + "math" + "testing" + + "gioui.org/f32" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "golang.org/x/image/colornames" +) + +func TestTransformMacro(t *testing.T) { + // testcase resulting from original bug when rendering layout.Stacked + + // pre-build the text + c := createText() + + run(t, func(o *op.Ops) { + + // render the first Stacked item + m1 := op.Record(o) + dr := f32.Rect(0, 0, 128, 50) + paint.ColorOp{Color: colornames.Black}.Add(o) + paint.PaintOp{Rect: dr}.Add(o) + c1 := m1.Stop() + + // Render the second stacked item + m2 := op.Record(o) + paint.ColorOp{Color: colornames.Red}.Add(o) + // Simulate a draw text call + stack := op.Push(o) + op.TransformOp{}.Offset(f32.Pt(0, 10)).Add(o) + + // Actually create the text clip-path + c.Add(o) + + paint.PaintOp{Rect: f32.Rect(0, 0, 10, 10)}.Add(o) + stack.Pop() + + c2 := m2.Stop() + + // Call each of them in a transform + s1 := op.Push(o) + op.TransformOp{}.Offset(f32.Pt(0, 0)).Add(o) + c1.Add(o) + s1.Pop() + s2 := op.Push(o) + op.TransformOp{}.Offset(f32.Pt(0, 0)).Add(o) + c2.Add(o) + s2.Pop() + }, func(r result) { + r.expect(5, 15, colornames.Red) + r.expect(15, 15, colornames.Black) + r.expect(11, 51, colornames.White) + }) +} + +func TestRepeatedPaintsZ(t *testing.T) { + run(t, func(o *op.Ops) { + // Draw a rectangle + paint.ColorOp{Color: colornames.Black}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 128, 50)}.Add(o) + + builder := clip.Path{} + builder.Begin(o) + builder.Move(f32.Pt(0, 0)) + builder.Line(f32.Pt(10, 0)) + builder.Line(f32.Pt(0, 10)) + builder.Line(f32.Pt(-10, 0)) + builder.Line(f32.Pt(0, -10)) + builder.End().Add(o) + paint.ColorOp{Color: colornames.Red}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 10, 10)}.Add(o) + }, func(r result) { + r.expect(5, 5, colornames.Red) + r.expect(11, 15, colornames.Black) + r.expect(11, 51, colornames.White) + }) +} + +func TestNoClipFromPaint(t *testing.T) { + // ensure that a paint operation does not polute the state + // by leaving any clip paths i place. + run(t, func(o *op.Ops) { + a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4) + op.Affine(a).Add(o) + paint.ColorOp{Color: colornames.Red}.Add(o) + paint.PaintOp{Rect: f32.Rect(10, 10, 30, 30)}.Add(o) + a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4) + op.Affine(a).Add(o) + + paint.ColorOp{Color: colornames.Black}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 50, 50)}.Add(o) + }, func(r result) { + r.expect(1, 1, colornames.Black) + r.expect(20, 20, colornames.Black) + r.expect(49, 49, colornames.Black) + r.expect(51, 51, colornames.White) + }) +} + +func createText() op.CallOp { + innerOps := new(op.Ops) + m := op.Record(innerOps) + builder := clip.Path{} + builder.Begin(innerOps) + builder.Move(f32.Pt(0, 0)) + builder.Line(f32.Pt(10, 0)) + builder.Line(f32.Pt(0, 10)) + builder.Line(f32.Pt(-10, 0)) + builder.Line(f32.Pt(0, -10)) + builder.End().Add(innerOps) + return m.Stop() +} + +func drawChild(ops *op.Ops, text op.CallOp) op.CallOp { + r1 := op.Record(ops) + text.Add(ops) + paint.PaintOp{Rect: f32.Rect(0, 0, 10, 10)}.Add(ops) + return r1.Stop() +} + +func TestReuseStencil(t *testing.T) { + txt := createText() + run(t, func(ops *op.Ops) { + c1 := drawChild(ops, txt) + c2 := drawChild(ops, txt) + + // lay out the children + stack1 := op.Push(ops) + c1.Add(ops) + stack1.Pop() + + stack2 := op.Push(ops) + op.TransformOp{}.Offset(f32.Pt(0, 50)).Add(ops) + c2.Add(ops) + stack2.Pop() + }, func(r result) { + r.expect(5, 5, colornames.Black) + r.expect(5, 55, colornames.Black) + }) +} diff --git a/internal/rendertest/transform_test.go b/internal/rendertest/transform_test.go new file mode 100644 index 00000000..ed7a1d2f --- /dev/null +++ b/internal/rendertest/transform_test.go @@ -0,0 +1,201 @@ +package rendertest + +import ( + "math" + "testing" + + "gioui.org/f32" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "golang.org/x/image/colornames" +) + +func TestPaintOffset(t *testing.T) { + run(t, func(o *op.Ops) { + op.Offset(f32.Pt(10, 20)).Add(o) + paint.ColorOp{Color: colornames.Red}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 50, 50)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.White) + r.expect(59, 30, colornames.Red) + r.expect(60, 30, colornames.White) + r.expect(10, 70, colornames.White) + }) +} + +func TestPaintRotate(t *testing.T) { + run(t, func(o *op.Ops) { + a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8) + op.Affine(a).Add(o) + paint.ColorOp{Color: colornames.Red}.Add(o) + paint.PaintOp{Rect: f32.Rect(20, 20, 60, 60)}.Add(o) + }, func(r result) { + r.expect(40, 40, colornames.Red) + r.expect(50, 19, colornames.Red) + r.expect(59, 19, colornames.White) + r.expect(21, 21, colornames.White) + }) +} + +func TestPaintShear(t *testing.T) { + run(t, func(o *op.Ops) { + a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0) + op.Affine(a).Add(o) + paint.ColorOp{Color: colornames.Red}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 40, 40)}.Add(o) + }, func(r result) { + r.expect(10, 30, colornames.White) + }) +} + +func TestClipPaintOffset(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + clip.Rect{Rect: f32.Rect(10, 10, 30, 30)}.Add(o) + op.Offset(f32.Pt(20, 20)).Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 100, 100)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.White) + r.expect(19, 19, colornames.White) + r.expect(20, 20, colornames.Red) + r.expect(30, 30, colornames.White) + }) +} + +func TestClipOffset(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + op.Offset(f32.Pt(20, 20)).Add(o) + clip.Rect{Rect: f32.Rect(10, 10, 30, 30)}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 100, 100)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.White) + r.expect(29, 29, colornames.White) + r.expect(30, 30, colornames.Red) + r.expect(49, 49, colornames.Red) + r.expect(50, 50, colornames.White) + }) +} + +func TestClipScale(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10)) + op.Affine(a).Add(o) + clip.Rect{Rect: f32.Rect(10, 10, 20, 20)}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 1000, 1000)}.Add(o) + }, func(r result) { + r.expect(19+10, 19+10, colornames.White) + r.expect(20+10, 20+10, colornames.Red) + r.expect(39+10, 39+10, colornames.Red) + r.expect(40+10, 40+10, colornames.White) + }) +} + +func TestClipRotate(t *testing.T) { + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Add(o) + clip.Rect{Rect: f32.Rect(30, 30, 50, 50)}.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 40, 100, 100)}.Add(o) + }, func(r result) { + r.expect(39, 39, colornames.White) + r.expect(41, 41, colornames.Red) + r.expect(50, 50, colornames.White) + }) +} + +func TestOffsetTexture(t *testing.T) { + run(t, func(o *op.Ops) { + op.Offset(f32.Pt(15, 15)).Add(o) + squares.Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 50, 50)}.Add(o) + }, func(r result) { + r.expect(14, 20, colornames.White) + r.expect(66, 20, colornames.White) + r.expect(16, 64, colornames.Green) + r.expect(64, 16, colornames.Green) + }) +} + +func TestOffsetScaleTexture(t *testing.T) { + run(t, func(o *op.Ops) { + op.Offset(f32.Pt(15, 15)).Add(o) + squares.Add(o) + op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 50, 50)}.Add(o) + }, func(r result) { + r.expect(114, 64, colornames.Blue) + r.expect(116, 64, colornames.White) + }) +} + +func TestRotateTexture(t *testing.T) { + run(t, func(o *op.Ops) { + squares.Add(o) + a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/4) + op.Affine(a).Add(o) + paint.PaintOp{Rect: f32.Rect(30, 30, 50, 50)}.Add(o) + }, func(r result) { + r.expect(40, 40-12, colornames.Blue) + r.expect(40+12, 40, colornames.Green) + }) +} + +func TestRotateClipTexture(t *testing.T) { + run(t, func(o *op.Ops) { + squares.Add(o) + a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/8) + op.Affine(a).Add(o) + clip.Rect{Rect: f32.Rect(30, 30, 50, 50)}.Add(o) + paint.PaintOp{Rect: f32.Rect(10, 10, 70, 70)}.Add(o) + }, func(r result) { + r.expect(0, 0, colornames.White) + r.expect(37, 39, colornames.Green) + r.expect(36, 39, colornames.Green) + r.expect(35, 39, colornames.Green) + r.expect(34, 39, colornames.Green) + r.expect(33, 39, colornames.Green) + }) +} + +func TestComplicatedTransform(t *testing.T) { + run(t, func(o *op.Ops) { + squares.Add(o) + + clip.Rect{Rect: f32.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Add(o) + + a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0) + op.Affine(a).Add(o) + clip.Rect{Rect: f32.Rect(0, 0, 50, 40)}.Add(o) + + op.Offset(f32.Pt(-100, -100)).Add(o) + paint.PaintOp{Rect: f32.Rect(100, 100, 150, 150)}.Add(o) + }, func(r result) { + r.expect(20, 5, colornames.White) + }) +} + +func TestTransformOrder(t *testing.T) { + // check the ordering of operations bot in affine and in gpu stack. + run(t, func(o *op.Ops) { + paint.ColorOp{Color: colornames.Red}.Add(o) + + a := f32.Affine2D{}.Offset(f32.Pt(64, 64)) + op.Affine(a).Add(o) + + b := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(8, 8)) + op.Affine(b).Add(o) + + c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5)) + op.Affine(c).Add(o) + paint.PaintOp{Rect: f32.Rect(0, 0, 20, 20)}.Add(o) + }, func(r result) { + // centered and with radius 40 + r.expect(64-41, 64, colornames.White) + r.expect(64-39, 64, colornames.Red) + r.expect(64+39, 64, colornames.Red) + r.expect(64+41, 64, colornames.White) + }) +} diff --git a/internal/rendertest/util_test.go b/internal/rendertest/util_test.go index f0ee0ed7..a8754be4 100644 --- a/internal/rendertest/util_test.go +++ b/internal/rendertest/util_test.go @@ -38,33 +38,40 @@ func init() { squares = paint.NewImageOp(im) } -func drawImage(size int, draw func(o *op.Ops)) (im *image.RGBA, err error) { +func drawImage(size int, ops *op.Ops, draw func(o *op.Ops)) (im *image.RGBA, err error) { sz := image.Point{X: size, Y: size} w, err := headless.NewWindow(sz.X, sz.Y) if err != nil { return im, err } - ops := new(op.Ops) draw(ops) w.Frame(ops) return w.Screenshot() } -func run(t *testing.T, f func(o *op.Ops)) result { - img, err := drawImage(128, f) - if err != nil { - t.Error("error rendering:", err) +func run(t *testing.T, f func(o *op.Ops), c func(r result)) { + // draw a few times and check that it is correct each time, to + // ensure any caching effects still generate the correct images. + ok := true + var img *image.RGBA + var err error + ops := new(op.Ops) + for i := 0; i < 3; i++ { + ops.Reset() + img, err = drawImage(128, ops, f) + if err != nil { + t.Error("error rendering:", err) + } + // check for a reference image and make sure we are identical. + ok = ok && verifyRef(t, img) + c(result{t: t, img: img}) } - // check for a reference image and make sure we are identical. - ok := verifyRef(t, img) - if *dumpImages || !ok { if err := saveImage(t.Name()+".png", img); err != nil { t.Error(err) } } - return result{t: t, img: img} } func verifyRef(t *testing.T, img *image.RGBA) (ok bool) {