From d6e7b39c7bcef97e93ec5e2fd096fddfb4907724 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Wed, 14 Mar 2018 17:16:07 +0000 Subject: [PATCH 1/2] gles compat: Handle unaligned buffer modification when bound We have to jump through hoops to support replaying on targets where the minimum uniform alignment is smaller than the trace target. There was a TODO for handling the case where the buffer is kept bound while the buffer is updated. This CL fixes this case. Issue: #1563 --- gapis/api/gles/BUILD.bazel | 1 + gapis/api/gles/compat.go | 77 +++---------- gapis/api/gles/compat_buffers.go | 190 +++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 64 deletions(-) create mode 100644 gapis/api/gles/compat_buffers.go diff --git a/gapis/api/gles/BUILD.bazel b/gapis/api/gles/BUILD.bazel index 64e8b4a2a5..1aebdecfdd 100644 --- a/gapis/api/gles/BUILD.bazel +++ b/gapis/api/gles/BUILD.bazel @@ -47,6 +47,7 @@ go_library( srcs = [ "compat.go", "compat_client.go", + "compat_buffers.go", "context.go", "custom_replay.go", "datatypes.go", diff --git a/gapis/api/gles/compat.go b/gapis/api/gles/compat.go index 0dac404b07..4ce3be2e8d 100644 --- a/gapis/api/gles/compat.go +++ b/gapis/api/gles/compat.go @@ -135,11 +135,6 @@ func getFeatures(ctx context.Context, version string, ext extensions) (features, return f, v, nil } -type scratchBuffer struct { - size GLsizeiptr - id BufferId -} - type onCompatError func(context.Context, api.CmdID, api.Cmd, error) func compat(ctx context.Context, device *device.Instance, onError onCompatError) (transform.Transformer, error) { @@ -157,19 +152,9 @@ func compat(ctx context.Context, device *device.Instance, onError onCompatError) } contexts := map[*Context]features{} + bufferCompat := newBufferCompat(int(glDev.UniformBufferAlignment)) eglContextHandle := map[*Context]EGLContext{} - scratchBuffers := map[interface{}]scratchBuffer{} - nextBufferID := BufferId(0xffff0000) - newBuffer := func(i api.CmdID, cb CommandBuilder, out transform.Writer) BufferId { - s := out.State() - id := nextBufferID - tmp := s.AllocDataOrPanic(ctx, id) - out.MutateAndWrite(ctx, i.Derived(), cb.GlGenBuffers(1, tmp.Ptr()).AddWrite(tmp.Data())) - nextBufferID-- - return id - } - nextTextureID := TextureId(0xffff0000) newTexture := func(i api.CmdID, cb CommandBuilder, out transform.Writer) TextureId { s := out.State() @@ -359,57 +344,21 @@ func compat(ctx context.Context, device *device.Instance, onError onCompatError) } } - case *GlBindBufferRange: - misalignment := cmd.Offset % GLintptr(glDev.UniformBufferAlignment) - if cmd.Target == GLenum_GL_UNIFORM_BUFFER && misalignment != 0 { - // We have a glBindBufferRange() taking a uniform buffer with an - // illegal offset alignment. - // TODO: We don't handle the case where the buffer is kept bound - // while the buffer is updated. It's an unlikely issue, but - // something that may break us. - if !c.Objects.Buffers.Contains(cmd.Buffer) { - return // Don't know what buffer this is referring to. - } - - // We need a scratch buffer to copy the buffer data to a correct - // alignment. - key := struct { - c *Context - Target GLenum - Index GLuint - }{c, cmd.Target, cmd.Index} - - // Look for pre-existing buffer we can reuse. - buffer, ok := scratchBuffers[key] - if !ok { - buffer.id = newBuffer(dID, cb, out) - scratchBuffers[key] = buffer - } - - // Bind the scratch buffer to GL_COPY_WRITE_BUFFER - origCopyWriteBuffer := c.Bound.CopyWriteBuffer - out.MutateAndWrite(ctx, dID, cb.GlBindBuffer(GLenum_GL_COPY_WRITE_BUFFER, buffer.id)) - - if buffer.size < cmd.Size { - // Resize the scratch buffer - out.MutateAndWrite(ctx, dID, cb.GlBufferData(GLenum_GL_COPY_WRITE_BUFFER, cmd.Size, memory.Nullptr, GLenum_GL_DYNAMIC_COPY)) - buffer.size = cmd.Size - scratchBuffers[key] = buffer - } - - // Copy out the misaligned data to the scratch buffer in the - // GL_COPY_WRITE_BUFFER binding. - out.MutateAndWrite(ctx, dID, cb.GlBindBuffer(cmd.Target, cmd.Buffer)) - out.MutateAndWrite(ctx, dID, cb.GlCopyBufferSubData(cmd.Target, GLenum_GL_COPY_WRITE_BUFFER, cmd.Offset, 0, cmd.Size)) + case *GlBufferData: + bufferCompat.modifyBufferData(ctx, out, cb, c, id, cmd.Target, func() { out.MutateAndWrite(ctx, id, cmd) }) + return - // We can now bind the range with correct alignment. - out.MutateAndWrite(ctx, id, cb.GlBindBufferRange(cmd.Target, cmd.Index, buffer.id, 0, cmd.Size)) + case *GlBufferSubData: + bufferCompat.modifyBufferData(ctx, out, cb, c, id, cmd.Target, func() { out.MutateAndWrite(ctx, id, cmd) }) + return - // Restore old GL_COPY_WRITE_BUFFER binding. - out.MutateAndWrite(ctx, dID, cb.GlBindBuffer(GLenum_GL_COPY_WRITE_BUFFER, origCopyWriteBuffer.GetID())) + case *GlCopyBufferSubData: + bufferCompat.modifyBufferData(ctx, out, cb, c, id, cmd.WriteTarget, func() { out.MutateAndWrite(ctx, id, cmd) }) + return - return - } + case *GlBindBufferRange: + bufferCompat.bindBufferRange(ctx, out, cb, c, id, cmd) + return case *GlDisableVertexAttribArray: if c.Bound.VertexArray.VertexAttributeArrays.Get(cmd.Location).Enabled == GLboolean_GL_FALSE { diff --git a/gapis/api/gles/compat_buffers.go b/gapis/api/gles/compat_buffers.go new file mode 100644 index 0000000000..97a95416c3 --- /dev/null +++ b/gapis/api/gles/compat_buffers.go @@ -0,0 +1,190 @@ +// Copyright (C) 2017 Google Inc. +// +// 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 ( + "context" + + "github.com/google/gapid/gapis/api" + "github.com/google/gapid/gapis/api/transform" + "github.com/google/gapid/gapis/memory" +) + +// bufferCompat provides compatibility transformations for GL buffers. +type bufferCompat struct { + // uniformBufferAlignment is the target's minimum alignment for uniform + // buffers. + uniformBufferAlignment int + // unaligned is a map of compat generated aligned buffer IDs to their + // original buffer. + unaligned map[BufferId]*Buffer + // scratch holds the temporary buffers created by the bufferCompat. + scratch map[scratchBufferKey]scratchBuffer + // nextBufferID is the buffer identifer to use for the next created scratch + // buffer. + nextBufferID BufferId +} + +func newBufferCompat(uniformBufferAlignment int) *bufferCompat { + return &bufferCompat{ + uniformBufferAlignment: uniformBufferAlignment, + unaligned: map[BufferId]*Buffer{}, + scratch: map[scratchBufferKey]scratchBuffer{}, + nextBufferID: BufferId(0xffff0000), + } +} + +// scratchBufferKey is the key to the bufferCompat's scratch-buffer map. +type scratchBufferKey struct { + c *Context // The current GL context. + Target GLenum // The buffer target. + Index GLuint // The buffer binding index. +} + +// scratchBufferKey is the value to the bufferCompat's scratch-buffer map. +type scratchBuffer struct { + size GLsizeiptr // Size of the buffer. + id BufferId // Identifier of the buffer. +} + +// modifyBufferData deals with the complexities of copying unaligned buffer data +// to their aligned copies and should be called when ever a buffer is to be +// modified. modify is called to apply the buffer modification. +func (m *bufferCompat) modifyBufferData(ctx context.Context, out transform.Writer, cb CommandBuilder, c *Context, id api.CmdID, target GLenum, modify func()) { + id = id.Derived() + s := out.State() + + // Get the target buffer. + buf, err := subGetBoundBuffer(ctx, nil, api.CmdNoID, nil, s, GetState(s), cb.Thread, nil, target) + if buf == nil || err != nil { + // Unknown buffer + modify() + return + } + + // Lookup the original (unaligned) buffer. + unaligned, ok := m.unaligned[buf.ID] + if !ok { + // Buffer was not unaligned + modify() + return + } + + // Walk the current bindings looking for those referencing the aligned + // buffer. + type binding struct { + index GLuint + offset GLintptr + size GLsizeiptr + } + + rebind := []binding{} + for i, b := range c.Bound.UniformBuffers.Range() { + if b.Binding == nil || m.unaligned[b.Binding.ID] != unaligned { + continue + } + + rebind = append(rebind, binding{ + index: GLuint(i), + offset: b.Start, + size: b.Size, + }) + } + + if len(rebind) == 0 { + // No unaligned buffers require copying. + modify() + return + } + + // Bind the original unaligned buffer. + out.MutateAndWrite(ctx, id, cb.GlBindBuffer(target, unaligned.ID)) + + // Apply the modification. + modify() + + // Rebind all the unaligned bindings. + for _, r := range rebind { + cmd := cb.GlBindBufferRange(target, r.index, buf.ID, r.offset, r.size) + m.bindBufferRange(ctx, out, cb, c, id, cmd) + } +} + +// bindBufferRange provides compatibiliy for glBindBufferRange by handling +// buffers that do not meet their minimum alignment on the target device. +// If a buffer is unaligned, then a new buffer is created and the data range is +// copied to this new buffer, and the new buffer is bound. +func (m *bufferCompat) bindBufferRange(ctx context.Context, out transform.Writer, cb CommandBuilder, c *Context, id api.CmdID, cmd *GlBindBufferRange) { + misalignment := cmd.Offset % GLintptr(m.uniformBufferAlignment) + + if cmd.Target != GLenum_GL_UNIFORM_BUFFER || misalignment == 0 { + out.MutateAndWrite(ctx, id, cmd) + return + } + + dID := id.Derived() + + // We have a glBindBufferRange() taking a uniform buffer with an illegal + // offset alignment. + + orig := c.Objects.Buffers.Get(cmd.Buffer) + if orig == nil { + return // Don't know what buffer this is referring to. + } + + // We need a scratch buffer to copy the buffer data to a correct + // alignment. + scratchKey := scratchBufferKey{c, cmd.Target, cmd.Index} + + // Look for pre-existing buffer we can reuse. + buffer, ok := m.scratch[scratchKey] + if !ok { + buffer.id = m.newBuffer(ctx, dID, cb, out) + m.scratch[scratchKey] = buffer + } + + // Bind the scratch buffer to GL_COPY_WRITE_BUFFER + origCopyWriteBuffer := c.Bound.CopyWriteBuffer + out.MutateAndWrite(ctx, dID, cb.GlBindBuffer(GLenum_GL_COPY_WRITE_BUFFER, buffer.id)) + + if buffer.size < cmd.Size { + // Resize the scratch buffer + out.MutateAndWrite(ctx, dID, cb.GlBufferData(GLenum_GL_COPY_WRITE_BUFFER, cmd.Size, memory.Nullptr, GLenum_GL_DYNAMIC_COPY)) + buffer.size = cmd.Size + m.scratch[scratchKey] = buffer + } + + // Copy out the unaligned data to the scratch buffer in the + // GL_COPY_WRITE_BUFFER binding. + out.MutateAndWrite(ctx, dID, cb.GlBindBuffer(cmd.Target, cmd.Buffer)) + out.MutateAndWrite(ctx, dID, cb.GlCopyBufferSubData(cmd.Target, GLenum_GL_COPY_WRITE_BUFFER, cmd.Offset, 0, cmd.Size)) + + // We can now bind the range with correct alignment. + out.MutateAndWrite(ctx, id, cb.GlBindBufferRange(cmd.Target, cmd.Index, buffer.id, 0, cmd.Size)) + + // Restore old GL_COPY_WRITE_BUFFER binding. + out.MutateAndWrite(ctx, dID, cb.GlBindBuffer(GLenum_GL_COPY_WRITE_BUFFER, origCopyWriteBuffer.GetID())) + + m.unaligned[buffer.id] = orig +} + +func (m *bufferCompat) newBuffer(ctx context.Context, id api.CmdID, cb CommandBuilder, out transform.Writer) BufferId { + s := out.State() + bufID := m.nextBufferID + tmp := s.AllocDataOrPanic(ctx, bufID) + out.MutateAndWrite(ctx, id, cb.GlGenBuffers(1, tmp.Ptr()).AddWrite(tmp.Data())) + m.nextBufferID-- + return bufID +} From 20ad88e473b23778ee4d106c8b6b70fbae9e96a4 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Wed, 14 Mar 2018 17:18:19 +0000 Subject: [PATCH 2/2] gles compat: Replace GL_BACK to GL_FRONT for glDrawBuffers() We only render single-buffered at the moment. --- gapis/api/gles/compat.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gapis/api/gles/compat.go b/gapis/api/gles/compat.go index 4ce3be2e8d..47b94a2d9f 100644 --- a/gapis/api/gles/compat.go +++ b/gapis/api/gles/compat.go @@ -458,6 +458,21 @@ func compat(ctx context.Context, device *device.Instance, onError onCompatError) } return + case *GlDrawBuffers: + // Currently the default framebuffer for replay is single-buffered + // and so we need to transform any usage of GL_BACK to GL_FRONT. + cmd.Extras().Observations().ApplyReads(s.Memory.ApplicationPool()) + bufs := cmd.Bufs.Slice(0, uint64(cmd.N), s.MemoryLayout).MustRead(ctx, cmd, s, nil) + for i, buf := range bufs { + if buf == GLenum_GL_BACK { + bufs[i] = GLenum_GL_FRONT + } + } + tmp := s.AllocDataOrPanic(ctx, bufs) + out.MutateAndWrite(ctx, id, cb.GlDrawBuffers(cmd.N, tmp.Ptr()).AddRead(tmp.Data())) + tmp.Free() + return + case *GlDrawArrays: if target.vertexArrayObjects == required { if clientVAsBound(c, clientVAs) {