~eliasnaur/gio

bebc73db37b4d3ed56aee604d3388b2b1c5bafaf — Elias Naur 11 months ago e69ef4f
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 <mail@eliasnaur.com>
M gpu/gpu.go => gpu/gpu.go +1 -1
@@ 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)
	}

M gpu/internal/d3d11/d3d11_windows.go => gpu/internal/d3d11/d3d11_windows.go +26 -3
@@ 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() {

M gpu/internal/driver/driver.go => gpu/internal/driver/driver.go +1 -0
@@ 166,6 166,7 @@ const (
const (
	FilterNearest TextureFilter = iota
	FilterLinear
	FilterLinearMipmapLinear
)

const (

M gpu/internal/metal/metal_darwin.go => gpu/internal/metal/metal_darwin.go +31 -11
@@ 239,6 239,14 @@ static void blitEncCopyBufferToTexture(CFTypeRef blitEncRef, CFTypeRef bufRef, C
	}
}

static void blitEncGenerateMipmapsForTexture(CFTypeRef blitEncRef, CFTypeRef texRef) {
	@autoreleasepool {
		id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
		id<MTLTexture> tex = (__bridge id<MTLTexture>)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<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)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<MTLDevice> dev = (__bridge id<MTLDevice>)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<MTLDevice> dev = (__bridge id<MTLDevice>)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() {

M gpu/internal/opengl/opengl.go => gpu/internal/opengl/opengl.go +30 -7
@@ 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() {

M gpu/internal/rendertest/refs/TestComplicatedTransform.png => gpu/internal/rendertest/refs/TestComplicatedTransform.png +0 -0
M gpu/internal/rendertest/refs/TestOffsetScaleTexture.png => gpu/internal/rendertest/refs/TestOffsetScaleTexture.png +0 -0
M gpu/internal/rendertest/refs/TestOffsetTexture.png => gpu/internal/rendertest/refs/TestOffsetTexture.png +0 -0
M gpu/internal/rendertest/refs/TestPaintClippedTexture.png => gpu/internal/rendertest/refs/TestPaintClippedTexture.png +0 -0
M gpu/internal/rendertest/refs/TestPaintTexture.png => gpu/internal/rendertest/refs/TestPaintTexture.png +0 -0
M gpu/internal/rendertest/refs/TestRotateClipTexture.png => gpu/internal/rendertest/refs/TestRotateClipTexture.png +0 -0
M gpu/internal/rendertest/refs/TestRotateTexture.png => gpu/internal/rendertest/refs/TestRotateTexture.png +0 -0
M gpu/internal/vulkan/vulkan.go => gpu/internal/vulkan/vulkan.go +55 -6
@@ 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

M internal/d3d11/d3d11_windows.go => internal/d3d11/d3d11_windows.go +12 -0
@@ 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,

M internal/gl/gl.go => internal/gl/gl.go +1 -0
@@ 56,6 56,7 @@ const (
	GREATER                               = 0x204
	GEQUAL                                = 0x206
	LINEAR                                = 0x2601
	LINEAR_MIPMAP_LINEAR                  = 0x2703
	LINK_STATUS                           = 0x8b82
	LUMINANCE                             = 0x1909
	MAP_READ_BIT                          = 0x0001

M internal/gl/gl_js.go => internal/gl/gl_js.go +5 -0
@@ 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

M internal/gl/gl_unix.go => internal/gl/gl_unix.go +11 -0
@@ 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))}
}

M internal/gl/gl_windows.go => internal/gl/gl_windows.go +4 -0
@@ 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)

M internal/vk/vulkan.go => internal/vk/vulkan.go +56 -10
@@ 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)), &regions[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)