blob: 9b5494f8a0ef1a810bf000b4a1d82b7271977405 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gles
import (
"fmt"
"reflect"
"strings"
"android.googlesource.com/platform/tools/gpu/framework/interval"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/gapid/atom"
"android.googlesource.com/platform/tools/gpu/gapid/database"
"android.googlesource.com/platform/tools/gpu/gapid/gfxapi"
"android.googlesource.com/platform/tools/gpu/gapid/gfxapi/gles/glsl/ast"
"android.googlesource.com/platform/tools/gpu/gapid/memory"
"android.googlesource.com/platform/tools/gpu/gapid/replay"
"android.googlesource.com/platform/tools/gpu/gapid/replay/builder"
"android.googlesource.com/platform/tools/gpu/gapid/service"
)
type support int
const (
unsupported support = iota
supported
required
)
var (
// We don't include tests directly in the gles package as it adds
// signaficantly to the test build time.
VisibleForTestingCompat = compat
VisibleForTestingGetContext = getContext
VisibleForTestingGlSlCompat = glslCompat
)
// If the default vertex array object (id 0) is not allowed on
// the target platform, we remap the uses to this array.
const DefaultVertexArrayId = VertexArrayId(0xFFFF0001)
type extensions map[string]struct{}
func parseExtensions(list string) extensions {
out := extensions{}
for _, s := range strings.Split(list, " ") {
out[s] = struct{}{}
}
return out
}
func (e extensions) get(name string) support {
if _, ok := e[name]; ok {
return supported
}
return unsupported
}
func (s support) String() string {
switch s {
case unsupported:
return "unsupported"
case supported:
return "supported"
case required:
return "required"
default:
return fmt.Sprintf("support<%d>", s)
}
}
type features struct {
vertexHalfFloatOES support // support for GL_OES_vertex_half_float
textureHalfFloatOES support // support for GL_OES_texture_half_float
eglImageExternal support // support for GL_OES_EGL_image_external
vertexArrayObjects support // support for VBOs
supportGenerateMipmapHint bool // support for GL_GENERATE_MIPMAP_HINT
uncompressedTextureFormats map[GLenum]struct{}
compressedTextureFormats map[GLenum]struct{}
glTexImage3D bool // support for glTexImage3D and friends
}
func getFeatures(ctx log.Context, version string, extensions string) (features, error) {
v, err := ParseVersion(version)
if err != nil {
return features{}, err
}
ext := parseExtensions(extensions)
utfs, err := getSupportedUncompressedTextureFormats(*v, ext)
if err != nil {
ctx.Warning().Fail(err, "getSupportedUncompressedTextureFormats")
}
f := features{
vertexHalfFloatOES: ext.get("GL_OES_vertex_half_float"),
textureHalfFloatOES: ext.get("GL_OES_texture_half_float"),
eglImageExternal: ext.get("GL_OES_EGL_image_external"),
uncompressedTextureFormats: utfs,
compressedTextureFormats: getSupportedCompressedTextureFormats(ext),
supportGenerateMipmapHint: v.IsES,
glTexImage3D: !v.IsES,
}
// TODO: Properly check the specifications for these flags.
switch {
case v.IsES && v.Major >= 3:
f.vertexArrayObjects = supported
case !v.IsES && v.Major >= 3:
f.vertexArrayObjects = required
}
return f, nil
}
func compat(ctx log.Context, device *service.Device, d database.Database) (atom.Transformer, error) {
ctx = ctx.Enter("compat")
target, err := getFeatures(ctx, device.Version, device.Extensions)
if err != nil {
return nil, fmt.Errorf(
"Error '%v' when getting feature list for version: '%s', extensions: '%s'.",
err, device.Version, device.Extensions)
}
contexts := map[*Context]features{}
s := gfxapi.NewState()
var t atom.Transformer
t = atom.Transform("compat", func(ctx log.Context, i atom.ID, a atom.Atom, out atom.Writer) {
c := getContext(s)
if c == nil {
// The compatibility translations below assume that we have a valid context.
mutateAndWrite(ctx, i, a, s, d, out)
return
}
switch a := a.(type) {
case *ContextInfo:
if _, found := contexts[c]; found {
break
}
// Mutate to set the Version and Extensions strings.
mutateAndWrite(ctx, i, a, s, d, out)
source, err := getFeatures(ctx, c.Constants.Version, c.Constants.Extensions)
if err != nil {
ctx.Error().V("version", c.Constants.Version).V("extensions", c.Constants.Extensions).Fail(err,
"Getting feature list for {version?$: '$s'}, {extensions?$: '%s'}.")
break
}
contexts[c] = source
if target.vertexArrayObjects == required &&
source.vertexArrayObjects != required {
// Replay device requires VAO, but capture did not enforce it.
// Satisfy the target by creating and binding a single VAO
// which we will use instead of the default VAO (id 0).
out.Write(ctx, atom.NoID, NewGlGenVertexArrays(1, memory.Tmp).
AddWrite(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp, VertexArrayId(DefaultVertexArrayId))))
out.Write(ctx, atom.NoID, NewGlBindVertexArray(DefaultVertexArrayId))
}
return
case *GlBindTexture:
if !c.Instances.Textures.Contains(a.Texture) {
// glGenTextures() was not used to generate the texture. Legal in GLES 2.
out.Write(ctx, atom.NoID, NewGlGenTextures(1, memory.Tmp).
AddRead(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp, VertexArrayId(a.Texture))))
}
if a.Target == GLenum_GL_TEXTURE_EXTERNAL_OES && target.eglImageExternal == unsupported {
// TODO: Implement full support for external images.
// Remap external textures to plain 2D textures - this matches GLSL compat.
out.Write(ctx, atom.NoID, NewGlBindTexture(GLenum_GL_TEXTURE_2D, a.Texture))
return
}
case *GlBindVertexArray:
if a.Array == VertexArrayId(0) {
if target.vertexArrayObjects == required &&
contexts[c].vertexArrayObjects != required {
a.Mutate(ctx, s, d, nil /* no builder, just mutate */)
out.Write(ctx, atom.NoID, NewGlBindVertexArray(DefaultVertexArrayId))
return
}
}
case *GlDisableVertexAttribArray:
vao := c.Instances.VertexArrays[c.BoundVertexArray]
if vao.VertexAttributeArrays[a.Location].Enabled == GLboolean_GL_FALSE {
// Ignore the call if it is redundant (i.e. it is already disabled).
// Some applications iterate over all arrays and explicitly disable them.
// This is a problem if the target supports fewer arrays than the capture.
return
}
case *GlVertexAttrib4fv:
if oldAttrib, ok := c.VertexAttributes[a.Location]; ok {
oldValue := oldAttrib.Value.Read(ctx, a, s, d, nil /* builder */)
a.Mutate(ctx, s, d, nil /* no builder, just mutate */)
newAttrib := c.VertexAttributes[a.Location]
newValue := newAttrib.Value.Read(ctx, a, s, d, nil /* builder */)
if reflect.DeepEqual(oldValue, newValue) {
// Ignore the call if it is redundant.
// Some applications iterate over all arrays and explicitly initialize them.
// This is a problem if the target supports fewer arrays than the capture.
return
}
}
out.Write(ctx, i, a)
return
case *GlGetVertexAttribIiv,
*GlGetVertexAttribIuiv,
*GlGetVertexAttribPointerv,
*GlGetVertexAttribfv,
*GlGetVertexAttribiv:
// Some applications iterate over all arrays and query their state.
// This may fail if the target supports fewer arrays than the capture.
// As these should have no side-effects, just drop them.
return
case *GlShaderSource:
// Apply the state mutation of the unmodified glShaderSource atom.
// This is so we can grab the source string from the Shader object.
a.Mutate(ctx, s, d, nil /* no builder, just mutate */)
shader := c.Instances.Shaders.Get(a.Shader)
lang := ast.LangVertexShader
switch shader.Type {
case GLenum_GL_VERTEX_SHADER:
case GLenum_GL_FRAGMENT_SHADER:
lang = ast.LangFragmentShader
default:
ctx.Warning().V("type", shader.Type).Log("Unknown shader type")
}
src, err := glslCompat(shader.Source, lang, device)
if err != nil {
ctx.Error().V("id", i).Fail(err, "Reformatting GLSL source for atom")
}
a = NewGlShaderSource(a.Shader, 1, memory.Tmp, memory.Nullptr).
AddRead(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp.Offset(8), src)).
AddRead(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp, memory.Tmp.Offset(8)))
mutateAndWrite(ctx, i, a, s, d, out)
return
// TODO: glVertexAttribIPointer
case *GlVertexAttribPointer:
if a.Type == GLenum_GL_HALF_FLOAT_OES && target.vertexHalfFloatOES == unsupported {
// Convert GL_HALF_FLOAT_OES to GL_HALF_FLOAT_ARB.
a = NewGlVertexAttribPointer(a.Location, a.Size, GLenum_GL_HALF_FLOAT_ARB, a.Normalized, a.Stride, memory.Pointer(a.Data))
}
if target.vertexArrayObjects == required &&
c.BoundBuffers[GLenum_GL_ARRAY_BUFFER] == 0 {
// Client-pointers are not supported, we need to copy this data to a buffer.
// However, we can't do this now as the observation only happens at the draw call.
// Apply the state changes, but don't write the emit the atom - we need to defer
// the trickery to the draw call.
a.Mutate(ctx, s, d, nil /* no builder, just mutate */)
return
}
mutateAndWrite(ctx, i, a, s, d, out)
return
case *GlDrawArrays:
if target.vertexArrayObjects == required {
if clientVAsBound(c) {
first := int(a.FirstIndex)
last := first + int(a.IndicesCount) - 1
defer moveClientVBsToVAs(ctx, first, last, i, a, s, c, d, out)()
}
}
case *GlDrawElements:
if target.vertexArrayObjects == required {
e := externs{ctx: ctx, a: a, s: s, d: d}
ib := c.BoundBuffers[GLenum_GL_ELEMENT_ARRAY_BUFFER]
clientIB := ib == 0
clientVB := clientVAsBound(c)
if clientIB {
// The indices for the glDrawElements call is in client memory.
// We need to move this into a temporary buffer.
// Generate a new element array buffer and bind it.
id := BufferId(newUnusedID(func(x uint32) bool { _, ok := c.Instances.Buffers[BufferId(x)]; return ok }))
c.Instances.Buffers[id] = &Buffer{} // Not used aside from reserving the ID.
out.Write(ctx, atom.NoID, NewGlGenBuffers(1, memory.Tmp).
AddRead(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp, id)))
out.Write(ctx, atom.NoID, NewGlBindBuffer(GLenum_GL_ELEMENT_ARRAY_BUFFER, id))
// By moving the draw call's observations earlier, populate the element array buffer.
size, base := DataTypeSize(a.IndicesType)*int(a.IndicesCount), memory.Pointer(a.Indices)
glBufferData := NewGlBufferData(GLenum_GL_ELEMENT_ARRAY_BUFFER, GLsizeiptr(size), memory.Pointer(base), GLenum_GL_STATIC_DRAW)
glBufferData.extras = a.extras
out.Write(ctx, atom.NoID, glBufferData)
// Clean-up
defer func() {
out.Write(ctx, atom.NoID, NewGlBindBuffer(GLenum_GL_ELEMENT_ARRAY_BUFFER, ib))
delete(c.Instances.Buffers, id)
}()
if clientVB {
// Some of the vertex arrays for the glDrawElements call is in
// client memory and we need to move this into temporary buffer(s).
// The indices are also in client memory, so we need to apply the
// atom's reads now so that the indices can be read from the
// application pool.
a.Extras().Observations().ApplyReads(s.Memory[memory.ApplicationPool])
limits := e.calcIndexLimits(U8ᵖ(a.Indices), a.IndicesType, 0, uint32(a.IndicesCount))
defer moveClientVBsToVAs(ctx, int(limits.Min), int(limits.Max), i, a, s, c, d, out)()
}
glDrawElements := *a
glDrawElements.Indices.Address = 0
glDrawElements.Mutate(ctx, s, d, nil /* no builder, just mutate */)
out.Write(ctx, i, &glDrawElements)
return
} else if clientVB { // GL_ELEMENT_ARRAY_BUFFER is bound
// Some of the vertex arrays for the glDrawElements call is in
// client memory and we need to move this into temporary buffer(s).
// The indices are server-side, so can just be read from the internal
// pooled buffer.
data := c.Instances.Buffers[ib].Data.Index(0, s)
base := uint32(a.Indices.Address)
limits := e.calcIndexLimits(data, a.IndicesType, base, uint32(a.IndicesCount))
defer moveClientVBsToVAs(ctx, int(limits.Min), int(limits.Max), i, a, s, c, d, out)()
}
}
case *GlTexStorage2DEXT:
if _, supported := target.uncompressedTextureFormats[a.Format]; !supported {
a := *a
a.Format = GLenum_GL_RGBA8
a.Mutate(ctx, s, d, nil /* no builder, just mutate */)
out.Write(ctx, i, &a)
return
}
case *GlTexImage2D:
{
a := *a
if a.Type == GLenum_GL_HALF_FLOAT_OES && target.textureHalfFloatOES == unsupported {
// Half-float made it to core desktop and ES specification, but it was renumbered.
a.Type = GLenum_GL_HALF_FLOAT
}
if _, supported := target.uncompressedTextureFormats[a.Format]; !supported {
if err := convertTexImage2DtoRGBA(ctx, i, &a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
}
case *GlTexSubImage2D:
{
a := *a
if a.Type == GLenum_GL_HALF_FLOAT_OES && target.textureHalfFloatOES == unsupported {
// Half-float made it to core desktop and ES specification, but it was renumbered.
a.Type = GLenum_GL_HALF_FLOAT
}
if _, supported := target.uncompressedTextureFormats[a.Format]; !supported {
if err := convertTexSubImage2DtoRGBA(ctx, i, &a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
}
case *GlCompressedTexImage2D:
if _, supported := target.compressedTextureFormats[a.Format]; !supported {
if err := decompressTexImage2D(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Decompressing texture")
}
case *GlCompressedTexSubImage2D:
if _, supported := target.compressedTextureFormats[a.Format]; !supported {
if err := decompressTexSubImage2D(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Decompressing texture")
}
case *GlTexImage3D:
if _, supported := target.uncompressedTextureFormats[a.Format]; !supported {
if err := convertTexImage3DtoRGBA(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
case *GlTexImage3DOES:
if _, supported := target.uncompressedTextureFormats[a.Format]; !supported {
if err := convertTexImage3DOEStoRGBA(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
if target.glTexImage3D {
if err := convertTexImage3DOES(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
case *GlTexSubImage3DOES:
if target.glTexImage3D {
if err := convertTexSubImage3DOES(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
case *GlCopyTexSubImage3DOES:
if target.glTexImage3D {
if err := convertCopyTexSubImage3DOES(ctx, i, a, s, d, out); err == nil {
return
}
ctx.Fail(err, "Converting texture")
}
case *GlProgramBinary:
if !canUsePrecompiledShader(c, device) {
for _, a := range buildStubProgram(ctx, a.Extras(), s, d, a.Program) {
mutateAndWrite(ctx, atom.NoID, a, s, d, atom.TransformWriter{T: t, O: out})
}
return
}
case *GlProgramBinaryOES:
if !canUsePrecompiledShader(c, device) {
for _, a := range buildStubProgram(ctx, a.Extras(), s, d, a.Program) {
mutateAndWrite(ctx, atom.NoID, a, s, d, atom.TransformWriter{T: t, O: out})
}
return
}
case *GlHint:
if a.Target == GLenum_GL_GENERATE_MIPMAP_HINT && !target.supportGenerateMipmapHint {
return // Not supported in the core profile of OpenGL.
}
case *GlGetBooleani_v,
*GlGetBooleanv,
*GlGetFloatv,
*GlGetInteger64i_v,
*GlGetInteger64v,
*GlGetIntegeri_v,
*GlGetIntegerv,
*GlGetInternalformativ,
*GlGetString,
*GlGetStringi:
// The acceptable values of these get functions vary between GL versions.
// As these should have no side-effects, just drop them.
return
case *GlGetActiveAttrib,
*GlGetActiveUniform:
// The number of active attributes and uniforms can vary between compilers
// depending on their ability to eliminate dead code. In particular,
// dead code in pixel shader can allow code removal in the vertex shader.
// As these should have no side-effects, just drop them.
return
case *GlLabelObjectEXT,
*GlGetObjectLabelEXT,
*GlObjectLabel,
*GlObjectLabelKHR,
*GlGetObjectLabel,
*GlObjectPtrLabel,
*GlGetObjectPtrLabel,
*GlGetObjectLabelKHR:
// These methods require non-trivial remapping for replay.
// As they do not affect rendering output, just drop them.
return
case *GlGetProgramBinary,
*GlGetProgramBinaryOES:
// Program binaries are very driver specific. This command may fail on replay
// because one of the arguments must be GL_PROGRAM_BINARY_LENGTH.
// It has no side effects, so just drop it.
return
case *GlDisable:
// GL_QCOM_alpha_test adds back GL_ALPHA_TEST from GLES 1.0 as extension.
// It seems that applications only disable it to make sure it is off, so
// we can safely ignore it. We should not ignore glEnable for it though.
if a.Capability == GLenum_GL_ALPHA_TEST_QCOM {
return
}
default:
if a.AtomFlags().IsDrawCall() && clientVAsBound(c) {
ctx.Warning().T("atom", a).Log("Draw call with client-pointers not handled by the compatability layer")
}
}
mutateAndWrite(ctx, i, a, s, d, out)
})
return t, nil
}
func mutateAndWrite(ctx log.Context, i atom.ID, a atom.Atom, s *gfxapi.State, d database.Database, out atom.Writer) {
a.Mutate(ctx, s, d, nil /* no builder, just mutate */)
out.Write(ctx, i, a)
}
// canUsePrecompiledShader returns true if precompiled shaders / programs
// captured with the context c can be replayed on the device d.
func canUsePrecompiledShader(c *Context, d *service.Device) bool {
return c.Constants.Vendor == d.Vendor && c.Constants.Version == d.Version
}
// clientVAsBound returns true if there are any vertex attribute arrays enabled
// with pointers to client-side memory.
func clientVAsBound(c *Context) bool {
// Only the default vertex array can use client-side memory.
if c.BoundVertexArray == 0 {
va := c.Instances.VertexArrays[c.BoundVertexArray]
for _, arr := range va.VertexAttributeArrays {
if arr.Enabled == GLboolean_GL_TRUE {
vb := va.VertexBufferBindings[arr.Binding]
if vb.Buffer == 0 && arr.Pointer.Address != 0 {
return true
}
}
}
}
return false
}
// moveClientVBsToVAs is a compatability helper for transforming client-side
// vertex array data (which is not supported by glVertexAttribPointer in later
// versions of GL), into array-buffers.
func moveClientVBsToVAs(
ctx log.Context,
first, last int, // vertex indices
i atom.ID,
a atom.Atom,
s *gfxapi.State,
c *Context,
d database.Database,
out atom.Writer) (revert func()) {
rngs := interval.U64RangeList{}
// Gather together all the client-buffers in use by the vertex-attribs.
// Merge together all the memory intervals that these use.
va := c.Instances.VertexArrays[c.BoundVertexArray]
for _, arr := range va.VertexAttributeArrays {
if arr.Enabled == GLboolean_GL_TRUE {
vb := va.VertexBufferBindings[arr.Binding]
if vb.Buffer == 0 && arr.Pointer.Address != 0 {
// TODO: We're currently ignoring the Offset and Stride fields of the VBB.
// TODO: We're currently ignoring the RelativeOffset field of the VA.
// TODO: Merge logic with ReadVertexArrays macro in vertex_arrays.api.
if vb.Divisor != 0 {
panic("Instanced draw calls not currently supported by the compatibility layer")
}
size := DataTypeSize(arr.Type) * int(arr.Size)
stride := int(vb.Stride)
base := memory.Pointer(arr.Pointer) // Always start from the 0'th vertex to simplify logic.
rng := base.Range(uint64(last*stride + size))
interval.Merge(&rngs, rng.Span(), true)
}
}
}
if len(rngs) == 0 {
// Draw call does not use client-side buffers. Just draw.
mutateAndWrite(ctx, i, a, s, d, out)
return
}
// Create an array-buffer for each chunk of overlapping client-side buffers in
// use. These are populated with data below.
ids := make([]BufferId, len(rngs))
for i := range rngs {
id := BufferId(newUnusedID(func(x uint32) bool { _, ok := c.Instances.Buffers[BufferId(x)]; return ok }))
c.Instances.Buffers[id] = &Buffer{} // Not used aside from reserving the ID.
ids[i] = id
}
out.Write(ctx, atom.NoID, NewGlGenBuffers(GLsizei(len(ids)), memory.Tmp).
AddRead(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp, ids)))
// Apply the memory observations that were made by the draw call now.
// We need to do this as the glBufferData calls below will require the data.
out.Write(ctx, atom.NoID, replay.Custom(func(ctx log.Context, s *gfxapi.State, d database.Database, b *builder.Builder) error {
a.Extras().Observations().ApplyReads(s.Memory[memory.ApplicationPool])
return nil
}))
// Note: be careful of overwriting the observations made above, before the
// calls to glBufferData below.
// Fill the array-buffers with the observed memory data.
for i, rng := range rngs {
base := memory.Pointer{Address: rng.First, Pool: memory.ApplicationPool}
size := GLsizeiptr(rng.Count)
out.Write(ctx, atom.NoID, NewGlBindBuffer(GLenum_GL_ARRAY_BUFFER, ids[i]))
out.Write(ctx, atom.NoID, NewGlBufferData(GLenum_GL_ARRAY_BUFFER, size, base, GLenum_GL_STATIC_DRAW))
}
// Redirect all the vertex attrib arrays to point to the array-buffer data.
for l, arr := range va.VertexAttributeArrays {
if arr.Enabled == GLboolean_GL_TRUE {
vb := va.VertexBufferBindings[arr.Binding]
if vb.Buffer == 0 && arr.Pointer.Address != 0 {
i := interval.IndexOf(&rngs, arr.Pointer.Address)
offset := arr.Pointer.Address - rngs[i].First
out.Write(ctx, atom.NoID, NewGlBindBuffer(GLenum_GL_ARRAY_BUFFER, ids[i]))
out.Write(ctx, atom.NoID, &GlVertexAttribPointer{
Location: l,
Size: GLint(arr.Size),
Type: arr.Type,
Normalized: arr.Normalized,
Stride: arr.Stride,
Data: NewVertexPointer(offset),
})
}
}
}
// Restore original state.
return func() {
out.Write(ctx, atom.NoID, NewGlBindBuffer(GLenum_GL_ARRAY_BUFFER, c.BoundBuffers[GLenum_GL_ARRAY_BUFFER]))
for _, id := range ids {
delete(c.Instances.Buffers, id)
out.Write(ctx, atom.NoID, NewGlDeleteBuffers(1, memory.Tmp).
AddRead(atom.Data(ctx, s.MemoryLayout, d, memory.Tmp, id)))
}
}
}