From 8ebadbf2611481ead13cc6d5989ddc944e5f43bc Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Tue, 12 Jun 2018 15:09:53 -0700 Subject: [PATCH 1/7] Ignore eglGetError calls while recording state. Android drivers love to call eglGetError from within other egl* functions. --- gapii/cc/spy.cpp | 10 ++++++++++ gapii/cc/spy.h | 1 + gapii/cc/spy_base.cpp | 3 ++- gapii/cc/spy_base.h | 8 ++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/gapii/cc/spy.cpp b/gapii/cc/spy.cpp index 59550de586..f3f4447f5f 100644 --- a/gapii/cc/spy.cpp +++ b/gapii/cc/spy.cpp @@ -467,8 +467,10 @@ void Spy::onPreStartOfFrame(CallObserver* observer, uint8_t api) { void Spy::saveInitialState() { GAPID_INFO("Saving initial state"); + set_recording_state(true); saveInitialStateForApi("gles-initial-state"); saveInitialStateForApi("vulkan-initial-state"); + set_recording_state(false); } template @@ -667,6 +669,14 @@ uint32_t Spy::glGetError(CallObserver* observer) { return GlesSpy::glGetError(observer); } +EGLint Spy::eglGetError(CallObserver* observer) { + // Ignore any (probably nested) eglGetError calls when recording state. + if (is_recording_state()) { + return GlesSpy::mImports.eglGetError(); + } + return GlesSpy::eglGetError(observer); +} + #if 0 // NON-EGL CONTEXTS ARE CURRENTLY NOT SUPPORTED gapil::Ref Spy::getWGLContextState(CallObserver*, HDC hdc, HGLRC hglrc) { if (hglrc == nullptr) { diff --git a/gapii/cc/spy.h b/gapii/cc/spy.h index 9aacff2f2b..99fdb25048 100644 --- a/gapii/cc/spy.h +++ b/gapii/cc/spy.h @@ -91,6 +91,7 @@ class Spy : public GlesSpy, public GvrSpy, public VulkanSpy { void setFakeGlError(CallObserver* observer, GLenum_Error error); uint32_t glGetError(CallObserver* observer); + EGLint eglGetError(CallObserver* observer); private: Spy(); diff --git a/gapii/cc/spy_base.cpp b/gapii/cc/spy_base.cpp index 57ad096221..4dc39db3cb 100644 --- a/gapii/cc/spy_base.cpp +++ b/gapii/cc/spy_base.cpp @@ -39,7 +39,8 @@ SpyBase::SpyBase() mCurrentABI(nullptr), mResources{{core::Id{{0}}, 0}}, mObserveApplicationPool(true), - mWatchedApis(0xFFFFFFFF) { + mWatchedApis(0xFFFFFFFF), + mIsRecordingState(false) { } void SpyBase::init(CallObserver* observer) { diff --git a/gapii/cc/spy_base.h b/gapii/cc/spy_base.h index 4fe1fac9b7..b173ee3e54 100644 --- a/gapii/cc/spy_base.h +++ b/gapii/cc/spy_base.h @@ -106,6 +106,9 @@ class SpyBase { void set_observing(bool observing) { mIsObserving = observing; } bool is_observing() const { return mIsObserving; } + bool is_recording_state() const { return mIsRecordingState; } + void set_recording_state(bool recording) { mIsRecordingState = recording; } + protected: // lock begins the interception of a single command. It must be called // before invoking any command on the spy. Blocks if any other thread @@ -222,6 +225,11 @@ class SpyBase { // For some API's this will require that we modify some of the // image creation parameters bool mIsObserving; + + // This is true when all commands are used to record state. This means + // the commands should still be recorded, but the underlying functions + // should not be called. + bool mIsRecordingState; }; template From 6064de1fac4b98995edd481134d333ef366193aa Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Tue, 12 Jun 2018 15:09:56 -0700 Subject: [PATCH 2/7] Don't recreate destroyed contexts. --- gapis/api/gles/state_builder.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gapis/api/gles/state_builder.go b/gapis/api/gles/state_builder.go index e0b9fc3530..62a5440380 100644 --- a/gapis/api/gles/state_builder.go +++ b/gapis/api/gles/state_builder.go @@ -63,6 +63,11 @@ func (s *State) RebuildState(ctx context.Context, oldState *api.GlobalState) ([] representative := map[ShareListʳ]EGLContext{} for i := ContextID(0); i < s.NextContextID(); i++ { for handle, c := range s.EGLContexts().All() { + // Don't recreate destroyed or uninitialized contexts. + if c.Other().Destroyed() || !c.Other().Initialized() { + continue + } + // TODO: We need to restore contexts in order without gaps, but this is messy. if c.Identifier() == i { sb.contextObject(ctx, handle, c, representative) @@ -198,10 +203,6 @@ func (sb *stateBuilder) contextObject(ctx context.Context, handle EGLContext, c write(api.WithExtras(cb.EglMakeCurrent(memory.Nullptr, memory.Nullptr, memory.Nullptr, handle, EGLBoolean(1)), c.Other().StaticStateExtra(), c.Other().DynamicStateExtra())) - if !c.Other().Initialized() { - return - } - write(cb.GlPixelStorei(GLenum_GL_UNPACK_ALIGNMENT, 1)) if names := c.Objects().GeneratedNames().Buffers(); sb.once(names) { From 646a230cf844180e4964b10e5ddc2224b310cd4d Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Tue, 12 Jun 2018 15:09:59 -0700 Subject: [PATCH 3/7] Fixes to extras for MEC: - Don't put references into the extras in MEC state rebuilding. - Clone extras before using them in the new state. - Handle null context extras. --- gapis/api/gles/state_builder.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/gapis/api/gles/state_builder.go b/gapis/api/gles/state_builder.go index 62a5440380..7c02f0e48e 100644 --- a/gapis/api/gles/state_builder.go +++ b/gapis/api/gles/state_builder.go @@ -38,6 +38,7 @@ type stateBuilder struct { tmpArena arena.Arena // The arena to use for temporary allocations seen map[interface{}]bool memoryIntervals interval.U64RangeList + cloneCtx api.CloneContext } func (s *State) RebuildState(ctx context.Context, oldState *api.GlobalState) ([]api.Cmd, interval.U64RangeList) { @@ -50,6 +51,7 @@ func (s *State) RebuildState(ctx context.Context, oldState *api.GlobalState) ([] tmpArena: arena.New(), seen: map[interface{}]bool{}, memoryIntervals: interval.U64RangeList{}, + cloneCtx: api.CloneContext{}, } defer sb.tmpArena.Dispose() @@ -191,6 +193,17 @@ func (sb *stateBuilder) once(key interface{}) (res bool) { return } +func (sb *stateBuilder) contextExtras(c Contextʳ) []api.CmdExtra { + r := []api.CmdExtra{} + if se := c.Other().StaticStateExtra(); !se.IsNil() { + r = append(r, se.Get().Clone(sb.cb.Arena, sb.cloneCtx)) + } + if de := c.Other().DynamicStateExtra(); !de.IsNil() { + r = append(r, de.Get().Clone(sb.cb.Arena, sb.cloneCtx)) + } + return r +} + func (sb *stateBuilder) contextObject(ctx context.Context, handle EGLContext, c Contextʳ, representative map[ShareListʳ]EGLContext) { write, cb := sb.write, sb.cb @@ -201,7 +214,7 @@ func (sb *stateBuilder) contextObject(ctx context.Context, handle EGLContext, c // TODO: Record the arguments in state. write(cb.EglCreateContext(memory.Nullptr, memory.Nullptr, sharedCtx, memory.Nullptr, handle)) write(api.WithExtras(cb.EglMakeCurrent(memory.Nullptr, memory.Nullptr, memory.Nullptr, handle, EGLBoolean(1)), - c.Other().StaticStateExtra(), c.Other().DynamicStateExtra())) + sb.contextExtras(c)...)) write(cb.GlPixelStorei(GLenum_GL_UNPACK_ALIGNMENT, 1)) @@ -359,7 +372,7 @@ func (sb *stateBuilder) contextObjectPostEGLImage(ctx context.Context, handle EG if c.Other().Initialized() { write(api.WithExtras(cb.EglMakeCurrent(memory.Nullptr, memory.Nullptr, memory.Nullptr, handle, EGLBoolean(1)), - c.Other().StaticStateExtra(), c.Other().DynamicStateExtra())) + sb.contextExtras(c)...)) for _, t := range c.Objects().Textures().All() { target := t.Kind() @@ -432,7 +445,7 @@ func (sb *stateBuilder) bindContexts(ctx context.Context, s *State) { if thread := c.Other().BoundOnThread(); thread != 0 { cb := CommandBuilder{Thread: thread, Arena: sb.cb.Arena} write(api.WithExtras(cb.EglMakeCurrent(memory.Nullptr, memory.Nullptr, memory.Nullptr, handle, EGLBoolean(1)), - c.Other().StaticStateExtra(), c.Other().DynamicStateExtra())) + sb.contextExtras(c)...)) } } write(cb.EglMakeCurrent(memory.Nullptr, memory.Nullptr, memory.Nullptr, memory.Nullptr, EGLBoolean(1))) @@ -607,7 +620,7 @@ func (sb *stateBuilder) shaderObject(ctx context.Context, s Shaderʳ) { sb.E(ctx, "Precompiled shaders not suppored yet") // TODO } write(cb.GlShaderSource(id, 1, sb.readsData(ctx, sb.readsData(ctx, e.Source())), memory.Nullptr)) - write(api.WithExtras(cb.GlCompileShader(id), e)) + write(api.WithExtras(cb.GlCompileShader(id), e.Get().Clone(cb.Arena, sb.cloneCtx))) } write(cb.GlShaderSource(id, 1, sb.readsData(ctx, sb.readsData(ctx, s.Source())), memory.Nullptr)) } @@ -645,7 +658,7 @@ func (sb *stateBuilder) programObject(ctx context.Context, p Programʳ) { if !p.LinkExtra().Binary().IsNil() { sb.E(ctx, "Precompiled programs not suppored yet") // TODO } - write(api.WithExtras(cb.GlLinkProgram(id), p.LinkExtra())) + write(api.WithExtras(cb.GlLinkProgram(id), p.LinkExtra().Get().Clone(cb.Arena, sb.cloneCtx))) write(cb.GlUseProgram(id)) for _, u := range p.ActiveResources().DefaultUniformBlock().All() { if loc, ok := u.Locations().Lookup(0); ok { @@ -655,7 +668,7 @@ func (sb *stateBuilder) programObject(ctx context.Context, p Programʳ) { write(cb.GlUseProgram(0)) } if !p.ValidateExtra().IsNil() { - write(api.WithExtras(cb.GlValidateProgram(id), p.ValidateExtra())) + write(api.WithExtras(cb.GlValidateProgram(id), p.ValidateExtra().Get().Clone(cb.Arena, sb.cloneCtx))) } } @@ -726,7 +739,7 @@ func (sb *stateBuilder) pipelineObject(ctx context.Context, pipe Pipelineʳ) { write(cb.GlUseProgramStages(id, GLbitfield_GL_TESS_EVALUATION_SHADER_BIT, pipe.TessEvaluationShader().GetID())) write(cb.GlUseProgramStages(id, GLbitfield_GL_GEOMETRY_SHADER_BIT, pipe.GeometryShader().GetID())) if !pipe.ValidateExtra().IsNil() { - write(api.WithExtras(cb.GlValidateProgramPipeline(id), pipe.ValidateExtra())) + write(api.WithExtras(cb.GlValidateProgramPipeline(id), pipe.ValidateExtra().Get().Clone(cb.Arena, sb.cloneCtx))) } write(cb.GlBindProgramPipeline(0)) } From e843d2b89fdb7497e52ffb138b5e6f4f561f2f1f Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Wed, 2 May 2018 09:45:15 -0700 Subject: [PATCH 4/7] Enable MEC for GLES in the UI. --- .../src/main/com/google/gapid/views/TracerDialog.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/gapic/src/main/com/google/gapid/views/TracerDialog.java b/gapic/src/main/com/google/gapid/views/TracerDialog.java index 9fee1a6d6d..f09de8e688 100644 --- a/gapic/src/main/com/google/gapid/views/TracerDialog.java +++ b/gapic/src/main/com/google/gapid/views/TracerDialog.java @@ -440,17 +440,6 @@ public AndroidInput( traceTarget.addBoxListener(SWT.Modify, targetListener); targetListener.handleEvent(null); - Listener apiListener = e -> { - if (getSelectedApi() == Tracer.Api.Vulkan) { - fromBeginning.setEnabled(true); - } else { - fromBeginning.setEnabled(false); - fromBeginning.setSelection(true); - } - }; - api.getCombo().addListener(SWT.Selection, apiListener); - apiListener.handleEvent(null); - disablePcs.addListener( SWT.Selection, e -> pcsWarning.setVisible(!disablePcs.getSelection())); } From 11ecc128c0e7786f217fc8c46598eb3a32c591d2 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Tue, 12 Jun 2018 15:10:03 -0700 Subject: [PATCH 5/7] Read the GPU data back for GLES MEC. Introduces mechanisms to read back data for renderbuffers and textures from the GPU for MEC. --- gapii/cc/gles_extras.cpp | 192 ----- gapii/cc/gles_mid_execution.cpp | 769 ++++++++++++++++++- gapis/api/gles/api/buffer_objects.api | 8 +- gapis/api/gles/api/textures_and_samplers.api | 5 +- 4 files changed, 775 insertions(+), 199 deletions(-) diff --git a/gapii/cc/gles_extras.cpp b/gapii/cc/gles_extras.cpp index fba5ee294b..0d638e843b 100644 --- a/gapii/cc/gles_extras.cpp +++ b/gapii/cc/gles_extras.cpp @@ -687,198 +687,6 @@ bool GlesSpy::getFramebufferAttachmentSize(CallObserver* observer, return false; } -static bool ReadExternalPixels(GlesImports& imports, EGLImageKHR img, - GLsizei width, GLsizei height, - std::vector* data) { - using namespace GLenum; - - const char* vsSource = - "precision highp float;\n" - "attribute vec2 position;\n" - "varying vec2 texcoord;\n" - "void main() {\n" - " gl_Position = vec4(position, 0.5, 1.0);\n" - " texcoord = position * vec2(0.5) + vec2(0.5);\n" - "}\n"; - - const char* fsSource = - "#extension GL_OES_EGL_image_external : require\n" - "precision highp float;\n" - "uniform samplerExternalOES tex;\n" - "varying vec2 texcoord;\n" - "void main() {\n" - " gl_FragColor = texture2D(tex, texcoord);\n" - "}\n"; - - GLint err; - auto prog = imports.glCreateProgram(); - - auto vs = imports.glCreateShader(GL_VERTEX_SHADER); - imports.glShaderSource(vs, 1, &vsSource, nullptr); - imports.glCompileShader(vs); - imports.glAttachShader(prog, vs); - - auto fs = imports.glCreateShader(GL_FRAGMENT_SHADER); - imports.glShaderSource(fs, 1, &fsSource, nullptr); - imports.glCompileShader(fs); - imports.glAttachShader(prog, fs); - - imports.glBindAttribLocation(prog, 0, "position"); - imports.glLinkProgram(prog); - - if ((err = imports.glGetError()) != 0) { - GAPID_ERROR("ReadExternalPixels: Failed to create program: 0x%X", err); - return false; - } - - GLint linkStatus = 0; - imports.glGetProgramiv(prog, GL_LINK_STATUS, &linkStatus); - if (linkStatus == 0) { - char log[1024]; - imports.glGetProgramInfoLog(prog, sizeof(log), nullptr, log); - GAPID_ERROR("ReadExternalPixels: Failed to compile program:\n%s", log); - return false; - } - - GLuint srcTex = 0; - imports.glGenTextures(1, &srcTex); - imports.glBindTexture(GL_TEXTURE_EXTERNAL_OES, srcTex); - imports.glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, img); - imports.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, - GL_NEAREST); - imports.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, - GL_NEAREST); - - GLuint dstTex = 0; - imports.glGenTextures(1, &dstTex); - imports.glBindTexture(GL_TEXTURE_2D, dstTex); - imports.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, nullptr); - - if ((err = imports.glGetError()) != 0) { - GAPID_ERROR("ReadExternalPixels: Failed to create texture: 0x%X", err); - return false; - } - - GLuint fb = 0; - imports.glGenFramebuffers(1, &fb); - imports.glBindFramebuffer(GL_FRAMEBUFFER, fb); - imports.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, dstTex, 0); - - if ((err = imports.glGetError()) != 0) { - GAPID_ERROR("ReadExternalPixels: Failed to create framebuffer: 0x%X", err); - return false; - } - if ((err = imports.glCheckFramebufferStatus(GL_FRAMEBUFFER)) != - GL_FRAMEBUFFER_COMPLETE) { - GAPID_ERROR("ReadExternalPixels: Framebuffer incomplete: 0x%X", err); - return false; - } - - imports.glDisable(GL_CULL_FACE); - imports.glDisable(GL_DEPTH_TEST); - imports.glViewport(0, 0, width, height); - imports.glClearColor(0.0, 0.0, 0.0, 0.0); - imports.glClear(GLbitfield::GL_COLOR_BUFFER_BIT); - imports.glUseProgram(prog); - GLfloat vb[] = { - -1.0f, +1.0f, // 2--4 - -1.0f, -1.0f, // |\ | - +1.0f, +1.0f, // | \| - +1.0f, -1.0f, // 1--3 - }; - imports.glEnableVertexAttribArray(0); - imports.glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, vb); - imports.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - if ((err = imports.glGetError()) != 0) { - GAPID_ERROR("ReadExternalPixels: Failed to draw quad: 0x%X", err); - return false; - } - - data->resize(width * height * 4); - imports.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, - data->data()); - if ((err = imports.glGetError()) != 0) { - GAPID_ERROR("ReadExternalPixels: Failed to read pixels: 0x%X", err); - return false; - } - - return true; -} - -void GlesSpy::GetEGLImageData(CallObserver* observer, EGLImageKHR img, - GLsizei width, GLsizei height) { - using namespace EGLenum; - - if (!should_trace(kApiIndex)) { - return; - } - - GAPID_DEBUG("Get EGLImage data: 0x%p x%xx%x", img, width, height); - - // Save old state. - auto display = mImports.eglGetCurrentDisplay(); - auto draw = mImports.eglGetCurrentSurface(EGL_DRAW); - auto read = mImports.eglGetCurrentSurface(EGL_READ); - auto oldCtx = mImports.eglGetCurrentContext(); - - // Find an EGL config. - EGLConfig cfg; - EGLint cfgAttribs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE}; - int one = 1; - if (mImports.eglChooseConfig(display, cfgAttribs, &cfg, 1, &one) == - EGL_FALSE || - one != 1) { - GAPID_ERROR("Failed to choose EGL config"); - return; - } - - // Create an EGL context. - EGLContext ctx; - EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - if ((ctx = mImports.eglCreateContext(display, cfg, nullptr, ctxAttribs)) == - nullptr) { - GAPID_ERROR("Failed to create EGL context"); - return; - } - - // Create an EGL surface. - EGLSurface surface; - EGLint surfaceAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE}; - if ((surface = mImports.eglCreatePbufferSurface(display, cfg, - surfaceAttribs)) == nullptr) { - GAPID_ERROR("Failed to create EGL surface"); - return; - } - - // Bind the EGL context. - if (mImports.eglMakeCurrent(display, surface, surface, ctx) == EGL_FALSE) { - GAPID_ERROR("Failed to bind new EGL context"); - return; - } - - std::vector data; - if (ReadExternalPixels(mImports, img, width, height, &data)) { - auto resIndex = sendResource(kApiIndex, data.data(), data.size()); - auto extra = new gles_pb::EGLImageData(); - extra->set_res_index(resIndex); - extra->set_size(data.size()); - extra->set_width(width); - extra->set_height(height); - extra->set_format(GLenum::GL_RGBA); - extra->set_type(GLenum::GL_UNSIGNED_BYTE); - observer->encodeAndDelete(extra); - } - - if (mImports.eglMakeCurrent(display, draw, read, oldCtx) == EGL_FALSE) { - GAPID_FATAL("Failed to restore old EGL context"); - } - - mImports.eglDestroySurface(display, surface); - mImports.eglDestroyContext(display, ctx); -} - bool GlesSpy::observeFramebuffer(CallObserver* observer, uint32_t* w, uint32_t* h, std::vector* data) { if (!getFramebufferAttachmentSize(observer, w, h)) { diff --git a/gapii/cc/gles_mid_execution.cpp b/gapii/cc/gles_mid_execution.cpp index 155ae1666a..aa3dff8b39 100644 --- a/gapii/cc/gles_mid_execution.cpp +++ b/gapii/cc/gles_mid_execution.cpp @@ -15,12 +15,779 @@ */ #include "gapii/cc/gles_spy.h" +#include "gapii/cc/gles_types.h" +#include "gapii/cc/spy.h" #include "gapii/cc/state_serializer.h" +#include "gapis/api/gles/gles_pb/extras.pb.h" + +namespace { +using namespace gapii; +using namespace gapii::GLenum; +using namespace gapii::GLbitfield; +using namespace gapii::EGLenum; + +struct ImageData { + std::unique_ptr> data; + uint32_t width; + uint32_t height; + GLint sizedFormat; + GLint dataFormat; + GLint dataType; +}; + +class TempObject { + public: + TempObject(uint64_t id, const std::function& deleteId) + : mId(id), mDeleteId(deleteId) {} + uint64_t id() { return mId; } + ~TempObject() { mDeleteId(); } + + private: + uint64_t mId; + std::function mDeleteId; +}; + +TempObject CreateAndBindFramebuffer(const GlesImports& imports, + uint32_t target) { + GLuint fb = 0; + imports.glGenFramebuffers(1, &fb); + imports.glBindFramebuffer(target, fb); + return TempObject(fb, [=] { + GLuint id = fb; + imports.glDeleteFramebuffers(1, &id); + }); +} + +TempObject CreateAndBindTexture2D(const GlesImports& imports, GLint w, GLint h, + uint32_t sizedFormat) { + GLuint tex = 0; + imports.glGenTextures(1, &tex); + imports.glBindTexture(GL_TEXTURE_2D, tex); + imports.glTexStorage2D(GL_TEXTURE_2D, 1, sizedFormat, w, h); + return TempObject(tex, [=] { + GLuint id = tex; + imports.glDeleteTextures(1, &id); + }); +} + +TempObject CreateAndBindTextureExternal(const GlesImports& imports, + EGLImageKHR handle) { + GLuint tex = 0; + imports.glGenTextures(1, &tex); + imports.glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex); + imports.glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, handle); + imports.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + imports.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + return TempObject(tex, [=] { + GLuint id = tex; + imports.glDeleteTextures(1, &id); + }); +} + +// Creates temporary GL context which shares objects with the given application +// context. This makes it easier to do a lot of work without worrying about +// corrupting the state. For example, calling glGetError would be otherwise +// technically invalid without hacks. +TempObject CreateAndBindContext(const GlesImports& imports, + EGLContext sharedContext, EGLint version) { + // Save old state. + auto display = imports.eglGetCurrentDisplay(); + auto draw = imports.eglGetCurrentSurface(EGL_DRAW); + auto read = imports.eglGetCurrentSurface(EGL_READ); + auto oldCtx = imports.eglGetCurrentContext(); + + // Find an EGL config. + EGLConfig cfg; + EGLint cfgAttribs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE}; + int one = 1; + if (imports.eglChooseConfig(display, cfgAttribs, &cfg, 1, &one) == + EGL_FALSE || + one != 1) { + GAPID_FATAL("MEC: Failed to choose EGL config: 0x%x", + imports.eglGetError()); + } + + // Create an EGL context. + EGLContext ctx; + EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, version, EGL_NONE}; + if ((ctx = imports.eglCreateContext(display, cfg, sharedContext, + ctxAttribs)) == nullptr) { + EGLint error = imports.eglGetError(); + if (sharedContext == nullptr || error != EGL_BAD_MATCH) { + GAPID_WARNING("MEC: Failed to create EGL context: 0x%x", error); + } else { + GAPID_WARNING("MEC: BAD_MATCH creating shared context. Querying config."); + EGLint configId = 42; + if (imports.eglQueryContext(display, sharedContext, EGL_CONFIG_ID, + &configId) == EGL_FALSE) { + GAPID_WARNING("MEC: Failed to query the config ID of the context: 0x%x", + imports.eglGetError()); + } else { + EGLint cfgAttribs[] = {EGL_CONFIG_ID, configId, EGL_NONE}; + if (imports.eglChooseConfig(display, cfgAttribs, &cfg, 1, &one) == + EGL_FALSE || + one != 1) { + GAPID_WARNING("MEC: Failed to choose EGL config by id %d: 0x%x", + configId, imports.eglGetError()); + } else if ((ctx = imports.eglCreateContext(display, cfg, sharedContext, + ctxAttribs)) == nullptr) { + GAPID_WARNING("MEC: Failed to create EGL context: 0x%x", + imports.eglGetError()); + } + } + } + } + + if (ctx == nullptr) { + return TempObject(reinterpret_cast(ctx), [=] { + if (imports.eglMakeCurrent(display, draw, read, oldCtx) == EGL_FALSE) { + GAPID_FATAL("MEC: Failed to restore old EGL context"); + } + }); + } + + // Create an EGL surface. + EGLSurface surface; + EGLint surfaceAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE}; + if ((surface = imports.eglCreatePbufferSurface(display, cfg, + surfaceAttribs)) == nullptr) { + GAPID_FATAL("MEC: Failed to create EGL surface: 0x%x", + imports.eglGetError()); + } + + // Bind the EGL context. + if (imports.eglMakeCurrent(display, surface, surface, ctx) == EGL_FALSE) { + GAPID_FATAL("MEC: Failed to bind new EGL context: 0x%x", + imports.eglGetError()); + } + + // Setup desirable default state for reading data. + imports.glPixelStorei(GL_PACK_ALIGNMENT, 1); + imports.glPixelStorei(GL_PACK_ROW_LENGTH, 0); + imports.glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + imports.glPixelStorei(GL_PACK_SKIP_ROWS, 0); + + return TempObject(reinterpret_cast(ctx), [=] { + if (imports.eglMakeCurrent(display, draw, read, oldCtx) == EGL_FALSE) { + GAPID_FATAL("MEC: Failed to restore old EGL context"); + } + imports.eglDestroySurface(display, surface); + imports.eglDestroyContext(display, ctx); + }); +} + +void DrawTexturedQuad(const GlesImports& imports, uint32_t textureTarget, + GLsizei w, GLsizei h) { + GLint err; + + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Entered DrawTextureQuad in error state: 0x%X", err); + } + + std::string vsSource; + vsSource += "precision highp float;\n"; + vsSource += "attribute vec2 position;\n"; + vsSource += "varying vec2 texcoord;\n"; + vsSource += "void main() {\n"; + vsSource += " gl_Position = vec4(position, 0.5, 1.0);\n"; + vsSource += " texcoord = position * vec2(0.5) + vec2(0.5);\n"; + vsSource += "}\n"; + + std::string fsSource; + fsSource += "#extension GL_OES_EGL_image_external : require\n"; + fsSource += "precision highp float;\n"; + if (textureTarget == GL_TEXTURE_EXTERNAL_OES) { + fsSource += "uniform samplerExternalOES tex;\n"; + } else { + fsSource += "uniform sampler2D tex;\n"; + } + fsSource += "varying vec2 texcoord;\n"; + fsSource += "void main() {\n"; + fsSource += " gl_FragColor = texture2D(tex, texcoord);\n"; + fsSource += "}\n"; + + auto prog = imports.glCreateProgram(); + + auto vs = imports.glCreateShader(GL_VERTEX_SHADER); + char* vsSources[] = {const_cast(vsSource.data())}; + imports.glShaderSource(vs, 1, vsSources, nullptr); + imports.glCompileShader(vs); + imports.glAttachShader(prog, vs); + + auto fs = imports.glCreateShader(GL_FRAGMENT_SHADER); + char* fsSources[] = {const_cast(fsSource.data())}; + imports.glShaderSource(fs, 1, fsSources, nullptr); + imports.glCompileShader(fs); + imports.glAttachShader(prog, fs); + + imports.glBindAttribLocation(prog, 0, "position"); + imports.glLinkProgram(prog); + + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to create program: 0x%X", err); + } + + GLint linkStatus = 0; + imports.glGetProgramiv(prog, GL_LINK_STATUS, &linkStatus); + if (linkStatus == 0) { + char log[1024]; + imports.glGetProgramInfoLog(prog, sizeof(log), nullptr, log); + GAPID_FATAL("MEC: Failed to compile program:\n%s", log); + } + + if ((err = imports.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)) != + GL_FRAMEBUFFER_COMPLETE) { + GAPID_FATAL("MEC: Draw framebuffer incomplete: 0x%X", err); + } + + imports.glDisable(GL_CULL_FACE); + imports.glDisable(GL_DEPTH_TEST); + imports.glViewport(0, 0, w, h); + imports.glClearColor(0.0, 0.0, 0.0, 0.0); + imports.glClear(GLbitfield::GL_COLOR_BUFFER_BIT); + imports.glUseProgram(prog); + GLfloat vb[] = { + -1.0f, +1.0f, // 2--4 + -1.0f, -1.0f, // |\ | + +1.0f, +1.0f, // | \| + +1.0f, -1.0f, // 1--3 + }; + imports.glEnableVertexAttribArray(0); + imports.glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, vb); + imports.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to draw quad: 0x%X", err); + } + + imports.glDeleteShader(vs); + imports.glDeleteShader(fs); + imports.glDeleteProgram(prog); +} + +ImageData ReadPixels(const GlesImports& imports, GLsizei w, GLsizei h) { + ImageData img; + uint32_t err; + + if ((err = imports.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)) != + GL_FRAMEBUFFER_COMPLETE) { + GAPID_FATAL("MEC: ReadPixels: Framebuffer incomplete: 0x%X", err); + } + + // Ask the driver what is the ideal format/type for reading the pixels. + imports.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &img.dataFormat); + imports.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &img.dataType); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: ReadPixels: Failed to get data format/type: 0x%X", err); + } + GAPID_DEBUG("MEC: Reading pixels as format 0x%x and type 0x%x", + img.dataFormat, img.dataType); + + auto spy = Spy::get(); + auto observer = spy->enter("subUncompressedImageSize", GlesSpy::kApiIndex); + auto size = spy->subUncompressedImageSize(observer, [] {}, w, h, + img.dataFormat, img.dataType); + spy->exit(); + + img.sizedFormat = GL_NONE; + img.width = w; + img.height = h; + img.data.reset(new std::vector(size)); + imports.glReadnPixels(0, 0, w, h, img.dataFormat, img.dataType, + img.data->size(), img.data->data()); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to read pixels: 0x%X", err); + } + + return img; +} + +typedef struct { + GLint r, g, b, a; +} swizzle_t; + +ImageData ReadTextureViaDrawQuad(const GlesImports& imports, GLint texID, + GLsizei w, GLsizei h, uint32_t format, + swizzle_t swizzle); + +ImageData ReadOneChannelTextureViaDrawQuad(const GlesImports& imports, + uint32_t kind, GLint texID, + GLsizei w, GLsizei h, + uint32_t format, const char* name, + uint32_t originalFormat, + GLint rSwizzle) { + if (kind != GL_TEXTURE_2D) { + // TODO: Copy the layer/level to temporary 2D texture. + GAPID_WARNING( + "MEC: Reading of %s data for target 0x%x is not yet supported", name, + kind); + return ImageData{}; + } + ImageData result = ReadTextureViaDrawQuad( + imports, texID, w, h, format, {rSwizzle, GL_ZERO, GL_ZERO, GL_ONE}); + // Restore original format, so it doesn't show up as GL_RED in the UI. + result.dataFormat = originalFormat; + return result; +} + +ImageData ReadTwoChannelTextureViaDrawQuad(const GlesImports& imports, + uint32_t kind, GLint texID, + GLsizei w, GLsizei h, + uint32_t format, const char* name, + uint32_t originalFormat, + GLint rSwizzle, GLint gSwizzle) { + if (kind != GL_TEXTURE_2D) { + // TODO: Copy the layer/level to temporary 2D texture. + GAPID_WARNING( + "MEC: Reading of %s data for target 0x%x is not yet supported", name, + kind); + return ImageData{}; + } + ImageData result = ReadTextureViaDrawQuad( + imports, texID, w, h, format, {rSwizzle, gSwizzle, GL_ZERO, GL_ONE}); + // Restore original format, so it doesn't show up as GL_RG in the UI. + result.dataFormat = originalFormat; + return result; +} + +ImageData ReadCompressedTexture(const GlesImports& imports, uint32_t kind, + GLint texID, GLsizei w, GLsizei h, + uint32_t format, swizzle_t swizzle) { + if (kind != GL_TEXTURE_2D) { + // TODO: Copy the layer/level to temporary 2D texture. + GAPID_WARNING( + "MEC: Reading of compressed data for target 0x%x is not yet supported", + kind); + return ImageData{}; + } + ImageData result = + ReadTextureViaDrawQuad(imports, texID, w, h, format, swizzle); + result.sizedFormat = format; + return result; +} + +ImageData ReadTexture(const GlesImports& imports, uint32_t kind, GLint texID, + GLint level, GLint layer, GLsizei w, GLsizei h, + uint32_t format) { + GAPID_DEBUG("MEC: Reading texture %d kind 0x%x %dx%d format 0x%x", texID, + kind, w, h, format); + switch (format) { + /* depth and stencil */ + case GL_STENCIL_INDEX8: + GAPID_WARNING("MEC: Reading of stencil data is not yet supported"); + break; + case GL_DEPTH24_STENCIL8: + case GL_DEPTH32F_STENCIL8: + GAPID_WARNING("MEC: Reading of stencil data is not yet supported"); + // Fall through to depth + case GL_DEPTH_COMPONENT16: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32F: + return ReadOneChannelTextureViaDrawQuad(imports, kind, texID, w, h, + GL_R32F, "depth", + GL_DEPTH_COMPONENT, GL_RED); + /* alpha and luminance */ + case GL_ALPHA8_EXT: + return ReadOneChannelTextureViaDrawQuad(imports, kind, texID, w, h, GL_R8, + "alpha", GL_ALPHA, GL_ALPHA); + case GL_ALPHA16F_EXT: + return ReadOneChannelTextureViaDrawQuad( + imports, kind, texID, w, h, GL_R16F_EXT, "alpha", GL_ALPHA, GL_ALPHA); + case GL_ALPHA32F_EXT: + return ReadOneChannelTextureViaDrawQuad( + imports, kind, texID, w, h, GL_R32F, "alpha", GL_ALPHA, GL_ALPHA); + case GL_LUMINANCE8_EXT: + return ReadOneChannelTextureViaDrawQuad( + imports, kind, texID, w, h, GL_R8, "luminance", GL_LUMINANCE, GL_RED); + case GL_LUMINANCE16F_EXT: + return ReadOneChannelTextureViaDrawQuad(imports, kind, texID, w, h, + GL_R16F_EXT, "luminance", + GL_LUMINANCE, GL_RED); + case GL_LUMINANCE32F_EXT: + return ReadOneChannelTextureViaDrawQuad(imports, kind, texID, w, h, + GL_R32F, "luminance", + GL_LUMINANCE, GL_RED); + case GL_LUMINANCE8_ALPHA8_EXT: + return ReadTwoChannelTextureViaDrawQuad( + imports, kind, texID, w, h, GL_RG8, "luminance alpha", + GL_LUMINANCE_ALPHA, GL_RED, GL_ALPHA); + case GL_LUMINANCE_ALPHA16F_EXT: + return ReadTwoChannelTextureViaDrawQuad( + imports, kind, texID, w, h, GL_RG16F_EXT, "luminance alpha", + GL_LUMINANCE_ALPHA, GL_RED, GL_ALPHA); + case GL_LUMINANCE_ALPHA32F_EXT: + return ReadTwoChannelTextureViaDrawQuad( + imports, kind, texID, w, h, GL_RG32F, "luminance alpha", + GL_LUMINANCE_ALPHA, GL_RED, GL_ALPHA); + /* compressed 8bit RGB */ + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_ATC_RGB_AMD: + case GL_ETC1_RGB8_OES: + return ReadCompressedTexture(imports, kind, texID, w, h, GL_RGB8, + {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}); + /* compressed 8bit RGBA */ + case GL_COMPRESSED_RGBA_ASTC_4x4: + case GL_COMPRESSED_RGBA_ASTC_5x4: + case GL_COMPRESSED_RGBA_ASTC_5x5: + case GL_COMPRESSED_RGBA_ASTC_6x5: + case GL_COMPRESSED_RGBA_ASTC_6x6: + case GL_COMPRESSED_RGBA_ASTC_8x5: + case GL_COMPRESSED_RGBA_ASTC_8x6: + case GL_COMPRESSED_RGBA_ASTC_8x8: + case GL_COMPRESSED_RGBA_ASTC_10x5: + case GL_COMPRESSED_RGBA_ASTC_10x6: + case GL_COMPRESSED_RGBA_ASTC_10x8: + case GL_COMPRESSED_RGBA_ASTC_10x10: + case GL_COMPRESSED_RGBA_ASTC_12x10: + case GL_COMPRESSED_RGBA_ASTC_12x12: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD: + case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD: + return ReadCompressedTexture(imports, kind, texID, w, h, GL_RGBA8, + {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}); + /* compressed 8bit SRGB */ + case GL_COMPRESSED_SRGB8_ETC2: + return ReadCompressedTexture(imports, kind, texID, w, h, GL_SRGB8, + {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}); + /* compressed 8bit SRGBA */ + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + return ReadCompressedTexture(imports, kind, texID, w, h, GL_SRGB8_ALPHA8, + {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}); + /* compressed 11bit R - Half floats have 11bit mantissa. */ + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + return ReadCompressedTexture(imports, kind, texID, w, h, GL_R16F, + {GL_RED, GL_ZERO, GL_ZERO, GL_ONE}); + /* compressed 11 bit RG - Half floats have 11bit mantissa. */ + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + return ReadCompressedTexture(imports, kind, texID, w, h, GL_RG16F, + {GL_RED, GL_GREEN, GL_ZERO, GL_ONE}); + /* formats that can be used as render targets */ + default: { + auto readFb = CreateAndBindFramebuffer(imports, GL_FRAMEBUFFER); + if (kind == GL_TEXTURE_CUBE_MAP) { + uint32_t face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (layer % 6); + imports.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + face, texID, level); + } else if (layer == 0) { + imports.glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texID, level); + } else { + imports.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texID, level, layer); + } + return ReadPixels(imports, w, h); + } + } + return ImageData{}; +} + +ImageData ReadTextureViaDrawQuad(const GlesImports& imports, GLint texID, + GLsizei w, GLsizei h, uint32_t format, + swizzle_t swizzle) { + GAPID_DEBUG("MEC: Drawing quad to format 0x%x", format); + GLint err; + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Entered RTvDrawQuad in error state: 0x%X", err); + } + + auto drawFb = CreateAndBindFramebuffer(imports, GL_DRAW_FRAMEBUFFER); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to create framebuffer1: 0x%X", err); + } + auto tmpTex = CreateAndBindTexture2D(imports, w, h, format); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to create framebuffer2: 0x%X", err); + } + imports.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + tmpTex.id(), 0); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to create framebuffer3: 0x%X", err); + } + imports.glBindTexture(GL_TEXTURE_2D, texID); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to create framebuffer: 0x%X", err); + } + + GLint oldCompMode = 0; + imports.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, + &oldCompMode); + swizzle_t oldSwizzle; + imports.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, + &oldSwizzle.r); + imports.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, + &oldSwizzle.g); + imports.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, + &oldSwizzle.b); + imports.glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, + &oldSwizzle.a); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed querying texture state: 0x%X", err); + } + + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swizzle.r); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, swizzle.g); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swizzle.b); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, swizzle.a); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed setting texture state: 0x%X", err); + } + + DrawTexturedQuad(imports, GL_TEXTURE_2D, w, h); + + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, oldCompMode); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, oldSwizzle.r); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, oldSwizzle.g); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, oldSwizzle.b); + imports.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, oldSwizzle.a); + if ((err = imports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed restoring texture state: 0x%X", err); + } + return ReadTexture(imports, GL_TEXTURE_2D, tmpTex.id(), 0, 0, w, h, format); +} + +ImageData ReadRenderbuffer(const GlesImports& imports, Renderbuffer* rb) { + auto img = rb->mImage; + auto w = img->mWidth; + auto h = img->mHeight; + auto format = img->mSizedFormat; + uint32_t attachment = GL_COLOR_ATTACHMENT0; + switch (img->mDataFormat) { + case GL_DEPTH_COMPONENT: + attachment = GL_DEPTH_ATTACHMENT; + break; + case GL_DEPTH_STENCIL: + attachment = GL_DEPTH_STENCIL_ATTACHMENT; + break; + case GL_STENCIL: + attachment = GL_STENCIL_ATTACHMENT; + break; + } + GAPID_DEBUG( + "MEC: Reading renderbuffer %d format 0x%x type 0x%x sized 0x%x " + "attachment 0x%x", + rb->mID, img->mDataFormat, img->mDataType, format, attachment); + if (attachment == GL_COLOR_ATTACHMENT0) { + auto readFb = CreateAndBindFramebuffer(imports, GL_READ_FRAMEBUFFER); + imports.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, rb->mID); + return ReadPixels(imports, w, h); + } else { + // Copy the renderbuffer data to temporary texture and then use the texture + // reading path. + auto readFb = CreateAndBindFramebuffer(imports, GL_READ_FRAMEBUFFER); + auto drawFb = CreateAndBindFramebuffer(imports, GL_DRAW_FRAMEBUFFER); + auto tmpTex = CreateAndBindTexture2D(imports, w, h, format); + imports.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, attachment, + GL_RENDERBUFFER, rb->mID); + imports.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, attachment, tmpTex.id(), + 0); + uint32_t mask = + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + imports.glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, mask, GL_NEAREST); + return ReadTexture(imports, GL_TEXTURE_2D, tmpTex.id(), 0, 0, w, h, format); + } +} + +ImageData ReadExternal(const GlesImports& imports, EGLImageKHR handle, + GLsizei w, GLsizei h) { + GAPID_DEBUG("MEC: Reading external texture 0x%p", handle); + auto extTex = CreateAndBindTextureExternal(imports, handle); + auto tmpTex = CreateAndBindTexture2D(imports, w, h, GL_RGBA8); + auto fb = CreateAndBindFramebuffer(imports, GL_FRAMEBUFFER); + imports.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tmpTex.id(), 0); + DrawTexturedQuad(imports, GL_TEXTURE_EXTERNAL_OES, w, h); + return ReadPixels(imports, w, h); +} + +void SerializeAndUpdate(StateSerializer* serializer, + gapil::Ref current, + const ImageData& read) { + if (read.data) { + current->mData = serializer->encodeBuffer( + read.data->size(), [serializer, &read](memory::Observation* obs) { + serializer->sendData(obs, false, read.data->data(), + read.data->size()); + }); + current->mDataFormat = read.dataFormat; + current->mDataType = read.dataType; + if (read.sizedFormat) { + current->mSizedFormat = read.sizedFormat; + } + } +} + +} // anonymous namespace + namespace gapii { +using namespace GLenum; +using namespace GLbitfield; +using namespace EGLenum; + +void GlesSpy::GetEGLImageData(CallObserver* observer, EGLImageKHR handle, + GLsizei width, GLsizei height) { + if (!should_trace(kApiIndex)) { + return; + } + + GAPID_DEBUG("MEC: Get EGLImage data: 0x%p x%xx%x", handle, width, height); + auto tmpCtx = CreateAndBindContext(mImports, nullptr, 2); + if (tmpCtx.id() == EGL_NO_CONTEXT) { + return; + } + + auto img = ReadExternal(mImports, handle, width, height); + + if (!img.data->empty()) { + auto resIndex = sendResource(kApiIndex, img.data->data(), img.data->size()); + auto extra = new gles_pb::EGLImageData(); + extra->set_res_index(resIndex); + extra->set_size(img.data->size()); + extra->set_width(width); + extra->set_height(height); + extra->set_format(img.dataFormat); + extra->set_type(img.dataType); + observer->encodeAndDelete(extra); + } +} void GlesSpy::serializeGPUBuffers(StateSerializer* serializer) { - // TODO + // Ensure we process shared objects only once. + std::unordered_set seen; + auto once = [&](const void* ptr) { return seen.emplace(ptr).second; }; + + for (auto& it : mState.EGLContexts) { + auto handle = it.first; + auto ctx = it.second; + if (ctx->mOther.mDestroyed) { + continue; + } + GAPID_DEBUG("MEC: processing context %d thread %s", ctx->mIdentifier, + ctx->mOther.mThreadName.c_str()); + + auto tmpCtx = CreateAndBindContext(mImports, handle, 3); + if (tmpCtx.id() == EGL_NO_CONTEXT) { + continue; + } + + if (once(ctx->mObjects.mRenderbuffers.instance_ptr())) { + for (auto& it : ctx->mObjects.mRenderbuffers) { + auto rb = it.second; + auto img = rb->mImage; + if (img != nullptr) { + auto newImg = ReadRenderbuffer(mImports, rb.get()); + SerializeAndUpdate(serializer, img, newImg); + } + } + } + if (once(ctx->mObjects.mTextures.instance_ptr())) { + for (auto& it : ctx->mObjects.mTextures) { + auto tex = it.second; + auto eglImage = tex->mEGLImage.get(); + if (eglImage != nullptr) { + if (once(eglImage)) { + for (auto& it : eglImage->mImages) { + auto img = it.second; + auto newImg = ReadExternal(mImports, eglImage->mID, img->mWidth, + img->mHeight); + SerializeAndUpdate(serializer, img, newImg); + } + } + } else { + for (auto it : tex->mLevels) { + auto level = it.first; + for (auto it2 : it.second.mLayers) { + auto layer = it2.first; + auto img = it2.second; + if (img->mSamples != 0) { + GAPID_WARNING( + "MEC: Reading of multisample textures is not yet " + "supported"); // TODO + continue; + } + auto newImg = + ReadTexture(mImports, tex->mKind, tex->mID, level, layer, + img->mWidth, img->mHeight, img->mSizedFormat); + SerializeAndUpdate(serializer, img, newImg); + GLint err; + if ((err = mImports.glGetError()) != 0) { + GAPID_FATAL("MEC: Failed to read texture %d: 0x%X", tex->mID, + err); + } + } + } + } + } + } + /* TODO: read buffers from GPU. Currently disabled due to buffer data + being required by draw calls. Need to be able to determine, + which buffers have been written to by the GPU. + if (once(ctx->mObjects.mBuffers.instance_ptr())) { + for (auto& it : ctx->mObjects.mBuffers) { + auto buffer = it.second; + size_t size = buffer->mSize; + if (size == 0) { + continue; + } + GAPID_DEBUG("MEC: Reading buffer %d size %zu", buffer->mID, size); + + void* data; + const uint32_t target = GL_ARRAY_BUFFER; + if (buffer->mMapped) { + if (buffer->mMapOffset != 0 || + static_cast(buffer->mMapLength) != size) { + // TODO: Implement - We can not unmap and remap since the + // application has the existing pointer, and we can't copy the + // buffer since copy is not allowed for mapped buffers. + // Proposed solution: change glMapBuffer* to always map the whole + // buffer, and return pointer inside that buffer to the user. + GAPID_WARNING("MEC: Can not read partially mapped buffer") + continue; + } + GAPID_DEBUG("MEC: buffer is application mapped"); + data = buffer->mMapPointer; + } else { + mImports.glBindBuffer(target, buffer->mID); + data = mImports.glMapBufferRange(target, 0, size, GL_MAP_READ_BIT); + } + buffer->mData = serializer->encodeBuffer( + size, [=](memory::Observation* obs) { + serializer->sendData(obs, false, data, size); + }); + if (!buffer->mMapped) { + mImports.glUnmapBuffer(target); + } + } + } + */ + } + + GAPID_DEBUG("MEC: done"); } } // namespace gapii diff --git a/gapis/api/gles/api/buffer_objects.api b/gapis/api/gles/api/buffer_objects.api index 94f02dc0a0..b62b80caec 100644 --- a/gapis/api/gles/api/buffer_objects.api +++ b/gapis/api/gles/api/buffer_objects.api @@ -267,9 +267,9 @@ cmd void glBufferData(GLenum target, GLsizeiptr size, BufferDataPointer data, GL } CheckSizeGE!GLsizeiptr(size, 0) - b.Data = switch (data != null) { - case true: clone(as!u8*(data)[0:size]) - case false: make!u8(size) + b.Data = make!u8(size) + if (data != null) { + copy(b.Data, as!u8*(data)[0:size]) } b.Size = size b.Usage = usage @@ -483,7 +483,7 @@ sub void MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr size, GLbitfi mapMemory(ptr[0:size]) if GL_MAP_READ_BIT in access { - copy(ptr[0:size], b.Data[offset:offset + as!GLintptr(size)]) + write(ptr[0:size]) } } diff --git a/gapis/api/gles/api/textures_and_samplers.api b/gapis/api/gles/api/textures_and_samplers.api index bc4a86098f..5eb8518dbf 100644 --- a/gapis/api/gles/api/textures_and_samplers.api +++ b/gapis/api/gles/api/textures_and_samplers.api @@ -113,7 +113,7 @@ class Image { // This is unrelated to the format of the data passed to the API. @unused GLenum SizedFormat - @internal u8[] Data + @internal @spy_disabled u8[] Data // Tuple of (format, type) describing the Data field above. // This describes the format of the data the user passed in, // not the format of the data stored on the GPU (sizedFormat). @@ -459,7 +459,8 @@ sub void TexImage(TexImageFlags flags, } else if isCompressedTexImageCmd { if (ctx.Bound.PixelUnpackBuffer == null) && (data != null) { // TODO: Implement sub-range updates - img.Data = clone(as!u8*(data)[0:data_size]) + img.Data = make!u8(data_size) + copy(img.Data, as!u8*(data)[0:data_size]) img.DataFormat = data_format } else { // TODO: Implement copy from unpack buffer From 5efaeddab590fbb8342af7bd6ccf7f29588d8aa7 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Thu, 28 Jun 2018 11:55:56 -0700 Subject: [PATCH 6/7] Show a warning when doing MEC for GLES. --- .../com/google/gapid/views/TracerDialog.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/gapic/src/main/com/google/gapid/views/TracerDialog.java b/gapic/src/main/com/google/gapid/views/TracerDialog.java index f09de8e688..288ab83a86 100644 --- a/gapic/src/main/com/google/gapid/views/TracerDialog.java +++ b/gapic/src/main/com/google/gapid/views/TracerDialog.java @@ -257,6 +257,7 @@ private abstract static class SharedTraceInput extends Composite { private static final String DEFAULT_TRACE_FILE = "trace"; private static final String TRACE_EXTENSION = ".gfxtrace"; private static final DateFormat TRACE_DATE_FORMAT = new SimpleDateFormat("_yyyyMMdd_HHmm"); + protected static final String MEC_LABEL = "Trace From Beginning"; private final String date = TRACE_DATE_FORMAT.format(new Date()); protected final ComboViewer api; @@ -302,7 +303,7 @@ protected void configureDialog(DirectoryDialog dialog) { createLabel(this, ""); fromBeginning = withLayoutData( - createCheckbox(this, "Trace From Beginning", !models.settings.traceMidExecution), + createCheckbox(this, MEC_LABEL, !models.settings.traceMidExecution), new GridData(SWT.FILL, SWT.FILL, true, false)); createLabel(this, ""); @@ -377,6 +378,8 @@ private File getOutputFile() { } private static class AndroidInput extends SharedTraceInput { + private static final String MEC_WARNING = "(mid-execution capture for GLES is experimental)"; + private final Runnable refreshDevices; private ComboViewer device; private LoadingIndicator.Widget deviceLoader; @@ -440,6 +443,17 @@ public AndroidInput( traceTarget.addBoxListener(SWT.Modify, targetListener); targetListener.handleEvent(null); + Listener mecListener = e -> { + if (getSelectedApi() == Tracer.Api.Vulkan || fromBeginning.getSelection()) { + fromBeginning.setText(MEC_LABEL); + } else { + fromBeginning.setText(MEC_LABEL + " " + MEC_WARNING); + } + }; + api.getCombo().addListener(SWT.Selection, mecListener); + fromBeginning.addListener(SWT.Selection, mecListener); + mecListener.handleEvent(null); + disablePcs.addListener( SWT.Selection, e -> pcsWarning.setVisible(!disablePcs.getSelection())); } From e3b4bc39f0b43c4f66a571fb1e8eea30661fe0a2 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Thu, 28 Jun 2018 21:27:22 -0700 Subject: [PATCH 7/7] Rebuild programs with shader source from their link state. Use the shaders and their source as they were when the program was linked, not using the program state on serialization. Shaders can be detached, have their source change, and even deleted, while the program that they were used to link stays unaffected. --- gapis/api/gles/state_builder.go | 45 ++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/gapis/api/gles/state_builder.go b/gapis/api/gles/state_builder.go index 7c02f0e48e..57dc13141d 100644 --- a/gapis/api/gles/state_builder.go +++ b/gapis/api/gles/state_builder.go @@ -276,9 +276,16 @@ func (sb *stateBuilder) contextObject(ctx context.Context, handle EGLContext, c } } if objs := c.Objects().Programs(); sb.once(objs) { + // Get the largest used shader ID. + maxID := ShaderId(0) + for i := range c.Objects().Shaders().All() { + if i > maxID { + maxID = i + } + } for _, id := range objs.Keys() { if o := c.Objects().Programs().Get(id); !o.IsNil() { - sb.programObject(ctx, o) + sb.programObject(ctx, o, uint32(maxID)+1) } } } @@ -625,15 +632,10 @@ func (sb *stateBuilder) shaderObject(ctx context.Context, s Shaderʳ) { write(cb.GlShaderSource(id, 1, sb.readsData(ctx, sb.readsData(ctx, s.Source())), memory.Nullptr)) } -func (sb *stateBuilder) programObject(ctx context.Context, p Programʳ) { +func (sb *stateBuilder) programObject(ctx context.Context, p Programʳ, firstShaderID uint32) { write, cb, id := sb.write, sb.cb, p.GetID() write(cb.GlCreateProgram(id)) - for _, s := range p.Shaders().All() { - if s := s.GetID(); s != 0 { - write(cb.GlAttachShader(id, s)) - } - } for name, location := range p.AttributeBindings().All() { write(cb.GlBindAttribLocation(id, location, sb.readsData(ctx, name))) } @@ -658,6 +660,23 @@ func (sb *stateBuilder) programObject(ctx context.Context, p Programʳ) { if !p.LinkExtra().Binary().IsNil() { sb.E(ctx, "Precompiled programs not suppored yet") // TODO } + + // Create the shaders from the extra. + attachedShaders := []ShaderId{} + for t, s := range p.LinkExtra().Shaders().All() { + if !s.Binary().IsNil() { + sb.E(ctx, "Precompiled programs not suppored yet") // TODO + continue + } + shaderID := ShaderId(firstShaderID) + firstShaderID++ + write(cb.GlCreateShader(t, shaderID)) + write(cb.GlShaderSource(shaderID, 1, sb.readsData(ctx, sb.readsData(ctx, s.Source())), memory.Nullptr)) + write(api.WithExtras(cb.GlCompileShader(shaderID), s.Get().Clone(cb.Arena, sb.cloneCtx))) + write(cb.GlAttachShader(id, shaderID)) + attachedShaders = append(attachedShaders, shaderID) + } + write(api.WithExtras(cb.GlLinkProgram(id), p.LinkExtra().Get().Clone(cb.Arena, sb.cloneCtx))) write(cb.GlUseProgram(id)) for _, u := range p.ActiveResources().DefaultUniformBlock().All() { @@ -666,10 +685,22 @@ func (sb *stateBuilder) programObject(ctx context.Context, p Programʳ) { } } write(cb.GlUseProgram(0)) + + // Detach and delete the linked shaders (we'll attach the shaders from the state below). + for _, shaderID := range attachedShaders { + write(cb.GlDetachShader(id, shaderID)) + write(cb.GlDeleteShader(shaderID)) + } } if !p.ValidateExtra().IsNil() { write(api.WithExtras(cb.GlValidateProgram(id), p.ValidateExtra().Get().Clone(cb.Arena, sb.cloneCtx))) } + + for _, s := range p.Shaders().All() { + if s := s.GetID(); s != 0 { + write(cb.GlAttachShader(id, s)) + } + } } func (sb *stateBuilder) uniform(ctx context.Context, ty GLenum, loc UniformLocation, n GLsizei, v memory.Pointer) {