diff --git a/gapic/src/main/com/google/gapid/views/TracerDialog.java b/gapic/src/main/com/google/gapid/views/TracerDialog.java index 9fee1a6d6d..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,16 +443,16 @@ public AndroidInput( traceTarget.addBoxListener(SWT.Modify, targetListener); targetListener.handleEvent(null); - Listener apiListener = e -> { - if (getSelectedApi() == Tracer.Api.Vulkan) { - fromBeginning.setEnabled(true); + Listener mecListener = e -> { + if (getSelectedApi() == Tracer.Api.Vulkan || fromBeginning.getSelection()) { + fromBeginning.setText(MEC_LABEL); } else { - fromBeginning.setEnabled(false); - fromBeginning.setSelection(true); + fromBeginning.setText(MEC_LABEL + " " + MEC_WARNING); } }; - api.getCombo().addListener(SWT.Selection, apiListener); - apiListener.handleEvent(null); + api.getCombo().addListener(SWT.Selection, mecListener); + fromBeginning.addListener(SWT.Selection, mecListener); + mecListener.handleEvent(null); disablePcs.addListener( SWT.Selection, e -> pcsWarning.setVisible(!disablePcs.getSelection())); 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/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 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 diff --git a/gapis/api/gles/state_builder.go b/gapis/api/gles/state_builder.go index e0b9fc3530..57dc13141d 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() @@ -63,6 +65,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) @@ -186,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 @@ -196,11 +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())) - - if !c.Other().Initialized() { - return - } + sb.contextExtras(c)...)) write(cb.GlPixelStorei(GLenum_GL_UNPACK_ALIGNMENT, 1)) @@ -262,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) } } } @@ -358,7 +379,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() @@ -431,7 +452,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))) @@ -606,20 +627,15 @@ 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)) } -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))) } @@ -644,7 +660,24 @@ 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())) + + // 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() { if loc, ok := u.Locations().Lookup(0); ok { @@ -652,9 +685,21 @@ 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())) + 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)) + } } } @@ -725,7 +770,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)) }