From bebc73db37b4d3ed56aee604d3388b2b1c5bafaf Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 22 Oct 2022 11:04:22 -0600 Subject: [PATCH] gpu: implement automatic mipmaps for images All GPU APIs except OpenGL ES 2 can generate mipmaps for textures. This trades 33% more GPU memory use for improved rendering quality and speed for downscaled images. Signed-off-by: Elias Naur --- gpu/gpu.go | 2 +- gpu/internal/d3d11/d3d11_windows.go | 29 +++++++- gpu/internal/driver/driver.go | 1 + gpu/internal/metal/metal_darwin.go | 42 ++++++++--- gpu/internal/opengl/opengl.go | 37 ++++++++-- .../refs/TestComplicatedTransform.png | Bin 491 -> 569 bytes .../refs/TestOffsetScaleTexture.png | Bin 273 -> 402 bytes .../rendertest/refs/TestOffsetTexture.png | Bin 283 -> 386 bytes .../refs/TestPaintClippedTexture.png | Bin 219 -> 304 bytes .../rendertest/refs/TestPaintTexture.png | Bin 280 -> 383 bytes .../rendertest/refs/TestRotateClipTexture.png | Bin 597 -> 791 bytes .../rendertest/refs/TestRotateTexture.png | Bin 374 -> 1026 bytes gpu/internal/vulkan/vulkan.go | 61 ++++++++++++++-- internal/d3d11/d3d11_windows.go | 12 ++++ internal/gl/gl.go | 1 + internal/gl/gl_js.go | 5 ++ internal/gl/gl_unix.go | 11 +++ internal/gl/gl_windows.go | 4 ++ internal/vk/vulkan.go | 66 +++++++++++++++--- 19 files changed, 233 insertions(+), 38 deletions(-) diff --git a/gpu/gpu.go b/gpu/gpu.go index cb566edb..1b92ccee 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -436,7 +436,7 @@ func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Text if tex.tex != nil { return tex.tex } - handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinear, driver.FilterLinear, driver.BufferBindingTexture) + handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinearMipmapLinear, driver.FilterLinear, driver.BufferBindingTexture) if err != nil { panic(err) } diff --git a/gpu/internal/d3d11/d3d11_windows.go b/gpu/internal/d3d11/d3d11_windows.go index a22510c0..b737f2cc 100644 --- a/gpu/internal/d3d11/d3d11_windows.go +++ b/gpu/internal/d3d11/d3d11_windows.go @@ -7,6 +7,7 @@ import ( "fmt" "image" "math" + "math/bits" "unsafe" "golang.org/x/sys/windows" @@ -58,6 +59,7 @@ type Texture struct { width int height int + mipmap bool foreign bool } @@ -219,17 +221,33 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min default: return nil, fmt.Errorf("unsupported texture format %d", format) } + bindFlags := convBufferBinding(bindings) + miscFlags := uint32(0) + mipmap := minFilter == driver.FilterLinearMipmapLinear + nmipmaps := 1 + if mipmap { + // Flags required by ID3D11DeviceContext::GenerateMips. + bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET + miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS + dim := width + if height > dim { + dim = height + } + log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1 + nmipmaps = log2 + 1 + } tex, err := b.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{ Width: uint32(width), Height: uint32(height), - MipLevels: 1, + MipLevels: uint32(nmipmaps), ArraySize: 1, Format: d3dfmt, SampleDesc: d3d11.DXGI_SAMPLE_DESC{ Count: 1, Quality: 0, }, - BindFlags: convBufferBinding(bindings), + BindFlags: bindFlags, + MiscFlags: miscFlags, }) if err != nil { return nil, err @@ -247,6 +265,8 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min filter = d3d11.FILTER_MIN_MAG_MIP_POINT case minFilter == driver.FilterLinear && magFilter == driver.FilterLinear: filter = d3d11.FILTER_MIN_MAG_LINEAR_MIP_POINT + case minFilter == driver.FilterLinearMipmapLinear && magFilter == driver.FilterLinear: + filter = d3d11.FILTER_MIN_MAG_MIP_LINEAR default: d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release) return nil, fmt.Errorf("unsupported texture filter combination %d, %d", minFilter, magFilter) @@ -325,7 +345,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min return nil, err } } - return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, renderTarget: fbo, bindings: bindings, width: width, height: height}, nil + return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, renderTarget: fbo, bindings: bindings, width: width, height: height, mipmap: mipmap}, nil } func (b *Backend) newInputLayout(vertexShader shader.Sources, layout []driver.InputDesc) (*d3d11.InputLayout, error) { @@ -609,6 +629,9 @@ func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { } res := (*d3d11.Resource)(unsafe.Pointer(t.tex)) t.backend.ctx.UpdateSubresource(res, dst, uint32(stride), uint32(len(pixels)), pixels) + if t.mipmap { + t.backend.ctx.GenerateMips(t.resView) + } } func (t *Texture) Release() { diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index 58cb89bd..d0e412c9 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -166,6 +166,7 @@ const ( const ( FilterNearest TextureFilter = iota FilterLinear + FilterLinearMipmapLinear ) const ( diff --git a/gpu/internal/metal/metal_darwin.go b/gpu/internal/metal/metal_darwin.go index 1d1aabaf..5d4a3391 100644 --- a/gpu/internal/metal/metal_darwin.go +++ b/gpu/internal/metal/metal_darwin.go @@ -239,6 +239,14 @@ static void blitEncCopyBufferToTexture(CFTypeRef blitEncRef, CFTypeRef bufRef, C } } +static void blitEncGenerateMipmapsForTexture(CFTypeRef blitEncRef, CFTypeRef texRef) { + @autoreleasepool { + id enc = (__bridge id)blitEncRef; + id tex = (__bridge id)texRef; + [enc generateMipmapsForTexture: tex]; + } +} + static void blitEncCopyTextureToBuffer(CFTypeRef blitEncRef, CFTypeRef texRef, CFTypeRef bufRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) { @autoreleasepool { id enc = (__bridge id)blitEncRef; @@ -269,25 +277,26 @@ static void blitEncCopyBufferToBuffer(CFTypeRef blitEncRef, CFTypeRef srcRef, CF } } -static CFTypeRef newTexture(CFTypeRef devRef, NSUInteger width, NSUInteger height, MTLPixelFormat format, MTLTextureUsage usage) { +static CFTypeRef newTexture(CFTypeRef devRef, NSUInteger width, NSUInteger height, MTLPixelFormat format, MTLTextureUsage usage, int mipmapped) { @autoreleasepool { id dev = (__bridge id)devRef; MTLTextureDescriptor *mtlDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format width: width height: height - mipmapped: NO]; + mipmapped: mipmapped ? YES : NO]; mtlDesc.usage = usage; mtlDesc.storageMode = MTLStorageModePrivate; return CFBridgingRetain([dev newTextureWithDescriptor:mtlDesc]); } } -static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter) { +static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter, MTLSamplerMipFilter mipFilter) { @autoreleasepool { id dev = (__bridge id)devRef; MTLSamplerDescriptor *desc = [MTLSamplerDescriptor new]; desc.minFilter = minFilter; desc.magFilter = magFilter; + desc.mipFilter = mipFilter; return CFBridgingRetain([dev newSamplerStateWithDescriptor:desc]); } } @@ -405,6 +414,7 @@ type Texture struct { sampler C.CFTypeRef width int height int + mipmap bool foreign bool } @@ -581,26 +591,33 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min if bindings&driver.BufferBindingShaderStorageWrite != 0 { usage |= C.MTLTextureUsageShaderWrite } - tex := C.newTexture(b.dev, C.NSUInteger(width), C.NSUInteger(height), mformat, usage) + min, mip := samplerFilterFor(minFilter) + max, _ := samplerFilterFor(magFilter) + mipmap := mip != C.MTLSamplerMipFilterNotMipmapped + mipmapped := C.int(0) + if mipmap { + mipmapped = 1 + } + tex := C.newTexture(b.dev, C.NSUInteger(width), C.NSUInteger(height), mformat, usage, mipmapped) if tex == 0 { return nil, errors.New("metal: [MTLDevice newTextureWithDescriptor:] failed") } - min := samplerFilterFor(minFilter) - max := samplerFilterFor(magFilter) - s := C.newSampler(b.dev, min, max) + s := C.newSampler(b.dev, min, max, mip) if s == 0 { C.CFRelease(tex) return nil, errors.New("metal: [MTLDevice newSamplerStateWithDescriptor:] failed") } - return &Texture{backend: b, texture: tex, sampler: s, width: width, height: height}, nil + return &Texture{backend: b, texture: tex, sampler: s, width: width, height: height, mipmap: mipmap}, nil } -func samplerFilterFor(f driver.TextureFilter) C.MTLSamplerMinMagFilter { +func samplerFilterFor(f driver.TextureFilter) (C.MTLSamplerMinMagFilter, C.MTLSamplerMipFilter) { switch f { case driver.FilterNearest: - return C.MTLSamplerMinMagFilterNearest + return C.MTLSamplerMinMagFilterNearest, C.MTLSamplerMipFilterNotMipmapped case driver.FilterLinear: - return C.MTLSamplerMinMagFilterLinear + return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterNotMipmapped + case driver.FilterLinearMipmapLinear: + return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterLinear default: panic("invalid texture filter") } @@ -900,6 +917,9 @@ func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { depth: 1, } C.blitEncCopyBufferToTexture(enc, buf, t.texture, C.NSUInteger(off), C.NSUInteger(dstStride), C.NSUInteger(len(store)), msize, orig) + if t.mipmap { + C.blitEncGenerateMipmapsForTexture(enc, t.texture) + } } func (t *Texture) Release() { diff --git a/gpu/internal/opengl/opengl.go b/gpu/internal/opengl/opengl.go index 7148e930..7ce5a577 100644 --- a/gpu/internal/opengl/opengl.go +++ b/gpu/internal/opengl/opengl.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "image" + "math/bits" "runtime" "strings" "time" @@ -105,6 +106,7 @@ type texture struct { triple textureTriple width int height int + mipmap bool bindings driver.BufferBinding foreign bool } @@ -695,13 +697,29 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min return nil, errors.New("unsupported texture format") } b.BindTexture(0, tex) - b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, toTexFilter(magFilter)) - b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, toTexFilter(minFilter)) + min, mipmap := toTexFilter(minFilter) + mag, _ := toTexFilter(magFilter) + if b.gles && b.glver[0] < 3 { + // OpenGL ES 2 only supports mipmaps for power-of-two textures. + mipmap = false + } + tex.mipmap = mipmap + b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag) + b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min) b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - if b.gles && b.glver[0] >= 3 { + if mipmap { + nmipmaps := 1 + if mipmap { + dim := width + if height > dim { + dim = height + } + log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1 + nmipmaps = log2 + 1 + } // Immutable textures are required for BindImageTexture, and can't hurt otherwise. - b.funcs.TexStorage2D(gl.TEXTURE_2D, 1, tex.triple.internalFormat, width, height) + b.funcs.TexStorage2D(gl.TEXTURE_2D, nmipmaps, tex.triple.internalFormat, width, height) } else { b.funcs.TexImage2D(gl.TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ) } @@ -1195,12 +1213,14 @@ func (p *pipeline) Release() { *p = pipeline{} } -func toTexFilter(f driver.TextureFilter) int { +func toTexFilter(f driver.TextureFilter) (int, bool) { switch f { case driver.FilterNearest: - return gl.NEAREST + return gl.NEAREST, false case driver.FilterLinear: - return gl.LINEAR + return gl.LINEAR, false + case driver.FilterLinearMipmapLinear: + return gl.LINEAR_MIPMAP_LINEAR, true default: panic("unsupported texture filter") } @@ -1234,6 +1254,9 @@ func (t *texture) Upload(offset, size image.Point, pixels []byte, stride int) { } t.backend.glstate.pixelStorei(t.backend.funcs, gl.UNPACK_ROW_LENGTH, rowLen) t.backend.funcs.TexSubImage2D(gl.TEXTURE_2D, 0, offset.X, offset.Y, size.X, size.Y, t.triple.format, t.triple.typ, pixels) + if t.mipmap { + t.backend.funcs.GenerateMipmap(gl.TEXTURE_2D) + } } func (t *timer) Begin() { diff --git a/gpu/internal/rendertest/refs/TestComplicatedTransform.png b/gpu/internal/rendertest/refs/TestComplicatedTransform.png index 4a92e3c78f7091b335854170793757da373c2df7..d1adb55d4557bec88de2736bfcf689621a1aed82 100644 GIT binary patch delta 509 zcmaFOypv^uay=7+r;B4q#jUruH)e?hO0+%nx3-=wl9ahH;)bLS%l6K2Lq_Y}9IH8# zHmip(xN(DHnwi+Q&Am%2Sh+R|u6ETG3Gyh?VOqpC>8>gB%!ic^(oZ-3yw7}^XY!mf zRq>!A>&bUctM~_hvQo_t-ZXiVzUEEelXcsQwRsNSyP8t3d_BE>{if3Y#a^$NjEnwP zJ^wp%$vux(kC)W>Wc{^Wy?NHzLeYZ?SEr`@PYkmEv-L~8I}5Y%$*F6$pP#&|`pF|# z0|V9R!JmvKzgfCuS?23Ij2#QEcgK64+&txv%@zego*Rqj_@2BwrN*|%QAuI}`(tm- zO5^WME(R;=zn2%)eye@R+PR?qd$;}cE8_pRDhPfl`zb#8*E<(6m8N-_%dTJYv0pm# z+TqK3mY418`e&=I-*~U<0gK$LJ3c43C;Q)d_T#}4y`oLC%Qgn@J!2Z=x?#)PIsPa6 z{lrz8g8xrQzw)!mMS)4M!+}LfppgTac|kb(aiH1p(sws2f;MjpE8eiAa(QO<^Xn0F zI}6M?^aqB0!VmA;Zy1@L}?ZD^qZ9jfInetgn lQgHG{M&-!|7&*mbf3f~PW@%Ge#aF-p1fH&bF6*2UngE{v?Wh0% delta 391 zcmV;20eJqo1nUEkBYy$1Nkl<*J$c9k2cJj`L9 zWX$ta-lpRc95$X^Ej0s{~SmJi#+wWGUna62NJ=e?(QEY?bqyqM9}^4*OK;XtARw& z?f5s7_TfSX5;#F;hNJxqM_)5ct*dK2iC}Aoxu43HB!Wx-b8oftfkczc0T~H&qpf!SgwIn%lgLhFay?_Dr;B4q#jUru7kaTc${hcAU#rPgr^(eILN{%)!Vea+T8`Zho9?z> zcqYuzbTVh76iau4V7G$NG~v^%(^J{=zkhyt{LcSx)praJt1v(SPr&83PnUlAc5a&2 zrr&ek#Hg%(?ip7dk)m5^?fI^M^6uUzn`d6OT&DJR{`02#_Xl=xIxuiBFe)&xG%yG- zFgY;vIBq}te(~%{Q>LAs%B9>}*sHelPW~%4gO{I!B~Cf|wp!N7FF&jRQw`C2famTv zRe%1fJ6v%-_r6bRKm4T7R?{|Tp_%HnUJr_vc`%l~CHn9?4c z+PBhb^XpTLJ0!pOKbuix%)y9o1khN6$=!<0k%x~?_!h(SQTTy90~3lvCq9&%_<>#g a-xFyK=KR>F3;&&G00K`}KbLh*2~7a&Jdp|j literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSH#}V&Ln>~)y?vUq!9c*_VDH|W ztw)zg?B1}W;=95Ml@n>t^#0A$XzK+k1p$SHpK?E6+a;f<`**!l?!?D7^X{KNYHVQ6 z$icv$R!CmBpMahm>|4IF~og;d|v5lX|O4^V~FJ zPM~oh5S?-1-Q&>EEj8a}$oRin|9_5JIseJD^}8iKjq-Q%|G$30_(DFD00WZ)0|x`6 z0s~6}g8)PAg?BtP+g6=A9=iX-RIYR#HD*Q=L;X81mxpe$Sh>IM%e*=B)vQmyOZ$`4 zJlzqYjR|N*gYLC=9AC1c%0&Lu%v^K)>vj1Wm2~lwAC>|w+;sE1-hzo2qazs}mll-- z*Z;kqSX4iI&$}{nWzTifnKF)@Rrt?{=9K#R-HM&vioH7}9;q`hLfr?l2*e=(UbN;v WO>~O=P;b->Qt9dH=d#Wzp$PyHz=C4{ literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk33x*Ln>~)y?v0k*+9Vc;QK3+ zf}F%foDEAHy*%}ov~BgjVVr(J>5G6+hA>b$2&i2-=C}X-rH8-GEAzJsy63vw@45K< zLyZ3U1GiWd7+4w@1Q?hc7&sUh6&S({zVX)Vf2aBG&`wjvFBU=E56V@aFSl4+(P?XZ z=?{++L=_86OG9KNkEc%C15ua&$PA$DjG0F#97*yp?^cXs1e*&KgIU6W9~=<+mv>Al T{=l1Zc923(S3j3^P6XFH=*T7_PrQ0VMHr&O{L z3(#7CgvcapLGh;HoCVzCmIrsoNfe;Pmpzz?ZX zKgykijbF)Zne=aE_S$K(-8%{EG=No8E`WBLoOa25P%fy;L;#%zuuj1Mn&fayuER2W y^)tDS1mF*mfEfk^z_*-)0g<62k+29Cf9nI8tRqZ1IJ$lS0000R$00fcG8IkfQk^ux4fSVUEp9W9; So4#rQ0000%;9z zj*9FauS>W1ywrRX9I@oQrjG7ct8JDI0-Q~oZY-OvnfBOwI2afe7+6p^2kebgJ_hFtefu?g zjoHaByU%@Jmw)o1w&ngUmHRF}FMMI$(4V@OA?knP*7N38XRY}YW_K}~*gaZPyYIND zRI3u2X$Ye)@J-$jbl3k-kJITAd#_B%KK_YuqKNbkj-Lhp<7%2+cf0L7*$nhXhm$?1gJros07k`~WQFTR_GlJ3pV znrZnowD~5dS&EwmPIbAgDVj39`(P?v`i0(~0mvt&olnKsu= zZ!b*Kv7h-ef76@KCN~-ufJhq@Hyr^u?B2)`Q}o=ldTe_6)l}XKo&DExV(Yl)?P;vl ztiLz4yyru(dhA2tMxOfpA;nF{>@SDS=uv<9zh4MW$Nu)Px$bWlkN?AYLC-r-Mox{O z0PK|f0V7WZu#_Ke<9du3?=>%a`DWxJ-(@|@?_UQnu?fdQDuv5Y+#ZA{$U_wrn)&Wk30F3#A zpRnfN`2!`S&#rQEnv+wlb+qdPu(i^8N3ip#QON$dDiq{YRW#hx!np;%AfjZB;`Nj5 zLS8dBG)t1z0|Id6UnfJKN{W&lN_O2P02OL95N^l)E8^B@YkursCC31&1@002ovPDHLkV1iXRYPgfzl%Ze)l(ejY&RGJar9ldv6(Pp8 z7eECPD+ry}Ku_h$Qp&hOB5?tJDaKMv;ly^Vh_SysSZq2d$dRmVqoeWc@n<@vgTaUN@-rNPKGCi-U(n}{F zbD14EHgow0NqJqe0Mj!_m3FG^%d}Yq=&oS-zo(N?s@&x~?kT)~qurzoS6YC2?q%Bq zRZequJKyr#Cx43+;8?iLD8R#;#jWP*@8>%1A~s<8M>Cgr__TZhu5uyVW)$FNu7xgk z^*^VITgMGp{)fr3%8E;HBiv>b;PXPupPs{~KfT`bf_LvaTdC4tx&X7k7d!~J8CU=D z+{tkR)Roc1A zU+!B|kSg7DGED1nfdY(M!doX_vYV9C$?>nh z6<|;wCu2>GkCJkel;;@*Xv=6LyzJFS#7lo_`J?(MIUA&G)qffp-`9U4VS{~lV4C!>4V;`4? z+3wh)rF&)R(kr=|ccT_BZN0Rx`SsP{=}9IMRv8~7oOfru4BFMM!(=VVG^=I%3B_Qx z=1#?#Ovh4uWC|v*TYCpoeEqfN@=F83O{zD~e`i*z_e+=Ff8YAPzq&u?HI4%^j5CxO zidho+7|sYbSTY&7Gdv?+P|c>H*{SA0K*W!4RW$;QGdCa2pKsT3;Mn)yH;=w|yKuAZ z^=p06En$)2FAaA8czf=DYwknK9R`y>pXK5HJuhq4J*gk`Y(&kzB;@utUr1EU<%6woqZwi{!~}Frwi76(~A7_6(9$0WS^m&-HiD*?-`!!>30bzt@%9nXwt&%8%G_OW$ry=DQVl zjGt+mzN+1&Rr|5~%Zu6fYNv0%veWNGU&r0r=b?K}mKZF!$T~sp>!swlh!gMT$j|@% zaqT^hNk_xeT;ANDE?DTWm{Y;}x%Sox!KyE>XZ;VY6FSKM`_*iLbN8onuo*nu-duRo zt(c9&J;_4p@I=YJfVt;>mj9o+E&cIJ8<~p_Px!qtU3#1Q<8cQI;TB`3M}KO~OV)_~ zRgaUd;9k6&&BMJQXy$vJ2k+bqqklxsi#w6>QTQd$^7HqnADv&((>Nh&k3frCTO(kNjpNBQ+AAZfUFXoi8vu|NlQtTlAfZ Ss`x`7hr!d;&t;ucLK6TGWzwJk delta 348 zcmZqT_{KCrxt`J1)5S5Q;?~<+hI~y90<0H&Z5HhPD03~Y_139d(l-m4coZ(Kv7RNt zeX~~ord!PHpC@=G2r(dmKle(F^`&(GT{!E_5vBXDAl~Uh!N=AGlI>eOkB4ae+jA`W zarufJKaQLDyx#ibv{2Kl?)mKs|6@3`HrYQeb}|@?+c#(bDy4jyX*<) z2s3@HW>7p>1&-2elI`G^bR!T=`%s%XBo~f~D(z8Hb2Gk{@v~8|(|1eKY=YUdX%j P6{N<~)z4*}Q$iB}fdY>V diff --git a/gpu/internal/vulkan/vulkan.go b/gpu/internal/vulkan/vulkan.go index 4d313553..9ae0bc69 100644 --- a/gpu/internal/vulkan/vulkan.go +++ b/gpu/internal/vulkan/vulkan.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "image" + "math/bits" "gioui.org/gpu/internal/driver" "gioui.org/internal/vk" @@ -75,6 +76,7 @@ type Texture struct { sampler vk.Sampler fbo vk.Framebuffer format vk.Format + mipmaps int layout vk.ImageLayout passLayout vk.ImageLayout width int @@ -155,7 +157,7 @@ func newVulkanDevice(api driver.Vulkan) (driver.Device, error) { if props&reqs == reqs { b.caps |= driver.FeatureFloatRenderTargets } - reqs = vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT + reqs = vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT props = vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R8G8B8A8_SRGB) if props&reqs == reqs { b.caps |= driver.FeatureSRGB @@ -292,19 +294,31 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min usage |= vk.IMAGE_USAGE_STORAGE_BIT } filterFor := func(f driver.TextureFilter) vk.Filter { - switch minFilter { - case driver.FilterLinear: + switch f { + case driver.FilterLinear, driver.FilterLinearMipmapLinear: return vk.FILTER_LINEAR case driver.FilterNearest: return vk.FILTER_NEAREST } panic("unknown filter") } - sampler, err := vk.CreateSampler(b.dev, filterFor(minFilter), filterFor(magFilter)) + mipmapMode := vk.SAMPLER_MIPMAP_MODE_NEAREST + mipmap := minFilter == driver.FilterLinearMipmapLinear + nmipmaps := 1 + if mipmap { + mipmapMode = vk.SAMPLER_MIPMAP_MODE_LINEAR + dim := width + if height > dim { + dim = height + } + log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1 + nmipmaps = log2 + 1 + } + sampler, err := vk.CreateSampler(b.dev, filterFor(minFilter), filterFor(magFilter), mipmapMode) if err != nil { return nil, mapErr(err) } - img, mem, err := vk.CreateImage(b.physDev, b.dev, vkfmt, width, height, usage) + img, mem, err := vk.CreateImage(b.physDev, b.dev, vkfmt, width, height, nmipmaps, usage) if err != nil { vk.DestroySampler(b.dev, sampler) return nil, mapErr(err) @@ -316,7 +330,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min vk.FreeMemory(b.dev, mem) return nil, mapErr(err) } - t := &Texture{backend: b, img: img, mem: mem, view: view, sampler: sampler, layout: vk.IMAGE_LAYOUT_UNDEFINED, passLayout: passLayout, width: width, height: height, format: vkfmt} + t := &Texture{backend: b, img: img, mem: mem, view: view, sampler: sampler, layout: vk.IMAGE_LAYOUT_UNDEFINED, passLayout: passLayout, width: width, height: height, format: vkfmt, mipmaps: nmipmaps} if bindings&driver.BufferBindingFramebuffer != 0 { pass, err := vk.CreateRenderPass(b.dev, vkfmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE, vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil) @@ -646,6 +660,40 @@ func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { vk.ACCESS_TRANSFER_WRITE_BIT, ) vk.CmdCopyBufferToImage(cmdBuf, stage.buf, t.img, t.layout, op) + // Build mipmaps by repeating linear blits. + w, h := t.width, t.height + for i := 1; i < t.mipmaps; i++ { + nw, nh := w/2, h/2 + if nh < 1 { + nh = 1 + } + if nw < 1 { + nw = 1 + } + // Transition previous (source) level. + b := vk.BuildImageMemoryBarrier( + t.img, + vk.ACCESS_TRANSFER_WRITE_BIT, vk.ACCESS_TRANSFER_READ_BIT, + vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + i-1, 1, + ) + vk.CmdPipelineBarrier(cmdBuf, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b}) + // Blit to this mipmap level. + blit := vk.BuildImageBlit(0, 0, 0, 0, w, h, nw, nh, i-1, i) + vk.CmdBlitImage(cmdBuf, t.img, vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, t.img, vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, []vk.ImageBlit{blit}, vk.FILTER_LINEAR) + w, h = nw, nh + } + if t.mipmaps > 1 { + // Add barrier for last blit. + b := vk.BuildImageMemoryBarrier( + t.img, + vk.ACCESS_TRANSFER_WRITE_BIT, vk.ACCESS_TRANSFER_READ_BIT, + vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + t.mipmaps-1, 1, + ) + vk.CmdPipelineBarrier(cmdBuf, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b}) + t.layout = vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL + } } func (t *Texture) Release() { @@ -756,6 +804,7 @@ func (t *Texture) imageBarrier(cmdBuf vk.CommandBuffer, layout vk.ImageLayout, s t.img, t.scope.access, access, t.layout, layout, + 0, vk.REMAINING_MIP_LEVELS, ) vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b}) t.layout = layout diff --git a/internal/d3d11/d3d11_windows.go b/internal/d3d11/d3d11_windows.go index 450fde1a..d7e2073c 100644 --- a/internal/d3d11/d3d11_windows.go +++ b/internal/d3d11/d3d11_windows.go @@ -685,6 +685,7 @@ const ( PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5 FILTER_MIN_MAG_LINEAR_MIP_POINT = 0x14 + FILTER_MIN_MAG_MIP_LINEAR = 0x15 FILTER_MIN_MAG_MIP_POINT = 0 TEXTURE_ADDRESS_MIRROR = 2 @@ -701,6 +702,7 @@ const ( BUFFEREX_SRV_FLAG_RAW = 0x1 RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS = 0x20 + RESOURCE_MISC_GENERATE_MIPS = 0x1 CREATE_DEVICE_DEBUG = 0x2 @@ -1190,6 +1192,16 @@ func (s *IDXGISwapChain) GetBuffer(index int, riid *GUID) (*IUnknown, error) { return buf, nil } +func (c *DeviceContext) GenerateMips(res *ShaderResourceView) { + syscall.Syscall( + c.Vtbl.GenerateMips, + 2, + uintptr(unsafe.Pointer(c)), + uintptr(unsafe.Pointer(res)), + 0, + ) +} + func (c *DeviceContext) Unmap(resource *Resource, subResource uint32) { syscall.Syscall( c.Vtbl.Unmap, diff --git a/internal/gl/gl.go b/internal/gl/gl.go index a9e378a1..c1b487ee 100644 --- a/internal/gl/gl.go +++ b/internal/gl/gl.go @@ -56,6 +56,7 @@ const ( GREATER = 0x204 GEQUAL = 0x206 LINEAR = 0x2601 + LINEAR_MIPMAP_LINEAR = 0x2703 LINK_STATUS = 0x8b82 LUMINANCE = 0x1909 MAP_READ_BIT = 0x0001 diff --git a/internal/gl/gl_js.go b/internal/gl/gl_js.go index 17a641e7..a85daf90 100644 --- a/internal/gl/gl_js.go +++ b/internal/gl/gl_js.go @@ -72,6 +72,7 @@ type Functions struct { _flush js.Value _framebufferRenderbuffer js.Value _framebufferTexture2D js.Value + _generateMipmap js.Value _getRenderbufferParameteri js.Value _getFramebufferAttachmentParameter js.Value _getParameter js.Value @@ -167,6 +168,7 @@ func NewFunctions(ctx Context, forceES bool) (*Functions, error) { _flush: _bind(webgl, `flush`), _framebufferRenderbuffer: _bind(webgl, `framebufferRenderbuffer`), _framebufferTexture2D: _bind(webgl, `framebufferTexture2D`), + _generateMipmap: _bind(webgl, `generateMipmap`), _getRenderbufferParameteri: _bind(webgl, `getRenderbufferParameteri`), _getFramebufferAttachmentParameter: _bind(webgl, `getFramebufferAttachmentParameter`), _getParameter: _bind(webgl, `getParameter`), @@ -419,6 +421,9 @@ func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarg func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) { f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level) } +func (f *Functions) GenerateMipmap(target Enum) { + f._generateMipmap.Invoke(int(target)) +} func (f *Functions) GetError() Enum { // Avoid slow getError calls. See gio#179. return 0 diff --git a/internal/gl/gl_unix.go b/internal/gl/gl_unix.go index a746ee92..def00859 100644 --- a/internal/gl/gl_unix.go +++ b/internal/gl/gl_unix.go @@ -72,6 +72,7 @@ typedef void (*_glFlush)(void); typedef void (*_glFramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); typedef void (*_glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (*_glGenBuffers)(GLsizei n, GLuint *buffers); +typedef void (*_glGenerateMipmap)(GLenum target); typedef void (*_glGenFramebuffers)(GLsizei n, GLuint *framebuffers); typedef void (*_glGenRenderbuffers)(GLsizei n, GLuint *renderbuffers); typedef void (*_glGenTextures)(GLsizei n, GLuint *textures); @@ -286,6 +287,10 @@ static void glGenBuffers(_glGenBuffers f, GLsizei n, GLuint *buffers) { f(n, buffers); } +static void glGenerateMipmap(_glGenerateMipmap f, GLenum target) { + f(target); +} + static void glGenFramebuffers(_glGenFramebuffers f, GLsizei n, GLuint *framebuffers) { f(n, framebuffers); } @@ -561,6 +566,7 @@ type Functions struct { glFramebufferRenderbuffer C._glFramebufferRenderbuffer glFramebufferTexture2D C._glFramebufferTexture2D glGenBuffers C._glGenBuffers + glGenerateMipmap C._glGenerateMipmap glGenFramebuffers C._glGenFramebuffers glGenRenderbuffers C._glGenRenderbuffers glGenTextures C._glGenTextures @@ -723,6 +729,7 @@ func (f *Functions) load(forceES bool) error { f.glFramebufferRenderbuffer = must("glFramebufferRenderbuffer") f.glFramebufferTexture2D = must("glFramebufferTexture2D") f.glGenBuffers = must("glGenBuffers") + f.glGenerateMipmap = must("glGenerateMipmap") f.glGenFramebuffers = must("glGenFramebuffers") f.glGenRenderbuffers = must("glGenRenderbuffers") f.glGenTextures = must("glGenTextures") @@ -1048,6 +1055,10 @@ func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t T C.glFramebufferTexture2D(f.glFramebufferTexture2D, C.GLenum(target), C.GLenum(attachment), C.GLenum(texTarget), C.GLuint(t.V), C.GLint(level)) } +func (f *Functions) GenerateMipmap(target Enum) { + C.glGenerateMipmap(f.glGenerateMipmap, C.GLenum(target)) +} + func (c *Functions) GetBinding(pname Enum) Object { return Object{uint(c.GetInteger(pname))} } diff --git a/internal/gl/gl_windows.go b/internal/gl/gl_windows.go index 99b16942..edf688df 100644 --- a/internal/gl/gl_windows.go +++ b/internal/gl/gl_windows.go @@ -35,6 +35,7 @@ var ( _glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays") _glCompileShader = LibGLESv2.NewProc("glCompileShader") _glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D") + _glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap") _glGenBuffers = LibGLESv2.NewProc("glGenBuffers") _glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers") _glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays") @@ -192,6 +193,9 @@ func (c *Functions) CompileShader(s Shader) { func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) { syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0) } +func (f *Functions) GenerateMipmap(target Enum) { + syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0) +} func (c *Functions) CreateBuffer() Buffer { var buf uintptr syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0) diff --git a/internal/vk/vulkan.go b/internal/vk/vulkan.go index b380e2ba..9d63c84a 100644 --- a/internal/vk/vulkan.go +++ b/internal/vk/vulkan.go @@ -303,6 +303,10 @@ static VkResult vkResetDescriptorPool(PFN_vkResetDescriptorPool f, VkDevice devi return f(device, descriptorPool, flags); } +static void vkCmdBlitImage(PFN_vkCmdBlitImage f, VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageBlit* pRegions, VkFilter filter) { + f(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter); +} + static void vkCmdCopyImage(PFN_vkCmdCopyImage f, VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy *pRegions) { f(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions); } @@ -415,6 +419,7 @@ type ( Queue = C.VkQueue IndexType = C.VkIndexType Image = C.VkImage + ImageBlit = C.VkImageBlit ImageCopy = C.VkImageCopy ImageLayout = C.VkImageLayout ImageMemoryBarrier = C.VkImageMemoryBarrier @@ -482,9 +487,10 @@ const ( FORMAT_R32G32B32_SFLOAT Format = C.VK_FORMAT_R32G32B32_SFLOAT FORMAT_R32G32B32A32_SFLOAT Format = C.VK_FORMAT_R32G32B32A32_SFLOAT - FORMAT_FEATURE_COLOR_ATTACHMENT_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT - FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT - FORMAT_FEATURE_SAMPLED_IMAGE_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + FORMAT_FEATURE_COLOR_ATTACHMENT_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT + FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT + FORMAT_FEATURE_SAMPLED_IMAGE_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT IMAGE_USAGE_SAMPLED_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_SAMPLED_BIT IMAGE_USAGE_COLOR_ATTACHMENT_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT @@ -574,6 +580,11 @@ const ( QUEUE_GRAPHICS_BIT QueueFlags = C.VK_QUEUE_GRAPHICS_BIT QUEUE_COMPUTE_BIT QueueFlags = C.VK_QUEUE_COMPUTE_BIT + + SAMPLER_MIPMAP_MODE_NEAREST SamplerMipmapMode = C.VK_SAMPLER_MIPMAP_MODE_NEAREST + SAMPLER_MIPMAP_MODE_LINEAR SamplerMipmapMode = C.VK_SAMPLER_MIPMAP_MODE_LINEAR + + REMAINING_MIP_LEVELS = -1 ) var ( @@ -654,6 +665,7 @@ var funcs struct { vkFreeDescriptorSets C.PFN_vkFreeDescriptorSets vkUpdateDescriptorSets C.PFN_vkUpdateDescriptorSets vkResetDescriptorPool C.PFN_vkResetDescriptorPool + vkCmdBlitImage C.PFN_vkCmdBlitImage vkCmdCopyImage C.PFN_vkCmdCopyImage vkCreateComputePipelines C.PFN_vkCreateComputePipelines vkCreateFence C.PFN_vkCreateFence @@ -794,6 +806,7 @@ func vkInit() error { funcs.vkFreeDescriptorSets = must("vkFreeDescriptorSets") funcs.vkUpdateDescriptorSets = must("vkUpdateDescriptorSets") funcs.vkResetDescriptorPool = must("vkResetDescriptorPool") + funcs.vkCmdBlitImage = must("vkCmdBlitImage") funcs.vkCmdCopyImage = must("vkCmdCopyImage") funcs.vkCreateComputePipelines = must("vkCreateComputePipelines") funcs.vkCreateFence = must("vkCreateFence") @@ -1376,6 +1389,13 @@ func CmdBindDescriptorSets(cmdBuf CommandBuffer, point PipelineBindPoint, layout C.vkCmdBindDescriptorSets(funcs.vkCmdBindDescriptorSets, cmdBuf, point, layout, C.uint32_t(firstSet), C.uint32_t(len(sets)), &sets[0], 0, nil) } +func CmdBlitImage(cmdBuf CommandBuffer, src Image, srcLayout ImageLayout, dst Image, dstLayout ImageLayout, regions []ImageBlit, filter Filter) { + if len(regions) == 0 { + return + } + C.vkCmdBlitImage(funcs.vkCmdBlitImage, cmdBuf, src, srcLayout, dst, dstLayout, C.uint32_t(len(regions)), ®ions[0], filter) +} + func CmdCopyImage(cmdBuf CommandBuffer, src Image, srcLayout ImageLayout, dst Image, dstLayout ImageLayout, regions []ImageCopy) { if len(regions) == 0 { return @@ -1394,7 +1414,7 @@ func CmdDispatch(cmdBuf CommandBuffer, x, y, z int) { C.vkCmdDispatch(funcs.vkCmdDispatch, cmdBuf, C.uint32_t(x), C.uint32_t(y), C.uint32_t(z)) } -func CreateImage(pd PhysicalDevice, d Device, format Format, width, height int, usage ImageUsageFlags) (Image, DeviceMemory, error) { +func CreateImage(pd PhysicalDevice, d Device, format Format, width, height, mipmaps int, usage ImageUsageFlags) (Image, DeviceMemory, error) { inf := C.VkImageCreateInfo{ sType: C.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, imageType: C.VK_IMAGE_TYPE_2D, @@ -1404,7 +1424,7 @@ func CreateImage(pd PhysicalDevice, d Device, format Format, width, height int, height: C.uint32_t(height), depth: 1, }, - mipLevels: 1, + mipLevels: C.uint32_t(mipmaps), arrayLayers: 1, samples: C.VK_SAMPLE_COUNT_1_BIT, tiling: C.VK_IMAGE_TILING_OPTIMAL, @@ -1451,11 +1471,13 @@ func FreeMemory(d Device, mem DeviceMemory) { C.vkFreeMemory(funcs.vkFreeMemory, d, mem, nil) } -func CreateSampler(d Device, minFilter, magFilter Filter) (Sampler, error) { +func CreateSampler(d Device, minFilter, magFilter Filter, mipmapMode SamplerMipmapMode) (Sampler, error) { inf := C.VkSamplerCreateInfo{ sType: C.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, minFilter: minFilter, magFilter: magFilter, + mipmapMode: mipmapMode, + maxLod: C.VK_LOD_CLAMP_NONE, addressModeU: C.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, addressModeV: C.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, } @@ -1905,7 +1927,7 @@ func BuildViewport(x, y, width, height float32) Viewport { } } -func BuildImageMemoryBarrier(img Image, srcMask, dstMask AccessFlags, oldLayout, newLayout ImageLayout) ImageMemoryBarrier { +func BuildImageMemoryBarrier(img Image, srcMask, dstMask AccessFlags, oldLayout, newLayout ImageLayout, baseMip, numMips int) ImageMemoryBarrier { return C.VkImageMemoryBarrier{ sType: C.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, srcAccessMask: srcMask, @@ -1914,9 +1936,10 @@ func BuildImageMemoryBarrier(img Image, srcMask, dstMask AccessFlags, oldLayout, newLayout: newLayout, image: img, subresourceRange: C.VkImageSubresourceRange{ - aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, - levelCount: C.VK_REMAINING_MIP_LEVELS, - layerCount: C.VK_REMAINING_ARRAY_LAYERS, + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + baseMipLevel: C.uint32_t(baseMip), + levelCount: C.uint32_t(numMips), + layerCount: C.VK_REMAINING_ARRAY_LAYERS, }, } } @@ -1982,6 +2005,29 @@ func BuildImageCopy(srcX, srcY, dstX, dstY, width, height int) ImageCopy { } } +func BuildImageBlit(srcX, srcY, dstX, dstY, srcWidth, srcHeight, dstWidth, dstHeight, srcMip, dstMip int) ImageBlit { + return C.VkImageBlit{ + srcOffsets: [2]C.VkOffset3D{ + {C.int32_t(srcX), C.int32_t(srcY), 0}, + {C.int32_t(srcWidth), C.int32_t(srcHeight), 1}, + }, + srcSubresource: C.VkImageSubresourceLayers{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + layerCount: 1, + mipLevel: C.uint32_t(srcMip), + }, + dstOffsets: [2]C.VkOffset3D{ + {C.int32_t(dstX), C.int32_t(dstY), 0}, + {C.int32_t(dstWidth), C.int32_t(dstHeight), 1}, + }, + dstSubresource: C.VkImageSubresourceLayers{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + layerCount: 1, + mipLevel: C.uint32_t(dstMip), + }, + } +} + func findMemoryTypeIndex(pd C.VkPhysicalDevice, constraints C.uint32_t, wantProps C.VkMemoryPropertyFlags) (int, bool) { var memProps C.VkPhysicalDeviceMemoryProperties C.vkGetPhysicalDeviceMemoryProperties(funcs.vkGetPhysicalDeviceMemoryProperties, pd, &memProps)