mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Chore] (MG_Backend, MG_Backend/DirectGLES, MG_Backend/DirectVulkan, MG_Backend/MGPipe, MG_Backend/Record, MG_Benchmark/Buffer, MG_Benchmark/Container, MG_Benchmark/Driver, MG_Benchmark/Program, MG_Benchmark/ShaderCache, MG_Benchmark/Transpile, MG_Gbm, MG_Impl, MG_Impl/CGLImpl, MG_Impl/DyldInterpose, MG_Impl/EGLImpl, MG_Impl/GLImpl, MG_Impl/GLXImpl, MG_Impl/NSOpenGLImpl, MG_Impl/Pipe, MG_Impl/WGLImpl, MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, MG_Pipe, MG_Remote, MG_Remote/Client, MG_Remote/Protocol, MG_Remote/Server, MG_Remote/Transport, MG_Remote/Wire, MG_State/EGLState, MG_State/GLState, MG_Test, MG_Test/Backend, MG_Test/BackendLoader, MG_Test/Buffer, MG_Test/EGLState, MG_Test/Framebuffer, MG_Test/Gbm, MG_Test/Pipe, MG_Test/Pipeline, MG_Test/Program, MG_Test/Query, MG_Test/SelfTest, MG_Test/ShaderTranspiler, MG_Test/State, MG_Test/Swapchain, MG_Test/Texture, MG_Test/Util, MG_Test/VertexArray, MG_Test/Wire, MG_Util/Async, MG_Util/BackendLoaders, MG_Util/Converters, MG_Util/Damage, MG_Util/Debug, MG_Util/Math, MG_Util/Metrics, MG_Util/SelfTest, MG_Util/ShaderTranspiler, MG_Util/Texture, MG_Util/X11, MobileGL, scripts/data, tools/cts, tools/device_bench, tools/spikes, tools/trace_replay, tools/wgl-smoke): clang-format every C and C++ source
- Layout only: token streams match up to split literals, continuations and using order - The size totals fall, so the structure baseline is lowered
This commit is contained in:
@@ -82,36 +82,31 @@ using std::vector;
|
||||
#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004
|
||||
#endif
|
||||
|
||||
namespace tcu
|
||||
{
|
||||
namespace mobilegl
|
||||
{
|
||||
namespace tcu {
|
||||
namespace mobilegl {
|
||||
|
||||
static string getLibraryName(void)
|
||||
{
|
||||
const char *env = std::getenv("MOBILEGL_CTS_LIB");
|
||||
return (env && env[0]) ? string(env) : string("libMobileGL.so");
|
||||
}
|
||||
static string getLibraryName(void) {
|
||||
const char* env = std::getenv("MOBILEGL_CTS_LIB");
|
||||
return (env && env[0]) ? string(env) : string("libMobileGL.so");
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
//! Window surfaces default on: they are the only kind DirectVulkan can use
|
||||
//! on Android (its pbuffer path needs VK_EXT_headless_surface).
|
||||
static bool useWindowSurface(void)
|
||||
{
|
||||
const char *env = std::getenv("MOBILEGL_CTS_SURFACE");
|
||||
return !(env && string(env) == "pbuffer");
|
||||
}
|
||||
//! Window surfaces default on: they are the only kind DirectVulkan can use
|
||||
//! on Android (its pbuffer path needs VK_EXT_headless_surface).
|
||||
static bool useWindowSurface(void) {
|
||||
const char* env = std::getenv("MOBILEGL_CTS_SURFACE");
|
||||
return !(env && string(env) == "pbuffer");
|
||||
}
|
||||
#else
|
||||
//! Desktop: pbuffer only. VK_EXT_headless_surface is available there and no
|
||||
//! Activity-free native window abstraction exists.
|
||||
static bool useWindowSurface(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//! Desktop: pbuffer only. VK_EXT_headless_surface is available there and no
|
||||
//! Activity-free native window abstraction exists.
|
||||
static bool useWindowSurface(void) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
/*--------------------------------------------------------------------*//*!
|
||||
/*--------------------------------------------------------------------*//*!
|
||||
* \brief A real ANativeWindow with no Activity behind it.
|
||||
*
|
||||
* AImageReader's window is an ordinary BufferQueue producer, so both
|
||||
@@ -119,381 +114,305 @@ static bool useWindowSurface(void)
|
||||
* listener must drain the queue: without it the producer blocks once maxImages
|
||||
* buffers are in flight and the next swap deadlocks.
|
||||
*//*--------------------------------------------------------------------*/
|
||||
class ImageReaderWindow
|
||||
{
|
||||
public:
|
||||
ImageReaderWindow(int width, int height) : m_reader(nullptr), m_window(nullptr)
|
||||
{
|
||||
const media_status_t status =
|
||||
AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888,
|
||||
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
|
||||
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,
|
||||
kMaxImages, &m_reader);
|
||||
if (status != AMEDIA_OK || m_reader == nullptr)
|
||||
throw tcu::ResourceError("AImageReader_newWithUsage() failed");
|
||||
class ImageReaderWindow {
|
||||
public:
|
||||
ImageReaderWindow(int width, int height) : m_reader(nullptr), m_window(nullptr) {
|
||||
const media_status_t status = AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888,
|
||||
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
|
||||
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,
|
||||
kMaxImages, &m_reader);
|
||||
if (status != AMEDIA_OK || m_reader == nullptr)
|
||||
throw tcu::ResourceError("AImageReader_newWithUsage() failed");
|
||||
|
||||
AImageReader_ImageListener listener = {this, onImageAvailable};
|
||||
AImageReader_setImageListener(m_reader, &listener);
|
||||
AImageReader_ImageListener listener = {this, onImageAvailable};
|
||||
AImageReader_setImageListener(m_reader, &listener);
|
||||
|
||||
if (AImageReader_getWindow(m_reader, &m_window) != AMEDIA_OK || m_window == nullptr)
|
||||
{
|
||||
AImageReader_delete(m_reader);
|
||||
m_reader = nullptr;
|
||||
throw tcu::ResourceError("AImageReader_getWindow() failed");
|
||||
}
|
||||
ANativeWindow_acquire(m_window);
|
||||
}
|
||||
if (AImageReader_getWindow(m_reader, &m_window) != AMEDIA_OK || m_window == nullptr) {
|
||||
AImageReader_delete(m_reader);
|
||||
m_reader = nullptr;
|
||||
throw tcu::ResourceError("AImageReader_getWindow() failed");
|
||||
}
|
||||
ANativeWindow_acquire(m_window);
|
||||
}
|
||||
|
||||
~ImageReaderWindow(void)
|
||||
{
|
||||
if (m_window != nullptr)
|
||||
ANativeWindow_release(m_window);
|
||||
if (m_reader != nullptr)
|
||||
{
|
||||
AImageReader_setImageListener(m_reader, nullptr);
|
||||
AImageReader_delete(m_reader);
|
||||
}
|
||||
}
|
||||
~ImageReaderWindow(void) {
|
||||
if (m_window != nullptr) ANativeWindow_release(m_window);
|
||||
if (m_reader != nullptr) {
|
||||
AImageReader_setImageListener(m_reader, nullptr);
|
||||
AImageReader_delete(m_reader);
|
||||
}
|
||||
}
|
||||
|
||||
ANativeWindow *getWindow(void) const
|
||||
{
|
||||
return m_window;
|
||||
}
|
||||
ANativeWindow* getWindow(void) const { return m_window; }
|
||||
|
||||
private:
|
||||
static const int kMaxImages = 4;
|
||||
private:
|
||||
static const int kMaxImages = 4;
|
||||
|
||||
static void onImageAvailable(void *, AImageReader *reader)
|
||||
{
|
||||
AImage *image = nullptr;
|
||||
if (AImageReader_acquireNextImage(reader, &image) == AMEDIA_OK && image != nullptr)
|
||||
AImage_delete(image);
|
||||
}
|
||||
static void onImageAvailable(void*, AImageReader* reader) {
|
||||
AImage* image = nullptr;
|
||||
if (AImageReader_acquireNextImage(reader, &image) == AMEDIA_OK && image != nullptr)
|
||||
AImage_delete(image);
|
||||
}
|
||||
|
||||
ImageReaderWindow(const ImageReaderWindow &);
|
||||
ImageReaderWindow &operator=(const ImageReaderWindow &);
|
||||
ImageReaderWindow(const ImageReaderWindow&);
|
||||
ImageReaderWindow& operator=(const ImageReaderWindow&);
|
||||
|
||||
AImageReader *m_reader;
|
||||
ANativeWindow *m_window;
|
||||
};
|
||||
AImageReader* m_reader;
|
||||
ANativeWindow* m_window;
|
||||
};
|
||||
#else
|
||||
//! Never instantiated on desktop; keeps EglRenderContext's member deletable.
|
||||
class ImageReaderWindow
|
||||
{
|
||||
};
|
||||
//! Never instantiated on desktop; keeps EglRenderContext's member deletable.
|
||||
class ImageReaderWindow {};
|
||||
#endif
|
||||
|
||||
class GetProcFuncLoader : public glw::FunctionLoader
|
||||
{
|
||||
public:
|
||||
GetProcFuncLoader(const eglw::Library &egl) : m_egl(egl)
|
||||
{
|
||||
}
|
||||
class GetProcFuncLoader : public glw::FunctionLoader {
|
||||
public:
|
||||
GetProcFuncLoader(const eglw::Library& egl) : m_egl(egl) {}
|
||||
|
||||
glw::GenericFuncType get(const char *name) const
|
||||
{
|
||||
return (glw::GenericFuncType)m_egl.getProcAddress(name);
|
||||
}
|
||||
glw::GenericFuncType get(const char* name) const {
|
||||
return (glw::GenericFuncType)m_egl.getProcAddress(name);
|
||||
}
|
||||
|
||||
protected:
|
||||
const eglw::Library &m_egl;
|
||||
};
|
||||
protected:
|
||||
const eglw::Library& m_egl;
|
||||
};
|
||||
|
||||
class EglRenderContext : public glu::RenderContext
|
||||
{
|
||||
public:
|
||||
EglRenderContext(const glu::RenderConfig &config, const tcu::CommandLine &cmdLine,
|
||||
const glu::RenderContext *sharedContext);
|
||||
~EglRenderContext(void);
|
||||
class EglRenderContext : public glu::RenderContext {
|
||||
public:
|
||||
EglRenderContext(const glu::RenderConfig& config, const tcu::CommandLine& cmdLine,
|
||||
const glu::RenderContext* sharedContext);
|
||||
~EglRenderContext(void);
|
||||
|
||||
glu::ContextType getType(void) const
|
||||
{
|
||||
return m_contextType;
|
||||
}
|
||||
eglw::EGLContext getEglContext(void) const
|
||||
{
|
||||
return m_eglContext;
|
||||
}
|
||||
const glw::Functions &getFunctions(void) const
|
||||
{
|
||||
return m_glFunctions;
|
||||
}
|
||||
const tcu::RenderTarget &getRenderTarget(void) const
|
||||
{
|
||||
return m_renderTarget;
|
||||
}
|
||||
void postIterate(void);
|
||||
void makeCurrent(void);
|
||||
glu::ContextType getType(void) const { return m_contextType; }
|
||||
eglw::EGLContext getEglContext(void) const { return m_eglContext; }
|
||||
const glw::Functions& getFunctions(void) const { return m_glFunctions; }
|
||||
const tcu::RenderTarget& getRenderTarget(void) const { return m_renderTarget; }
|
||||
void postIterate(void);
|
||||
void makeCurrent(void);
|
||||
|
||||
glw::GenericFuncType getProcAddress(const char *name) const
|
||||
{
|
||||
return (glw::GenericFuncType)m_egl.getProcAddress(name);
|
||||
}
|
||||
glw::GenericFuncType getProcAddress(const char* name) const {
|
||||
return (glw::GenericFuncType)m_egl.getProcAddress(name);
|
||||
}
|
||||
|
||||
private:
|
||||
const eglw::DefaultLibrary m_egl;
|
||||
const glu::ContextType m_contextType;
|
||||
eglw::EGLDisplay m_eglDisplay;
|
||||
eglw::EGLContext m_eglContext;
|
||||
eglw::EGLSurface m_eglSurface;
|
||||
ImageReaderWindow *m_window;
|
||||
glw::Functions m_glFunctions;
|
||||
tcu::RenderTarget m_renderTarget;
|
||||
eglw::EGLContext m_sharedEglContext;
|
||||
};
|
||||
private:
|
||||
const eglw::DefaultLibrary m_egl;
|
||||
const glu::ContextType m_contextType;
|
||||
eglw::EGLDisplay m_eglDisplay;
|
||||
eglw::EGLContext m_eglContext;
|
||||
eglw::EGLSurface m_eglSurface;
|
||||
ImageReaderWindow* m_window;
|
||||
glw::Functions m_glFunctions;
|
||||
tcu::RenderTarget m_renderTarget;
|
||||
eglw::EGLContext m_sharedEglContext;
|
||||
};
|
||||
|
||||
class ContextFactory : public glu::ContextFactory
|
||||
{
|
||||
public:
|
||||
ContextFactory(void) : glu::ContextFactory("default", "MobileGL EGL context")
|
||||
{
|
||||
}
|
||||
class ContextFactory : public glu::ContextFactory {
|
||||
public:
|
||||
ContextFactory(void) : glu::ContextFactory("default", "MobileGL EGL context") {}
|
||||
|
||||
glu::RenderContext *createContext(const glu::RenderConfig &config, const tcu::CommandLine &cmdLine,
|
||||
const glu::RenderContext *sharedContext) const
|
||||
{
|
||||
return new EglRenderContext(config, cmdLine, sharedContext);
|
||||
}
|
||||
};
|
||||
glu::RenderContext* createContext(const glu::RenderConfig& config, const tcu::CommandLine& cmdLine,
|
||||
const glu::RenderContext* sharedContext) const {
|
||||
return new EglRenderContext(config, cmdLine, sharedContext);
|
||||
}
|
||||
};
|
||||
|
||||
class Platform : public tcu::Platform, public glu::Platform
|
||||
{
|
||||
public:
|
||||
Platform(void)
|
||||
{
|
||||
m_contextFactoryRegistry.registerFactory(new ContextFactory());
|
||||
}
|
||||
class Platform : public tcu::Platform, public glu::Platform {
|
||||
public:
|
||||
Platform(void) { m_contextFactoryRegistry.registerFactory(new ContextFactory()); }
|
||||
|
||||
const glu::Platform &getGLPlatform(void) const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
const glu::Platform& getGLPlatform(void) const { return *this; }
|
||||
};
|
||||
|
||||
EglRenderContext::EglRenderContext(const glu::RenderConfig &config, const tcu::CommandLine &cmdLine,
|
||||
const glu::RenderContext *sharedContext)
|
||||
: m_egl(getLibraryName().c_str())
|
||||
, m_contextType(config.type)
|
||||
, m_eglDisplay(EGL_NO_DISPLAY)
|
||||
, m_eglContext(EGL_NO_CONTEXT)
|
||||
, m_eglSurface(EGL_NO_SURFACE)
|
||||
, m_window(nullptr)
|
||||
, m_renderTarget(config.width, config.height,
|
||||
tcu::PixelFormat(config.redBits, config.greenBits, config.blueBits, config.alphaBits),
|
||||
config.depthBits, config.stencilBits, config.numSamples)
|
||||
, m_sharedEglContext(EGL_NO_CONTEXT)
|
||||
{
|
||||
DE_UNREF(cmdLine);
|
||||
EglRenderContext::EglRenderContext(const glu::RenderConfig& config, const tcu::CommandLine& cmdLine,
|
||||
const glu::RenderContext* sharedContext)
|
||||
: m_egl(getLibraryName().c_str()), m_contextType(config.type), m_eglDisplay(EGL_NO_DISPLAY),
|
||||
m_eglContext(EGL_NO_CONTEXT), m_eglSurface(EGL_NO_SURFACE), m_window(nullptr),
|
||||
m_renderTarget(config.width, config.height,
|
||||
tcu::PixelFormat(config.redBits, config.greenBits, config.blueBits, config.alphaBits),
|
||||
config.depthBits, config.stencilBits, config.numSamples),
|
||||
m_sharedEglContext(EGL_NO_CONTEXT) {
|
||||
DE_UNREF(cmdLine);
|
||||
|
||||
const glu::ContextType &contextType = config.type;
|
||||
const bool isES = glu::isContextTypeES(contextType);
|
||||
eglw::EGLint eglMajorVersion = 0;
|
||||
eglw::EGLint eglMinorVersion = 0;
|
||||
const glu::ContextType& contextType = config.type;
|
||||
const bool isES = glu::isContextTypeES(contextType);
|
||||
eglw::EGLint eglMajorVersion = 0;
|
||||
eglw::EGLint eglMinorVersion = 0;
|
||||
|
||||
m_eglDisplay = m_egl.getDisplay(EGL_DEFAULT_DISPLAY);
|
||||
EGLU_CHECK_MSG(m_egl, "eglGetDisplay()");
|
||||
if (m_eglDisplay == EGL_NO_DISPLAY)
|
||||
throw tcu::ResourceError("eglGetDisplay() failed");
|
||||
m_eglDisplay = m_egl.getDisplay(EGL_DEFAULT_DISPLAY);
|
||||
EGLU_CHECK_MSG(m_egl, "eglGetDisplay()");
|
||||
if (m_eglDisplay == EGL_NO_DISPLAY) throw tcu::ResourceError("eglGetDisplay() failed");
|
||||
|
||||
EGLU_CHECK_CALL(m_egl, initialize(m_eglDisplay, &eglMajorVersion, &eglMinorVersion));
|
||||
EGLU_CHECK_CALL(m_egl, initialize(m_eglDisplay, &eglMajorVersion, &eglMinorVersion));
|
||||
|
||||
// MobileGL cannot make a context current without a surface, so
|
||||
// SURFACETYPE_DONT_CARE (which is what --deqp-surface-type=fbo requests)
|
||||
// still gets a real one.
|
||||
bool wantWindow = false;
|
||||
switch (config.surfaceType)
|
||||
{
|
||||
case glu::RenderConfig::SURFACETYPE_WINDOW:
|
||||
wantWindow = true;
|
||||
break;
|
||||
case glu::RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE:
|
||||
case glu::RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC:
|
||||
wantWindow = false;
|
||||
break;
|
||||
case glu::RenderConfig::SURFACETYPE_DONT_CARE:
|
||||
wantWindow = useWindowSurface();
|
||||
break;
|
||||
default:
|
||||
TCU_CHECK_INTERNAL(false);
|
||||
}
|
||||
// MobileGL cannot make a context current without a surface, so
|
||||
// SURFACETYPE_DONT_CARE (which is what --deqp-surface-type=fbo requests)
|
||||
// still gets a real one.
|
||||
bool wantWindow = false;
|
||||
switch (config.surfaceType) {
|
||||
case glu::RenderConfig::SURFACETYPE_WINDOW:
|
||||
wantWindow = true;
|
||||
break;
|
||||
case glu::RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE:
|
||||
case glu::RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC:
|
||||
wantWindow = false;
|
||||
break;
|
||||
case glu::RenderConfig::SURFACETYPE_DONT_CARE:
|
||||
wantWindow = useWindowSurface();
|
||||
break;
|
||||
default:
|
||||
TCU_CHECK_INTERNAL(false);
|
||||
}
|
||||
|
||||
const int width = (config.width == glu::RenderConfig::DONT_CARE) ? 256 : config.width;
|
||||
const int height = (config.height == glu::RenderConfig::DONT_CARE) ? 256 : config.height;
|
||||
const int width = (config.width == glu::RenderConfig::DONT_CARE) ? 256 : config.width;
|
||||
const int height = (config.height == glu::RenderConfig::DONT_CARE) ? 256 : config.height;
|
||||
|
||||
vector<eglw::EGLint> cfgAttribs;
|
||||
cfgAttribs.push_back(EGL_RENDERABLE_TYPE);
|
||||
if (isES)
|
||||
{
|
||||
switch (contextType.getMajorVersion())
|
||||
{
|
||||
case 3:
|
||||
cfgAttribs.push_back(EGL_OPENGL_ES3_BIT);
|
||||
break;
|
||||
case 2:
|
||||
cfgAttribs.push_back(EGL_OPENGL_ES2_BIT);
|
||||
break;
|
||||
default:
|
||||
cfgAttribs.push_back(EGL_OPENGL_ES_BIT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Desktop GL, which is the whole point of this port.
|
||||
cfgAttribs.push_back(EGL_OPENGL_BIT);
|
||||
}
|
||||
vector<eglw::EGLint> cfgAttribs;
|
||||
cfgAttribs.push_back(EGL_RENDERABLE_TYPE);
|
||||
if (isES) {
|
||||
switch (contextType.getMajorVersion()) {
|
||||
case 3:
|
||||
cfgAttribs.push_back(EGL_OPENGL_ES3_BIT);
|
||||
break;
|
||||
case 2:
|
||||
cfgAttribs.push_back(EGL_OPENGL_ES2_BIT);
|
||||
break;
|
||||
default:
|
||||
cfgAttribs.push_back(EGL_OPENGL_ES_BIT);
|
||||
}
|
||||
} else {
|
||||
// Desktop GL, which is the whole point of this port.
|
||||
cfgAttribs.push_back(EGL_OPENGL_BIT);
|
||||
}
|
||||
|
||||
cfgAttribs.push_back(EGL_SURFACE_TYPE);
|
||||
cfgAttribs.push_back(wantWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT);
|
||||
cfgAttribs.push_back(EGL_SURFACE_TYPE);
|
||||
cfgAttribs.push_back(wantWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT);
|
||||
|
||||
static const struct
|
||||
{
|
||||
eglw::EGLint attrib;
|
||||
int glu::RenderConfig::*field;
|
||||
} s_sizeAttribs[] = {
|
||||
{EGL_RED_SIZE, &glu::RenderConfig::redBits}, {EGL_GREEN_SIZE, &glu::RenderConfig::greenBits},
|
||||
{EGL_BLUE_SIZE, &glu::RenderConfig::blueBits}, {EGL_ALPHA_SIZE, &glu::RenderConfig::alphaBits},
|
||||
{EGL_DEPTH_SIZE, &glu::RenderConfig::depthBits}, {EGL_STENCIL_SIZE, &glu::RenderConfig::stencilBits},
|
||||
{EGL_SAMPLES, &glu::RenderConfig::numSamples},
|
||||
};
|
||||
for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_sizeAttribs); ndx++)
|
||||
{
|
||||
const int value = config.*(s_sizeAttribs[ndx].field);
|
||||
if (value != glu::RenderConfig::DONT_CARE)
|
||||
{
|
||||
cfgAttribs.push_back(s_sizeAttribs[ndx].attrib);
|
||||
cfgAttribs.push_back(value);
|
||||
}
|
||||
}
|
||||
cfgAttribs.push_back(EGL_NONE);
|
||||
static const struct {
|
||||
eglw::EGLint attrib;
|
||||
int glu::RenderConfig::* field;
|
||||
} s_sizeAttribs[] = {
|
||||
{EGL_RED_SIZE, &glu::RenderConfig::redBits}, {EGL_GREEN_SIZE, &glu::RenderConfig::greenBits},
|
||||
{EGL_BLUE_SIZE, &glu::RenderConfig::blueBits}, {EGL_ALPHA_SIZE, &glu::RenderConfig::alphaBits},
|
||||
{EGL_DEPTH_SIZE, &glu::RenderConfig::depthBits}, {EGL_STENCIL_SIZE, &glu::RenderConfig::stencilBits},
|
||||
{EGL_SAMPLES, &glu::RenderConfig::numSamples},
|
||||
};
|
||||
for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_sizeAttribs); ndx++) {
|
||||
const int value = config.*(s_sizeAttribs[ndx].field);
|
||||
if (value != glu::RenderConfig::DONT_CARE) {
|
||||
cfgAttribs.push_back(s_sizeAttribs[ndx].attrib);
|
||||
cfgAttribs.push_back(value);
|
||||
}
|
||||
}
|
||||
cfgAttribs.push_back(EGL_NONE);
|
||||
|
||||
eglw::EGLConfig eglConfig = nullptr;
|
||||
eglw::EGLint numConfigs = 0;
|
||||
EGLU_CHECK_CALL(m_egl, chooseConfig(m_eglDisplay, &cfgAttribs[0], &eglConfig, 1, &numConfigs));
|
||||
if (numConfigs < 1)
|
||||
throw tcu::NotSupportedError("No matching EGL config for the requested context");
|
||||
eglw::EGLConfig eglConfig = nullptr;
|
||||
eglw::EGLint numConfigs = 0;
|
||||
EGLU_CHECK_CALL(m_egl, chooseConfig(m_eglDisplay, &cfgAttribs[0], &eglConfig, 1, &numConfigs));
|
||||
if (numConfigs < 1) throw tcu::NotSupportedError("No matching EGL config for the requested context");
|
||||
|
||||
if (wantWindow)
|
||||
{
|
||||
if (wantWindow) {
|
||||
#if defined(__ANDROID__)
|
||||
m_window = new ImageReaderWindow(width, height);
|
||||
m_window = new ImageReaderWindow(width, height);
|
||||
|
||||
eglw::EGLint visualId = 0;
|
||||
if (m_egl.getConfigAttrib(m_eglDisplay, eglConfig, EGL_NATIVE_VISUAL_ID, &visualId) && visualId != 0)
|
||||
ANativeWindow_setBuffersGeometry(m_window->getWindow(), width, height, visualId);
|
||||
eglw::EGLint visualId = 0;
|
||||
if (m_egl.getConfigAttrib(m_eglDisplay, eglConfig, EGL_NATIVE_VISUAL_ID, &visualId) && visualId != 0)
|
||||
ANativeWindow_setBuffersGeometry(m_window->getWindow(), width, height, visualId);
|
||||
|
||||
m_eglSurface = m_egl.createWindowSurface(m_eglDisplay, eglConfig,
|
||||
(eglw::EGLNativeWindowType)m_window->getWindow(), nullptr);
|
||||
EGLU_CHECK_MSG(m_egl, "eglCreateWindowSurface()");
|
||||
m_eglSurface = m_egl.createWindowSurface(m_eglDisplay, eglConfig,
|
||||
(eglw::EGLNativeWindowType)m_window->getWindow(), nullptr);
|
||||
EGLU_CHECK_MSG(m_egl, "eglCreateWindowSurface()");
|
||||
#else
|
||||
throw tcu::NotSupportedError("Window surfaces are not supported by the desktop MobileGL platform");
|
||||
throw tcu::NotSupportedError("Window surfaces are not supported by the desktop MobileGL platform");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
const eglw::EGLint surfaceAttribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE};
|
||||
m_eglSurface = m_egl.createPbufferSurface(m_eglDisplay, eglConfig, surfaceAttribs);
|
||||
EGLU_CHECK_MSG(m_egl, "eglCreatePbufferSurface()");
|
||||
}
|
||||
} else {
|
||||
const eglw::EGLint surfaceAttribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE};
|
||||
m_eglSurface = m_egl.createPbufferSurface(m_eglDisplay, eglConfig, surfaceAttribs);
|
||||
EGLU_CHECK_MSG(m_egl, "eglCreatePbufferSurface()");
|
||||
}
|
||||
|
||||
if (m_eglSurface == EGL_NO_SURFACE)
|
||||
throw tcu::ResourceError("Failed to create EGL surface");
|
||||
if (m_eglSurface == EGL_NO_SURFACE) throw tcu::ResourceError("Failed to create EGL surface");
|
||||
|
||||
vector<eglw::EGLint> ctxAttribs;
|
||||
ctxAttribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
|
||||
ctxAttribs.push_back(contextType.getMajorVersion());
|
||||
ctxAttribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
|
||||
ctxAttribs.push_back(contextType.getMinorVersion());
|
||||
vector<eglw::EGLint> ctxAttribs;
|
||||
ctxAttribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
|
||||
ctxAttribs.push_back(contextType.getMajorVersion());
|
||||
ctxAttribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
|
||||
ctxAttribs.push_back(contextType.getMinorVersion());
|
||||
|
||||
switch (contextType.getProfile())
|
||||
{
|
||||
case glu::PROFILE_ES:
|
||||
EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_ES_API));
|
||||
break;
|
||||
case glu::PROFILE_CORE:
|
||||
EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API));
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR);
|
||||
break;
|
||||
case glu::PROFILE_COMPATIBILITY:
|
||||
EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API));
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR);
|
||||
break;
|
||||
default:
|
||||
TCU_CHECK_INTERNAL(false);
|
||||
}
|
||||
switch (contextType.getProfile()) {
|
||||
case glu::PROFILE_ES:
|
||||
EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_ES_API));
|
||||
break;
|
||||
case glu::PROFILE_CORE:
|
||||
EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API));
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR);
|
||||
break;
|
||||
case glu::PROFILE_COMPATIBILITY:
|
||||
EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API));
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
|
||||
ctxAttribs.push_back(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR);
|
||||
break;
|
||||
default:
|
||||
TCU_CHECK_INTERNAL(false);
|
||||
}
|
||||
|
||||
eglw::EGLint flags = 0;
|
||||
if ((contextType.getFlags() & glu::CONTEXT_DEBUG) != 0)
|
||||
flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
|
||||
if ((contextType.getFlags() & glu::CONTEXT_ROBUST) != 0)
|
||||
flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
|
||||
if ((contextType.getFlags() & glu::CONTEXT_FORWARD_COMPATIBLE) != 0)
|
||||
flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
|
||||
if (flags != 0)
|
||||
{
|
||||
ctxAttribs.push_back(EGL_CONTEXT_FLAGS_KHR);
|
||||
ctxAttribs.push_back(flags);
|
||||
}
|
||||
ctxAttribs.push_back(EGL_NONE);
|
||||
eglw::EGLint flags = 0;
|
||||
if ((contextType.getFlags() & glu::CONTEXT_DEBUG) != 0) flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
|
||||
if ((contextType.getFlags() & glu::CONTEXT_ROBUST) != 0) flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
|
||||
if ((contextType.getFlags() & glu::CONTEXT_FORWARD_COMPATIBLE) != 0)
|
||||
flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
|
||||
if (flags != 0) {
|
||||
ctxAttribs.push_back(EGL_CONTEXT_FLAGS_KHR);
|
||||
ctxAttribs.push_back(flags);
|
||||
}
|
||||
ctxAttribs.push_back(EGL_NONE);
|
||||
|
||||
const EglRenderContext *sharedEglRenderContext = dynamic_cast<const EglRenderContext *>(sharedContext);
|
||||
m_sharedEglContext = sharedEglRenderContext ? sharedEglRenderContext->getEglContext() : EGL_NO_CONTEXT;
|
||||
const EglRenderContext* sharedEglRenderContext = dynamic_cast<const EglRenderContext*>(sharedContext);
|
||||
m_sharedEglContext = sharedEglRenderContext ? sharedEglRenderContext->getEglContext() : EGL_NO_CONTEXT;
|
||||
|
||||
m_eglContext = m_egl.createContext(m_eglDisplay, eglConfig, m_sharedEglContext, &ctxAttribs[0]);
|
||||
EGLU_CHECK_MSG(m_egl, "eglCreateContext()");
|
||||
if (!m_eglContext)
|
||||
throw tcu::ResourceError("eglCreateContext() failed");
|
||||
m_eglContext = m_egl.createContext(m_eglDisplay, eglConfig, m_sharedEglContext, &ctxAttribs[0]);
|
||||
EGLU_CHECK_MSG(m_egl, "eglCreateContext()");
|
||||
if (!m_eglContext) throw tcu::ResourceError("eglCreateContext() failed");
|
||||
|
||||
// MobileGL requires draw == read.
|
||||
EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
|
||||
// MobileGL requires draw == read.
|
||||
EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
|
||||
|
||||
// MobileGL advertises EGL 1.5, so eglGetProcAddress resolves core entry
|
||||
// points too; there is no separate GL library to dlopen.
|
||||
GetProcFuncLoader funcLoader(m_egl);
|
||||
glu::initCoreFunctions(&m_glFunctions, &funcLoader, contextType.getAPI());
|
||||
glu::initExtensionFunctions(&m_glFunctions, &funcLoader, contextType.getAPI());
|
||||
}
|
||||
|
||||
EglRenderContext::~EglRenderContext(void)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_eglDisplay != EGL_NO_DISPLAY)
|
||||
{
|
||||
m_egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (m_eglContext != EGL_NO_CONTEXT)
|
||||
m_egl.destroyContext(m_eglDisplay, m_eglContext);
|
||||
|
||||
if (m_eglSurface != EGL_NO_SURFACE)
|
||||
m_egl.destroySurface(m_eglDisplay, m_eglSurface);
|
||||
|
||||
if (m_sharedEglContext == EGL_NO_CONTEXT)
|
||||
m_egl.terminate(m_eglDisplay);
|
||||
// MobileGL advertises EGL 1.5, so eglGetProcAddress resolves core entry
|
||||
// points too; there is no separate GL library to dlopen.
|
||||
GetProcFuncLoader funcLoader(m_egl);
|
||||
glu::initCoreFunctions(&m_glFunctions, &funcLoader, contextType.getAPI());
|
||||
glu::initExtensionFunctions(&m_glFunctions, &funcLoader, contextType.getAPI());
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
delete m_window;
|
||||
}
|
||||
EglRenderContext::~EglRenderContext(void) {
|
||||
try {
|
||||
if (m_eglDisplay != EGL_NO_DISPLAY) {
|
||||
m_egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
void EglRenderContext::makeCurrent(void)
|
||||
{
|
||||
EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
|
||||
}
|
||||
if (m_eglContext != EGL_NO_CONTEXT) m_egl.destroyContext(m_eglDisplay, m_eglContext);
|
||||
|
||||
void EglRenderContext::postIterate(void)
|
||||
{
|
||||
m_glFunctions.finish();
|
||||
}
|
||||
if (m_eglSurface != EGL_NO_SURFACE) m_egl.destroySurface(m_eglDisplay, m_eglSurface);
|
||||
|
||||
} // namespace mobilegl
|
||||
if (m_sharedEglContext == EGL_NO_CONTEXT) m_egl.terminate(m_eglDisplay);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
|
||||
delete m_window;
|
||||
}
|
||||
|
||||
void EglRenderContext::makeCurrent(void) {
|
||||
EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
|
||||
}
|
||||
|
||||
void EglRenderContext::postIterate(void) {
|
||||
m_glFunctions.finish();
|
||||
}
|
||||
|
||||
} // namespace mobilegl
|
||||
} // namespace tcu
|
||||
|
||||
tcu::Platform *createPlatform(void)
|
||||
{
|
||||
tcu::Platform* createPlatform(void) {
|
||||
return new tcu::mobilegl::Platform();
|
||||
}
|
||||
|
||||
@@ -23,11 +23,10 @@
|
||||
|
||||
#include "tcuDefs.hpp"
|
||||
|
||||
namespace tcu
|
||||
{
|
||||
class Platform;
|
||||
}
|
||||
namespace tcu {
|
||||
class Platform;
|
||||
} // namespace tcu
|
||||
|
||||
tcu::Platform *createPlatform(void);
|
||||
tcu::Platform* createPlatform(void);
|
||||
|
||||
#endif // _TCUMOBILEGLPLATFORM_HPP
|
||||
|
||||
+76
-63
@@ -33,15 +33,15 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef void *EGLDisplay;
|
||||
typedef void *EGLConfig;
|
||||
typedef void *EGLSurface;
|
||||
typedef void *EGLContext;
|
||||
typedef void* EGLDisplay;
|
||||
typedef void* EGLConfig;
|
||||
typedef void* EGLSurface;
|
||||
typedef void* EGLContext;
|
||||
typedef int EGLint;
|
||||
typedef unsigned int EGLBoolean;
|
||||
typedef unsigned int EGLenum;
|
||||
typedef void *EGLNativeDisplayType;
|
||||
typedef void *EGLNativeWindowType;
|
||||
typedef void* EGLNativeDisplayType;
|
||||
typedef void* EGLNativeWindowType;
|
||||
|
||||
#define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType)0)
|
||||
#define EGL_NO_CONTEXT ((EGLContext)0)
|
||||
@@ -88,43 +88,45 @@ typedef void *EGLNativeWindowType;
|
||||
#define GL_RENDERBUFFER 0x8D41
|
||||
|
||||
typedef EGLDisplay (*P_getDisplay)(EGLNativeDisplayType);
|
||||
typedef EGLBoolean (*P_initialize)(EGLDisplay, EGLint *, EGLint *);
|
||||
typedef EGLBoolean (*P_initialize)(EGLDisplay, EGLint*, EGLint*);
|
||||
typedef EGLBoolean (*P_bindAPI)(EGLenum);
|
||||
typedef EGLBoolean (*P_chooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
|
||||
typedef EGLBoolean (*P_getConfigAttrib)(EGLDisplay, EGLConfig, EGLint, EGLint *);
|
||||
typedef EGLSurface (*P_createWindowSurface)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *);
|
||||
typedef EGLSurface (*P_createPbufferSurface)(EGLDisplay, EGLConfig, const EGLint *);
|
||||
typedef EGLContext (*P_createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *);
|
||||
typedef EGLBoolean (*P_chooseConfig)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*);
|
||||
typedef EGLBoolean (*P_getConfigAttrib)(EGLDisplay, EGLConfig, EGLint, EGLint*);
|
||||
typedef EGLSurface (*P_createWindowSurface)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint*);
|
||||
typedef EGLSurface (*P_createPbufferSurface)(EGLDisplay, EGLConfig, const EGLint*);
|
||||
typedef EGLContext (*P_createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint*);
|
||||
typedef EGLBoolean (*P_makeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
|
||||
typedef EGLint (*P_getError)(void);
|
||||
|
||||
typedef const unsigned char *(*P_glGetString)(unsigned int);
|
||||
typedef void (*P_glGetIntegerv)(unsigned int, int *);
|
||||
typedef const unsigned char* (*P_glGetString)(unsigned int);
|
||||
typedef void (*P_glGetIntegerv)(unsigned int, int*);
|
||||
typedef void (*P_glClearColor)(float, float, float, float);
|
||||
typedef void (*P_glClear)(unsigned int);
|
||||
typedef void (*P_glFinish)(void);
|
||||
typedef void (*P_glReadPixels)(int, int, int, int, unsigned int, unsigned int, void *);
|
||||
typedef void (*P_glReadPixels)(int, int, int, int, unsigned int, unsigned int, void*);
|
||||
typedef unsigned int (*P_glGetError)(void);
|
||||
typedef void (*P_glGenTextures)(int, unsigned int *);
|
||||
typedef void (*P_glGenTextures)(int, unsigned int*);
|
||||
typedef void (*P_glBindTexture)(unsigned int, unsigned int);
|
||||
typedef void (*P_glTexImage2D)(unsigned int, int, int, int, int, int, unsigned int, unsigned int, const void *);
|
||||
typedef void (*P_glTexImage2D)(unsigned int, int, int, int, int, int, unsigned int, unsigned int, const void*);
|
||||
typedef void (*P_glTexParameteri)(unsigned int, unsigned int, int);
|
||||
typedef void (*P_glGenFramebuffers)(int, unsigned int *);
|
||||
typedef void (*P_glGenFramebuffers)(int, unsigned int*);
|
||||
typedef void (*P_glBindFramebuffer)(unsigned int, unsigned int);
|
||||
typedef void (*P_glFramebufferTexture2D)(unsigned int, unsigned int, unsigned int, unsigned int, int);
|
||||
typedef unsigned int (*P_glCheckFramebufferStatus)(unsigned int);
|
||||
typedef void (*P_glViewport)(int, int, int, int);
|
||||
typedef void (*P_glGenRenderbuffers)(int, unsigned int *);
|
||||
typedef void (*P_glGenRenderbuffers)(int, unsigned int*);
|
||||
typedef void (*P_glBindRenderbuffer)(unsigned int, unsigned int);
|
||||
typedef void (*P_glRenderbufferStorage)(unsigned int, unsigned int, int, int);
|
||||
typedef void (*P_glFramebufferRenderbuffer)(unsigned int, unsigned int, unsigned int, unsigned int);
|
||||
|
||||
static void *g_lib;
|
||||
static void *S(const char *n) { return dlsym(g_lib, n); }
|
||||
static void* g_lib;
|
||||
static void* S(const char* n) {
|
||||
return dlsym(g_lib, n);
|
||||
}
|
||||
|
||||
static void on_image(void *ctx, AImageReader *r) {
|
||||
static void on_image(void* ctx, AImageReader* r) {
|
||||
(void)ctx;
|
||||
AImage *img = NULL;
|
||||
AImage* img = NULL;
|
||||
/* Drain the queue, or the producer blocks once maxImages are in flight. */
|
||||
if (AImageReader_acquireNextImage(r, &img) == AMEDIA_OK && img) AImage_delete(img);
|
||||
}
|
||||
@@ -136,18 +138,23 @@ static int near8(unsigned got, int want, int tol) {
|
||||
return d <= tol && d >= -tol;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *backend = "DirectGLES";
|
||||
const char *surface = "pbuffer";
|
||||
const char *libpath = "libMobileGL.so";
|
||||
int main(int argc, char** argv) {
|
||||
const char* backend = "DirectGLES";
|
||||
const char* surface = "pbuffer";
|
||||
const char* libpath = "libMobileGL.so";
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "--backend") && i + 1 < argc) backend = argv[++i];
|
||||
else if (!strcmp(argv[i], "--surface") && i + 1 < argc) surface = argv[++i];
|
||||
else if (!strcmp(argv[i], "--lib") && i + 1 < argc) libpath = argv[++i];
|
||||
if (!strcmp(argv[i], "--backend") && i + 1 < argc)
|
||||
backend = argv[++i];
|
||||
else if (!strcmp(argv[i], "--surface") && i + 1 < argc)
|
||||
surface = argv[++i];
|
||||
else if (!strcmp(argv[i], "--lib") && i + 1 < argc)
|
||||
libpath = argv[++i];
|
||||
else {
|
||||
fprintf(stderr, "usage: %s [--backend DirectGLES|DirectVulkan]"
|
||||
" [--surface pbuffer|imagereader] [--lib path]\n", argv[0]);
|
||||
fprintf(stderr,
|
||||
"usage: %s [--backend DirectGLES|DirectVulkan]"
|
||||
" [--surface pbuffer|imagereader] [--lib path]\n",
|
||||
argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@@ -159,13 +166,13 @@ int main(int argc, char **argv) {
|
||||
printf("mgprobe backend=%s surface=%s lib=%s\n", backend, surface, libpath);
|
||||
|
||||
int useWindow = !strcmp(surface, "imagereader");
|
||||
ANativeWindow *win = NULL;
|
||||
AImageReader *reader = NULL;
|
||||
ANativeWindow* win = NULL;
|
||||
AImageReader* reader = NULL;
|
||||
if (useWindow) {
|
||||
if (AImageReader_newWithUsage(DIM, DIM, AIMAGE_FORMAT_RGBA_8888,
|
||||
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
|
||||
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,
|
||||
4, &reader) != AMEDIA_OK || !reader) {
|
||||
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,
|
||||
4, &reader) != AMEDIA_OK ||
|
||||
!reader) {
|
||||
printf("FAIL AImageReader_newWithUsage\n");
|
||||
return 3;
|
||||
}
|
||||
@@ -210,12 +217,23 @@ int main(int argc, char **argv) {
|
||||
return 7;
|
||||
}
|
||||
|
||||
const EGLint cfgAttribs[] = {
|
||||
EGL_SURFACE_TYPE, useWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
|
||||
EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8,
|
||||
EGL_NONE};
|
||||
const EGLint cfgAttribs[] = {EGL_SURFACE_TYPE,
|
||||
useWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_ALPHA_SIZE,
|
||||
8,
|
||||
EGL_DEPTH_SIZE,
|
||||
24,
|
||||
EGL_STENCIL_SIZE,
|
||||
8,
|
||||
EGL_NONE};
|
||||
EGLConfig cfg = 0;
|
||||
EGLint ncfg = 0;
|
||||
if (!eglChooseConfig_(dpy, cfgAttribs, &cfg, 1, &ncfg) || ncfg < 1) {
|
||||
@@ -234,14 +252,13 @@ int main(int argc, char **argv) {
|
||||
surf = eglCreatePbufferSurface_(dpy, cfg, sa);
|
||||
}
|
||||
if (surf == EGL_NO_SURFACE) {
|
||||
printf("FAIL create%sSurface err=0x%x\n", useWindow ? "Window" : "Pbuffer",
|
||||
eglGetError_ ? eglGetError_() : 0);
|
||||
printf("FAIL create%sSurface err=0x%x\n", useWindow ? "Window" : "Pbuffer", eglGetError_ ? eglGetError_() : 0);
|
||||
return 9;
|
||||
}
|
||||
|
||||
const EGLint ctxAttribs[] = {
|
||||
EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3,
|
||||
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE};
|
||||
EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
||||
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE};
|
||||
EGLContext ctx = eglCreateContext_(dpy, cfg, EGL_NO_CONTEXT, ctxAttribs);
|
||||
if (ctx == EGL_NO_CONTEXT) {
|
||||
printf("FAIL eglCreateContext(3.3 core) err=0x%x\n", eglGetError_ ? eglGetError_() : 0);
|
||||
@@ -272,18 +289,18 @@ int main(int argc, char **argv) {
|
||||
P_glGenRenderbuffers glGenRenderbuffers_ = (P_glGenRenderbuffers)S("glGenRenderbuffers");
|
||||
P_glBindRenderbuffer glBindRenderbuffer_ = (P_glBindRenderbuffer)S("glBindRenderbuffer");
|
||||
P_glRenderbufferStorage glRenderbufferStorage_ = (P_glRenderbufferStorage)S("glRenderbufferStorage");
|
||||
P_glFramebufferRenderbuffer glFramebufferRenderbuffer_ = (P_glFramebufferRenderbuffer)S("glFramebufferRenderbuffer");
|
||||
P_glFramebufferRenderbuffer glFramebufferRenderbuffer_ =
|
||||
(P_glFramebufferRenderbuffer)S("glFramebufferRenderbuffer");
|
||||
|
||||
int major = -1, minor = -1, profile = -1;
|
||||
glGetIntegerv_(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv_(GL_MINOR_VERSION, &minor);
|
||||
glGetIntegerv_(GL_CONTEXT_PROFILE_MASK, &profile);
|
||||
printf(" GL_VENDOR %s\n", (const char *)glGetString_(GL_VENDOR));
|
||||
printf(" GL_RENDERER %s\n", (const char *)glGetString_(GL_RENDERER));
|
||||
printf(" GL_VERSION %s\n", (const char *)glGetString_(GL_VERSION));
|
||||
printf(" GLSL %s\n", (const char *)glGetString_(GL_SHADING_LANGUAGE_VERSION));
|
||||
printf(" version %d.%d profile_mask 0x%x %s\n", major, minor, profile,
|
||||
(profile & 1) ? "(core)" : "(NOT CORE)");
|
||||
printf(" GL_VENDOR %s\n", (const char*)glGetString_(GL_VENDOR));
|
||||
printf(" GL_RENDERER %s\n", (const char*)glGetString_(GL_RENDERER));
|
||||
printf(" GL_VERSION %s\n", (const char*)glGetString_(GL_VERSION));
|
||||
printf(" GLSL %s\n", (const char*)glGetString_(GL_SHADING_LANGUAGE_VERSION));
|
||||
printf(" version %d.%d profile_mask 0x%x %s\n", major, minor, profile, (profile & 1) ? "(core)" : "(NOT CORE)");
|
||||
|
||||
unsigned char px[4];
|
||||
|
||||
@@ -294,8 +311,7 @@ int main(int argc, char **argv) {
|
||||
memset(px, 0, sizeof px);
|
||||
glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
||||
int defOk = near8(px[0], 64, 10) && near8(px[1], 128, 10) && near8(px[2], 191, 10);
|
||||
printf(" default-FB readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3],
|
||||
defOk ? "ok" : "BROKEN");
|
||||
printf(" default-FB readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], defOk ? "ok" : "BROKEN");
|
||||
|
||||
/* User FBO - this is what dEQP uses with --deqp-surface-type=fbo. */
|
||||
unsigned int tex = 0, fbo = 0;
|
||||
@@ -317,8 +333,7 @@ int main(int argc, char **argv) {
|
||||
memset(px, 0, sizeof px);
|
||||
glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
||||
fboOk = near8(px[0], 230, 10) && near8(px[1], 51, 10) && near8(px[2], 102, 10);
|
||||
printf(" user-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3],
|
||||
fboOk ? "ok" : "BROKEN");
|
||||
printf(" user-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], fboOk ? "ok" : "BROKEN");
|
||||
} else {
|
||||
printf(" user-FBO incomplete status=0x%x\n", fbst);
|
||||
}
|
||||
@@ -346,8 +361,7 @@ int main(int argc, char **argv) {
|
||||
memset(px, 0, sizeof px);
|
||||
glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
||||
rboOk = near8(px[0], 26, 10) && near8(px[1], 179, 10) && near8(px[2], 77, 10);
|
||||
printf(" rbo-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3],
|
||||
rboOk ? "ok" : "BROKEN");
|
||||
printf(" rbo-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], rboOk ? "ok" : "BROKEN");
|
||||
} else {
|
||||
printf(" rbo-FBO incomplete status=0x%x\n", rst);
|
||||
}
|
||||
@@ -357,9 +371,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
unsigned glerr = glGetError_ ? glGetError_() : 0;
|
||||
int ok = fboOk && rboOk && (major > 3 || (major == 3 && minor >= 3)) && (profile & 1) && glerr == 0;
|
||||
printf("%s backend=%s surface=%s default_fb=%s user_fbo=%s rbo_fbo=%s glerr=0x%x\n",
|
||||
ok ? "PASS" : "FAIL", backend, surface, defOk ? "ok" : "broken",
|
||||
fboOk ? "ok" : "broken", rboOk ? "ok" : "broken", glerr);
|
||||
printf("%s backend=%s surface=%s default_fb=%s user_fbo=%s rbo_fbo=%s glerr=0x%x\n", ok ? "PASS" : "FAIL", backend,
|
||||
surface, defOk ? "ok" : "broken", fboOk ? "ok" : "broken", rboOk ? "ok" : "broken", glerr);
|
||||
|
||||
fflush(stdout);
|
||||
/* MobileGL aborts in static teardown; leave before that runs. */
|
||||
|
||||
@@ -114,8 +114,10 @@ static int ScanPids(const char* pkg, int* pids) {
|
||||
|
||||
static void Push(long long t, int index, long long cpu) {
|
||||
struct Sample* s = &ring[(ringHead + ringSize) % ringCap];
|
||||
if (ringSize == ringCap) ringHead = (ringHead + 1) % ringCap;
|
||||
else ++ringSize;
|
||||
if (ringSize == ringCap)
|
||||
ringHead = (ringHead + 1) % ringCap;
|
||||
else
|
||||
++ringSize;
|
||||
s->t = t;
|
||||
s->index = index;
|
||||
s->cpu = cpu;
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
#include <unistd.h>
|
||||
|
||||
static volatile sig_atomic_t g_stop = 0;
|
||||
static void onsig(int s) { (void)s; g_stop = 1; }
|
||||
static void onsig(int s) {
|
||||
(void)s;
|
||||
g_stop = 1;
|
||||
}
|
||||
|
||||
#define MAXT 128
|
||||
#define MAXSY 400
|
||||
@@ -29,14 +32,14 @@ static void onsig(int s) { (void)s; g_stop = 1; }
|
||||
struct T {
|
||||
int tid;
|
||||
char name[32];
|
||||
long long cpu0; // utime+stime at start (ticks)
|
||||
long long vcs0; // nr_voluntary_switches at start
|
||||
long long run; // samples with syscall "running"
|
||||
long long sys[MAXSY]; // samples while in syscall nr
|
||||
long long cpu0; // utime+stime at start (ticks)
|
||||
long long vcs0; // nr_voluntary_switches at start
|
||||
long long run; // samples with syscall "running"
|
||||
long long sys[MAXSY]; // samples while in syscall nr
|
||||
long long openerr;
|
||||
};
|
||||
|
||||
static long long readfile(const char *p, char *buf, int n) {
|
||||
static long long readfile(const char* p, char* buf, int n) {
|
||||
int fd = open(p, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
long long r = read(fd, buf, n - 1);
|
||||
@@ -45,12 +48,15 @@ static long long readfile(const char *p, char *buf, int n) {
|
||||
return r;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 4) { fprintf(stderr, "usage: sampler pid dur_s interval_ms out\n"); return 2; }
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 4) {
|
||||
fprintf(stderr, "usage: sampler pid dur_s interval_ms out\n");
|
||||
return 2;
|
||||
}
|
||||
int pid = atoi(argv[1]);
|
||||
int durMs = atoi(argv[2]) * 1000;
|
||||
int intMs = argc > 4 ? atoi(argv[3]) : 2;
|
||||
const char *out = argc > 4 ? argv[4] : "sampler.csv";
|
||||
const char* out = argc > 4 ? argv[4] : "sampler.csv";
|
||||
|
||||
struct T ts[MAXT];
|
||||
int n = 0;
|
||||
@@ -59,35 +65,45 @@ int main(int argc, char **argv) {
|
||||
// initial scan
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
snprintf(path, sizeof path, "/proc/%d/task", pid);
|
||||
DIR *d = opendir(path);
|
||||
if (!d) { perror("opendir task"); return 1; }
|
||||
struct dirent *e;
|
||||
DIR* d = opendir(path);
|
||||
if (!d) {
|
||||
perror("opendir task");
|
||||
return 1;
|
||||
}
|
||||
struct dirent* e;
|
||||
n = 0;
|
||||
while ((e = readdir(d)) && n < MAXT) {
|
||||
if (e->d_name[0] == '.') continue;
|
||||
int tid = atoi(e->d_name);
|
||||
if (tid <= 0) continue;
|
||||
struct T *t = &ts[n++];
|
||||
struct T* t = &ts[n++];
|
||||
memset(t, 0, sizeof *t);
|
||||
t->tid = tid;
|
||||
snprintf(path, sizeof path, "/proc/%d/task/%d/comm", pid, tid);
|
||||
if (readfile(path, buf, sizeof buf) > 0) {
|
||||
strncpy(t->name, buf, sizeof t->name - 1);
|
||||
char *nl = strchr(t->name, '\n'); if (nl) *nl = 0;
|
||||
char* nl = strchr(t->name, '\n');
|
||||
if (nl) *nl = 0;
|
||||
}
|
||||
snprintf(path, sizeof path, "/proc/%d/task/%d/stat", pid, tid);
|
||||
long long ut = 0, st = 0;
|
||||
if (readfile(path, buf, sizeof buf) > 0) {
|
||||
// fields: 1 pid, 2 comm, 3 state ... 14 utime, 15 stime.
|
||||
// tokens after ')' start at field 3, so utime is f==11, stime f==12.
|
||||
char *r = strrchr(buf, ')');
|
||||
char* r = strrchr(buf, ')');
|
||||
if (r) {
|
||||
int f = 0; char *sp = r + 1;
|
||||
int f = 0;
|
||||
char* sp = r + 1;
|
||||
while (*sp) {
|
||||
if (f == 11) ut = atoll(sp);
|
||||
if (f == 12) { st = atoll(sp); break; }
|
||||
while (*sp && *sp != ' ') sp++;
|
||||
while (*sp == ' ') sp++;
|
||||
if (f == 12) {
|
||||
st = atoll(sp);
|
||||
break;
|
||||
}
|
||||
while (*sp && *sp != ' ')
|
||||
sp++;
|
||||
while (*sp == ' ')
|
||||
sp++;
|
||||
f++;
|
||||
}
|
||||
}
|
||||
@@ -96,15 +112,15 @@ int main(int argc, char **argv) {
|
||||
snprintf(path, sizeof path, "/proc/%d/task/%d/sched", pid, tid);
|
||||
t->vcs0 = -1;
|
||||
if (readfile(path, buf, sizeof buf) > 0) {
|
||||
char *v = strstr(buf, "nr_voluntary_switches");
|
||||
char* v = strstr(buf, "nr_voluntary_switches");
|
||||
if (v) {
|
||||
char *col = strchr(v, ':');
|
||||
char* col = strchr(v, ':');
|
||||
if (col) t->vcs0 = atoll(col + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
if (n > 1) break; // first pass may catch a dying thread set
|
||||
if (n > 1) break; // first pass may catch a dying thread set
|
||||
usleep(50000);
|
||||
}
|
||||
|
||||
@@ -118,11 +134,20 @@ int main(int argc, char **argv) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
snprintf(path, sizeof path, "/proc/%d/task/%d/syscall", pid, ts[i].tid);
|
||||
long long r = readfile(path, buf, sizeof buf);
|
||||
if (r <= 0) { ts[i].openerr++; continue; }
|
||||
if (!strncmp(buf, "running", 7)) { ts[i].run++; continue; }
|
||||
if (r <= 0) {
|
||||
ts[i].openerr++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(buf, "running", 7)) {
|
||||
ts[i].run++;
|
||||
continue;
|
||||
}
|
||||
long nr = -2;
|
||||
if (buf[0] != '-') nr = strtol(buf, NULL, 10);
|
||||
if (nr < 0) { ts[i].run++; continue; } // -1 means running
|
||||
if (nr < 0) {
|
||||
ts[i].run++;
|
||||
continue;
|
||||
} // -1 means running
|
||||
if (nr >= MAXSY) nr = MAXSY - 1;
|
||||
ts[i].sys[nr]++;
|
||||
}
|
||||
@@ -131,24 +156,32 @@ int main(int argc, char **argv) {
|
||||
elapsed = (now.tv_sec - t0.tv_sec) * 1000LL + (now.tv_nsec - t0.tv_nsec) / 1000000LL;
|
||||
}
|
||||
|
||||
FILE *f = fopen(out, "w");
|
||||
if (!f) { perror("fopen out"); return 1; }
|
||||
fprintf(f, "# pid=%d dur_ms=%lld interval_ms=%d samples_per_thread~=%lld\n",
|
||||
pid, elapsed, intMs, elapsed / intMs);
|
||||
FILE* f = fopen(out, "w");
|
||||
if (!f) {
|
||||
perror("fopen out");
|
||||
return 1;
|
||||
}
|
||||
fprintf(f, "# pid=%d dur_ms=%lld interval_ms=%d samples_per_thread~=%lld\n", pid, elapsed, intMs, elapsed / intMs);
|
||||
fprintf(f, "tid,name,cpu_ticks_delta,vcs_delta,run_samples,open_err,sys_hist\n");
|
||||
for (int i = 0; i < n; i++) {
|
||||
struct T *t = &ts[i];
|
||||
struct T* t = &ts[i];
|
||||
long long ut = 0, st = 0;
|
||||
snprintf(path, sizeof path, "/proc/%d/task/%d/stat", pid, t->tid);
|
||||
if (readfile(path, buf, sizeof buf) > 0) {
|
||||
char *r = strrchr(buf, ')');
|
||||
char* r = strrchr(buf, ')');
|
||||
if (r) {
|
||||
int f2 = 0; char *sp = r + 1;
|
||||
int f2 = 0;
|
||||
char* sp = r + 1;
|
||||
while (*sp) {
|
||||
if (f2 == 11) ut = atoll(sp);
|
||||
if (f2 == 12) { st = atoll(sp); break; }
|
||||
while (*sp && *sp != ' ') sp++;
|
||||
while (*sp == ' ') sp++;
|
||||
if (f2 == 12) {
|
||||
st = atoll(sp);
|
||||
break;
|
||||
}
|
||||
while (*sp && *sp != ' ')
|
||||
sp++;
|
||||
while (*sp == ' ')
|
||||
sp++;
|
||||
f2++;
|
||||
}
|
||||
}
|
||||
@@ -156,15 +189,14 @@ int main(int argc, char **argv) {
|
||||
long long vcs = -1;
|
||||
snprintf(path, sizeof path, "/proc/%d/task/%d/sched", pid, t->tid);
|
||||
if (readfile(path, buf, sizeof buf) > 0) {
|
||||
char *v = strstr(buf, "nr_voluntary_switches");
|
||||
char* v = strstr(buf, "nr_voluntary_switches");
|
||||
if (v) {
|
||||
char *col = strchr(v, ':');
|
||||
char* col = strchr(v, ':');
|
||||
if (col) vcs = atoll(col + 1);
|
||||
}
|
||||
}
|
||||
fprintf(f, "%d,%s,%lld,%lld,%lld,%lld,\"", t->tid, t->name,
|
||||
(ut + st) - t->cpu0, (vcs >= 0 && t->vcs0 >= 0) ? vcs - t->vcs0 : -1,
|
||||
t->run, t->openerr);
|
||||
fprintf(f, "%d,%s,%lld,%lld,%lld,%lld,\"", t->tid, t->name, (ut + st) - t->cpu0,
|
||||
(vcs >= 0 && t->vcs0 >= 0) ? vcs - t->vcs0 : -1, t->run, t->openerr);
|
||||
for (int s = 0; s < MAXSY; s++)
|
||||
if (t->sys[s]) fprintf(f, "%d:%lld;", s, t->sys[s]);
|
||||
fprintf(f, "\"\n");
|
||||
|
||||
@@ -18,53 +18,57 @@
|
||||
#undef GL_GLEXT_PROTOTYPES
|
||||
|
||||
namespace MGITest {
|
||||
class P5fPublicMipmapScenario : public ScenarioTest {
|
||||
protected:
|
||||
void Check(bool gpuWritten) {
|
||||
if (!Ready()) return;
|
||||
GLuint texture = 0, fbo = 0;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
std::array<GLubyte, 4 * 4 * 4> red{};
|
||||
for (std::size_t i = 0; i < red.size(); i += 4) {
|
||||
red[i] = 255;
|
||||
red[i + 3] = 255;
|
||||
class P5fPublicMipmapScenario : public ScenarioTest {
|
||||
protected:
|
||||
void Check(bool gpuWritten) {
|
||||
if (!Ready()) return;
|
||||
GLuint texture = 0, fbo = 0;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
std::array<GLubyte, 4 * 4 * 4> red{};
|
||||
for (std::size_t i = 0; i < red.size(); i += 4) {
|
||||
red[i] = 255;
|
||||
red[i + 3] = 255;
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
if (gpuWritten) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE});
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
for (GLint level = 0; level <= 2; ++level) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE}) << level;
|
||||
std::array<GLubyte, 4> pixel{71, 72, 73, 74};
|
||||
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(pixel[0], gpuWritten ? 0 : 255) << "mip=" << level;
|
||||
EXPECT_EQ(pixel[1], gpuWritten ? 255 : 0) << "mip=" << level;
|
||||
EXPECT_EQ(pixel[2], 0) << "mip=" << level;
|
||||
EXPECT_EQ(pixel[3], 255) << "mip=" << level;
|
||||
}
|
||||
EXPECT_EQ(glGetError(), GLenum{GL_NO_ERROR});
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &texture);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
if (gpuWritten) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE});
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
for (GLint level = 0; level <= 2; ++level) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE}) << level;
|
||||
std::array<GLubyte, 4> pixel{71, 72, 73, 74};
|
||||
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
EXPECT_EQ(pixel[0], gpuWritten ? 0 : 255) << "mip=" << level;
|
||||
EXPECT_EQ(pixel[1], gpuWritten ? 255 : 0) << "mip=" << level;
|
||||
EXPECT_EQ(pixel[2], 0) << "mip=" << level;
|
||||
EXPECT_EQ(pixel[3], 255) << "mip=" << level;
|
||||
}
|
||||
EXPECT_EQ(glGetError(), GLenum{GL_NO_ERROR});
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &texture);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(P5fPublicMipmapScenario, UploadedRgba8BaseGeneratesRedMipChain) { Check(false); }
|
||||
TEST_F(P5fPublicMipmapScenario, GpuWrittenBaseGeneratesGreenMipChainWithoutStaleCpuUpload) { Check(true); }
|
||||
TEST_F(P5fPublicMipmapScenario, UploadedRgba8BaseGeneratesRedMipChain) {
|
||||
Check(false);
|
||||
}
|
||||
TEST_F(P5fPublicMipmapScenario, GpuWrittenBaseGeneratesGreenMipChainWithoutStaleCpuUpload) {
|
||||
Check(true);
|
||||
}
|
||||
} // namespace MGITest
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,31 +33,31 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// The single fact that makes this spike conclusive rather than suggestive: the exec'd
|
||||
// child reports the domain it is running in. `u:r:untrusted_app:s0:...` means an ordinary
|
||||
// application process really did exec this file; anything else (shell, adb, a platform
|
||||
// domain) means the test was run the wrong way and its verdict does not transfer.
|
||||
void ReadSelinuxContext(char* out, size_t size) {
|
||||
out[0] = '\0';
|
||||
FILE* file = std::fopen("/proc/self/attr/current", "r");
|
||||
if (file == nullptr) {
|
||||
std::snprintf(out, size, "<unreadable>");
|
||||
return;
|
||||
}
|
||||
const size_t read = std::fread(out, 1, size - 1, file);
|
||||
std::fclose(file);
|
||||
out[read] = '\0';
|
||||
// The kernel returns the context NUL-terminated inside the read; trim anything after.
|
||||
for (size_t index = 0; index < read; ++index) {
|
||||
if (out[index] == '\n' || out[index] == '\0') {
|
||||
out[index] = '\0';
|
||||
break;
|
||||
// The single fact that makes this spike conclusive rather than suggestive: the exec'd
|
||||
// child reports the domain it is running in. `u:r:untrusted_app:s0:...` means an ordinary
|
||||
// application process really did exec this file; anything else (shell, adb, a platform
|
||||
// domain) means the test was run the wrong way and its verdict does not transfer.
|
||||
void ReadSelinuxContext(char* out, size_t size) {
|
||||
out[0] = '\0';
|
||||
FILE* file = std::fopen("/proc/self/attr/current", "r");
|
||||
if (file == nullptr) {
|
||||
std::snprintf(out, size, "<unreadable>");
|
||||
return;
|
||||
}
|
||||
const size_t read = std::fread(out, 1, size - 1, file);
|
||||
std::fclose(file);
|
||||
out[read] = '\0';
|
||||
// The kernel returns the context NUL-terminated inside the read; trim anything after.
|
||||
for (size_t index = 0; index < read; ++index) {
|
||||
if (out[index] == '\n' || out[index] == '\0') {
|
||||
out[index] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (out[0] == '\0') {
|
||||
std::snprintf(out, size, "<empty>");
|
||||
}
|
||||
}
|
||||
if (out[0] == '\0') {
|
||||
std::snprintf(out, size, "<empty>");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -69,9 +69,8 @@ int main(int argc, char** argv) {
|
||||
std::snprintf(line, sizeof(line),
|
||||
"MobileGLServer-spike ok argv0=%s pid=%d ppid=%d uid=%d gid=%d argc=%d "
|
||||
"selinux=%s\n",
|
||||
argc > 0 && argv[0] != nullptr ? argv[0] : "<null>",
|
||||
static_cast<int>(getpid()), static_cast<int>(getppid()),
|
||||
static_cast<int>(getuid()), static_cast<int>(getgid()), argc, context);
|
||||
argc > 0 && argv[0] != nullptr ? argv[0] : "<null>", static_cast<int>(getpid()),
|
||||
static_cast<int>(getppid()), static_cast<int>(getuid()), static_cast<int>(getgid()), argc, context);
|
||||
|
||||
std::fputs(line, stdout);
|
||||
std::fflush(stdout);
|
||||
@@ -83,14 +82,12 @@ int main(int argc, char** argv) {
|
||||
|
||||
FILE* marker = std::fopen(argv[1], "w");
|
||||
if (marker == nullptr) {
|
||||
std::fprintf(stderr, "MobileGLServer-spike: cannot open marker %s: %s\n", argv[1],
|
||||
std::strerror(errno));
|
||||
std::fprintf(stderr, "MobileGLServer-spike: cannot open marker %s: %s\n", argv[1], std::strerror(errno));
|
||||
return 3;
|
||||
}
|
||||
std::fputs(line, marker);
|
||||
if (std::fclose(marker) != 0) {
|
||||
std::fprintf(stderr, "MobileGLServer-spike: cannot write marker %s: %s\n", argv[1],
|
||||
std::strerror(errno));
|
||||
std::fprintf(stderr, "MobileGLServer-spike: cannot write marker %s: %s\n", argv[1], std::strerror(errno));
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace mobilegl_trace_dump {
|
||||
|
||||
// Installs the opt-in framebuffer-attachment and named-2D-texture dump hooks. The respective
|
||||
// environments are MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS and MOBILEGL_TRACE_DUMP_TEXTURE_2D.
|
||||
// Safe and cheap to call on every makeCurrent: the environment is consulted once and the hook is
|
||||
// installed at most once.
|
||||
void InstallIfRequested();
|
||||
// Installs the opt-in framebuffer-attachment and named-2D-texture dump hooks. The respective
|
||||
// environments are MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS and MOBILEGL_TRACE_DUMP_TEXTURE_2D.
|
||||
// Safe and cheap to call on every makeCurrent: the environment is consulted once and the hook is
|
||||
// installed at most once.
|
||||
void InstallIfRequested();
|
||||
|
||||
} // namespace mobilegl_trace_dump
|
||||
|
||||
@@ -16,136 +16,136 @@
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
|
||||
void *_libGlHandle = nullptr;
|
||||
void* _libGlHandle = nullptr;
|
||||
|
||||
namespace {
|
||||
|
||||
void *LookupSymbol(const char *procName);
|
||||
void* LookupSymbol(const char* procName);
|
||||
|
||||
void *GetMobileGlHandle() {
|
||||
if (_libGlHandle != nullptr) {
|
||||
void* GetMobileGlHandle() {
|
||||
if (_libGlHandle != nullptr) {
|
||||
return _libGlHandle;
|
||||
}
|
||||
|
||||
const char* library = std::getenv("MOBILEGL_TRACE_LIBRARY");
|
||||
if (library != nullptr && library[0] != '\0') {
|
||||
_libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
|
||||
if (_libGlHandle == nullptr) {
|
||||
_libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL);
|
||||
}
|
||||
}
|
||||
if (_libGlHandle == nullptr) {
|
||||
_libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
|
||||
}
|
||||
if (_libGlHandle == nullptr) {
|
||||
_libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
}
|
||||
return _libGlHandle;
|
||||
}
|
||||
|
||||
const char *library = std::getenv("MOBILEGL_TRACE_LIBRARY");
|
||||
if (library != nullptr && library[0] != '\0') {
|
||||
_libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
|
||||
if (_libGlHandle == nullptr) {
|
||||
_libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL);
|
||||
using GlReadBuffer = void (*)(GLenum mode);
|
||||
using GlReadPixels = void (*)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void* pixels);
|
||||
using GlGetIntegerv = void (*)(GLenum pname, GLint* data);
|
||||
using GlGetError = GLenum (*)();
|
||||
GlReadBuffer gRealGlReadBuffer = nullptr;
|
||||
GlReadPixels gRealGlReadPixels = nullptr;
|
||||
GlGetIntegerv gRealGlGetIntegerv = nullptr;
|
||||
GlGetError gRealGlGetError = nullptr;
|
||||
int gSuppressInvalidEnumCount = 0;
|
||||
|
||||
void MobileGLTraceReadBuffer(GLenum mode) {
|
||||
if (mode == GL_BACK) {
|
||||
++gSuppressInvalidEnumCount;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_libGlHandle == nullptr) {
|
||||
_libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
|
||||
}
|
||||
if (_libGlHandle == nullptr) {
|
||||
_libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
}
|
||||
return _libGlHandle;
|
||||
}
|
||||
|
||||
using GlReadBuffer = void (*)(GLenum mode);
|
||||
using GlReadPixels = void (*)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void *pixels);
|
||||
using GlGetIntegerv = void (*)(GLenum pname, GLint *data);
|
||||
using GlGetError = GLenum (*)();
|
||||
GlReadBuffer gRealGlReadBuffer = nullptr;
|
||||
GlReadPixels gRealGlReadPixels = nullptr;
|
||||
GlGetIntegerv gRealGlGetIntegerv = nullptr;
|
||||
GlGetError gRealGlGetError = nullptr;
|
||||
int gSuppressInvalidEnumCount = 0;
|
||||
|
||||
void MobileGLTraceReadBuffer(GLenum mode) {
|
||||
if (mode == GL_BACK) {
|
||||
++gSuppressInvalidEnumCount;
|
||||
return;
|
||||
}
|
||||
if (gRealGlReadBuffer == nullptr) {
|
||||
gRealGlReadBuffer = reinterpret_cast<GlReadBuffer>(LookupSymbol("glReadBuffer"));
|
||||
}
|
||||
if (gRealGlReadBuffer != nullptr) {
|
||||
gRealGlReadBuffer(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void MobileGLTraceReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void *pixels) {
|
||||
if (gRealGlReadPixels == nullptr) {
|
||||
gRealGlReadPixels = reinterpret_cast<GlReadPixels>(LookupSymbol("glReadPixels"));
|
||||
}
|
||||
if (gRealGlReadPixels != nullptr) {
|
||||
gRealGlReadPixels(x, y, width, height, format, type, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
void MobileGLTraceGetIntegerv(GLenum pname, GLint *data) {
|
||||
if (pname == GL_READ_BUFFER) {
|
||||
if (data != nullptr) {
|
||||
*data = GL_BACK;
|
||||
if (gRealGlReadBuffer == nullptr) {
|
||||
gRealGlReadBuffer = reinterpret_cast<GlReadBuffer>(LookupSymbol("glReadBuffer"));
|
||||
}
|
||||
++gSuppressInvalidEnumCount;
|
||||
return;
|
||||
}
|
||||
if (gRealGlGetIntegerv == nullptr) {
|
||||
gRealGlGetIntegerv = reinterpret_cast<GlGetIntegerv>(LookupSymbol("glGetIntegerv"));
|
||||
}
|
||||
if (gRealGlGetIntegerv != nullptr) {
|
||||
gRealGlGetIntegerv(pname, data);
|
||||
}
|
||||
}
|
||||
|
||||
GLenum MobileGLTraceGetError() {
|
||||
if (gRealGlGetError == nullptr) {
|
||||
gRealGlGetError = reinterpret_cast<GlGetError>(LookupSymbol("glGetError"));
|
||||
}
|
||||
if (gRealGlGetError == nullptr) {
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
GLenum error = gRealGlGetError();
|
||||
if (error == GL_INVALID_ENUM && gSuppressInvalidEnumCount > 0) {
|
||||
--gSuppressInvalidEnumCount;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void *LookupSymbol(const char *procName) {
|
||||
void *mobileGl = GetMobileGlHandle();
|
||||
if (mobileGl != nullptr) {
|
||||
void *proc = dlsym(mobileGl, procName);
|
||||
if (proc != nullptr) {
|
||||
return proc;
|
||||
if (gRealGlReadBuffer != nullptr) {
|
||||
gRealGlReadBuffer(mode);
|
||||
}
|
||||
}
|
||||
|
||||
return dlsym(RTLD_DEFAULT, procName);
|
||||
}
|
||||
void MobileGLTraceReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void* pixels) {
|
||||
if (gRealGlReadPixels == nullptr) {
|
||||
gRealGlReadPixels = reinterpret_cast<GlReadPixels>(LookupSymbol("glReadPixels"));
|
||||
}
|
||||
if (gRealGlReadPixels != nullptr) {
|
||||
gRealGlReadPixels(x, y, width, height, format, type, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
void MobileGLTraceGetIntegerv(GLenum pname, GLint* data) {
|
||||
if (pname == GL_READ_BUFFER) {
|
||||
if (data != nullptr) {
|
||||
*data = GL_BACK;
|
||||
}
|
||||
++gSuppressInvalidEnumCount;
|
||||
return;
|
||||
}
|
||||
if (gRealGlGetIntegerv == nullptr) {
|
||||
gRealGlGetIntegerv = reinterpret_cast<GlGetIntegerv>(LookupSymbol("glGetIntegerv"));
|
||||
}
|
||||
if (gRealGlGetIntegerv != nullptr) {
|
||||
gRealGlGetIntegerv(pname, data);
|
||||
}
|
||||
}
|
||||
|
||||
GLenum MobileGLTraceGetError() {
|
||||
if (gRealGlGetError == nullptr) {
|
||||
gRealGlGetError = reinterpret_cast<GlGetError>(LookupSymbol("glGetError"));
|
||||
}
|
||||
if (gRealGlGetError == nullptr) {
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
GLenum error = gRealGlGetError();
|
||||
if (error == GL_INVALID_ENUM && gSuppressInvalidEnumCount > 0) {
|
||||
--gSuppressInvalidEnumCount;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void* LookupSymbol(const char* procName) {
|
||||
void* mobileGl = GetMobileGlHandle();
|
||||
if (mobileGl != nullptr) {
|
||||
void* proc = dlsym(mobileGl, procName);
|
||||
if (proc != nullptr) {
|
||||
return proc;
|
||||
}
|
||||
}
|
||||
|
||||
return dlsym(RTLD_DEFAULT, procName);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void *_getPublicProcAddress(const char *procName) {
|
||||
void* _getPublicProcAddress(const char* procName) {
|
||||
if (strcmp(procName, "glReadPixels") == 0) {
|
||||
return reinterpret_cast<void *>(&MobileGLTraceReadPixels);
|
||||
return reinterpret_cast<void*>(&MobileGLTraceReadPixels);
|
||||
}
|
||||
if (strcmp(procName, "glReadBuffer") == 0) {
|
||||
return reinterpret_cast<void *>(&MobileGLTraceReadBuffer);
|
||||
return reinterpret_cast<void*>(&MobileGLTraceReadBuffer);
|
||||
}
|
||||
if (strcmp(procName, "glGetIntegerv") == 0) {
|
||||
return reinterpret_cast<void *>(&MobileGLTraceGetIntegerv);
|
||||
return reinterpret_cast<void*>(&MobileGLTraceGetIntegerv);
|
||||
}
|
||||
if (strcmp(procName, "glGetError") == 0) {
|
||||
return reinterpret_cast<void *>(&MobileGLTraceGetError);
|
||||
return reinterpret_cast<void*>(&MobileGLTraceGetError);
|
||||
}
|
||||
return LookupSymbol(procName);
|
||||
}
|
||||
|
||||
void *_getPrivateProcAddress(const char *procName) {
|
||||
void *proc = LookupSymbol(procName);
|
||||
void* _getPrivateProcAddress(const char* procName) {
|
||||
void* proc = LookupSymbol(procName);
|
||||
if (proc != nullptr) {
|
||||
return proc;
|
||||
}
|
||||
|
||||
if (_eglGetProcAddress != nullptr) {
|
||||
return reinterpret_cast<void *>(_eglGetProcAddress(procName));
|
||||
return reinterpret_cast<void*>(_eglGetProcAddress(procName));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,383 +46,401 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr unsigned kUniformBuffer = 0x8A11;
|
||||
constexpr unsigned kShaderStorageBuffer = 0x90D2;
|
||||
constexpr unsigned kDynamicStorageBit = 0x0100;
|
||||
constexpr unsigned kRelocBufferName = 1000000;
|
||||
constexpr unsigned kUniformBuffer = 0x8A11;
|
||||
constexpr unsigned kShaderStorageBuffer = 0x90D2;
|
||||
constexpr unsigned kDynamicStorageBit = 0x0100;
|
||||
constexpr unsigned kRelocBufferName = 1000000;
|
||||
|
||||
[[noreturn]] void Fail(const trace::Call *call, const std::string &what) {
|
||||
std::fprintf(stderr, "error: call %u %s: %s\n", call ? call->no : 0u, call ? call->name() : "-", what.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
bool Is(const trace::Call *call, const char *name) { return std::strcmp(call->name(), name) == 0; }
|
||||
|
||||
bool StartsWith(const char *s, const char *prefix) { return std::strncmp(s, prefix, std::strlen(prefix)) == 0; }
|
||||
|
||||
bool ReadsIndexedBuffers(const trace::Call *call) {
|
||||
const char *n = call->name();
|
||||
return StartsWith(n, "glDraw") || StartsWith(n, "glMultiDraw") || StartsWith(n, "glDispatchCompute");
|
||||
}
|
||||
|
||||
bool IsRangeTarget(unsigned target) { return target == kUniformBuffer || target == kShaderStorageBuffer; }
|
||||
|
||||
struct MapRegion {
|
||||
unsigned buffer;
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
uint64_t ptr;
|
||||
};
|
||||
|
||||
struct Reloc {
|
||||
unsigned src = 0;
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
bool dirty = true;
|
||||
std::vector<char> last;
|
||||
const trace::EnumSig *targetSig = nullptr;
|
||||
};
|
||||
|
||||
// Signatures for the calls this tool inserts; ids are allocated past the trace's own.
|
||||
struct Sigs {
|
||||
const char *createNames[2] = {"n", "buffers"};
|
||||
const char *storageNames[4] = {"buffer", "size", "data", "flags"};
|
||||
const char *subDataNames[4] = {"buffer", "offset", "size", "data"};
|
||||
const char *bindRangeNames[5] = {"target", "index", "buffer", "offset", "size"};
|
||||
const char *bindNames[2] = {"target", "buffer"};
|
||||
trace::FunctionSig create{}, storage{}, subData{}, bindRange{}, bind{};
|
||||
|
||||
void Init(trace::Id firstId) {
|
||||
create = {firstId + 0, "glCreateBuffers", 2, createNames};
|
||||
storage = {firstId + 1, "glNamedBufferStorage", 4, storageNames};
|
||||
subData = {firstId + 2, "glNamedBufferSubData", 4, subDataNames};
|
||||
bindRange = {firstId + 3, "glBindBufferRange", 5, bindRangeNames};
|
||||
bind = {firstId + 4, "glBindBuffer", 2, bindNames};
|
||||
}
|
||||
};
|
||||
|
||||
class Rewriter {
|
||||
public:
|
||||
uint64_t align = 1024;
|
||||
std::set<unsigned> mapCalls;
|
||||
|
||||
int Run(const char *in, const char *out) {
|
||||
Scan(in);
|
||||
if (relocSrc_.empty()) {
|
||||
std::fprintf(stderr, "nothing to realign at %llu bytes\n", (unsigned long long)align);
|
||||
return 2;
|
||||
}
|
||||
sigs_.Init(maxSigId_ + 1);
|
||||
stride_ = (maxRelocSize_ + align - 1) / align * align;
|
||||
Pass(in, nullptr); // dry run: count the slots
|
||||
slotCap_ = slotsUsed_;
|
||||
Pass(in, out);
|
||||
std::fprintf(stderr,
|
||||
"realigned %llu binds of %zu source buffer(s) to %llu bytes: %llu uploads, relocation buffer %llu "
|
||||
"bytes, %llu calls inserted\n",
|
||||
(unsigned long long)relocBinds_, relocSrc_.size(), (unsigned long long)align,
|
||||
(unsigned long long)slotsUsed_, (unsigned long long)(slotCap_ * stride_),
|
||||
(unsigned long long)inserted_);
|
||||
for (const auto &m : callMap_) {
|
||||
std::printf("call %u -> %llu\n", m.first, (unsigned long long)m.second);
|
||||
}
|
||||
return 0;
|
||||
[[noreturn]] void Fail(const trace::Call* call, const std::string& what) {
|
||||
std::fprintf(stderr, "error: call %u %s: %s\n", call ? call->no : 0u, call ? call->name() : "-", what.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
private:
|
||||
// Pass 0: which buffers need relocating, and the sizes involved.
|
||||
void Scan(const char *in) {
|
||||
trace::Parser p;
|
||||
if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in);
|
||||
trace::Call *call;
|
||||
while ((call = p.parse_call())) {
|
||||
maxSigId_ = std::max(maxSigId_, call->sig->id);
|
||||
if (Is(call, "glBindBufferRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
uint64_t offset = uint64_t(call->arg(3).toSInt());
|
||||
if (IsRangeTarget(target) && offset % align != 0) {
|
||||
relocSrc_.insert(unsigned(call->arg(2).toUInt()));
|
||||
maxRelocSize_ = std::max(maxRelocSize_, uint64_t(call->arg(4).toSInt()));
|
||||
}
|
||||
} else if (Is(call, "glBindBuffersRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
const trace::Array *offsets = call->arg(4).toArray();
|
||||
if (IsRangeTarget(target) && offsets) {
|
||||
for (const trace::Value *v : offsets->values) {
|
||||
if (uint64_t(v->toSInt()) % align != 0) Fail(call, "misaligned glBindBuffersRange is not handled");
|
||||
bool Is(const trace::Call* call, const char* name) {
|
||||
return std::strcmp(call->name(), name) == 0;
|
||||
}
|
||||
|
||||
bool StartsWith(const char* s, const char* prefix) {
|
||||
return std::strncmp(s, prefix, std::strlen(prefix)) == 0;
|
||||
}
|
||||
|
||||
bool ReadsIndexedBuffers(const trace::Call* call) {
|
||||
const char* n = call->name();
|
||||
return StartsWith(n, "glDraw") || StartsWith(n, "glMultiDraw") || StartsWith(n, "glDispatchCompute");
|
||||
}
|
||||
|
||||
bool IsRangeTarget(unsigned target) {
|
||||
return target == kUniformBuffer || target == kShaderStorageBuffer;
|
||||
}
|
||||
|
||||
struct MapRegion {
|
||||
unsigned buffer;
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
uint64_t ptr;
|
||||
};
|
||||
|
||||
struct Reloc {
|
||||
unsigned src = 0;
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
bool dirty = true;
|
||||
std::vector<char> last;
|
||||
const trace::EnumSig* targetSig = nullptr;
|
||||
};
|
||||
|
||||
// Signatures for the calls this tool inserts; ids are allocated past the trace's own.
|
||||
struct Sigs {
|
||||
const char* createNames[2] = {"n", "buffers"};
|
||||
const char* storageNames[4] = {"buffer", "size", "data", "flags"};
|
||||
const char* subDataNames[4] = {"buffer", "offset", "size", "data"};
|
||||
const char* bindRangeNames[5] = {"target", "index", "buffer", "offset", "size"};
|
||||
const char* bindNames[2] = {"target", "buffer"};
|
||||
trace::FunctionSig create{}, storage{}, subData{}, bindRange{}, bind{};
|
||||
|
||||
void Init(trace::Id firstId) {
|
||||
create = {firstId + 0, "glCreateBuffers", 2, createNames};
|
||||
storage = {firstId + 1, "glNamedBufferStorage", 4, storageNames};
|
||||
subData = {firstId + 2, "glNamedBufferSubData", 4, subDataNames};
|
||||
bindRange = {firstId + 3, "glBindBufferRange", 5, bindRangeNames};
|
||||
bind = {firstId + 4, "glBindBuffer", 2, bindNames};
|
||||
}
|
||||
};
|
||||
|
||||
class Rewriter {
|
||||
public:
|
||||
uint64_t align = 1024;
|
||||
std::set<unsigned> mapCalls;
|
||||
|
||||
int Run(const char* in, const char* out) {
|
||||
Scan(in);
|
||||
if (relocSrc_.empty()) {
|
||||
std::fprintf(stderr, "nothing to realign at %llu bytes\n", (unsigned long long)align);
|
||||
return 2;
|
||||
}
|
||||
sigs_.Init(maxSigId_ + 1);
|
||||
stride_ = (maxRelocSize_ + align - 1) / align * align;
|
||||
Pass(in, nullptr); // dry run: count the slots
|
||||
slotCap_ = slotsUsed_;
|
||||
Pass(in, out);
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"realigned %llu binds of %zu source buffer(s) to %llu bytes: %llu uploads, relocation buffer %llu "
|
||||
"bytes, %llu calls inserted\n",
|
||||
(unsigned long long)relocBinds_, relocSrc_.size(), (unsigned long long)align,
|
||||
(unsigned long long)slotsUsed_, (unsigned long long)(slotCap_ * stride_),
|
||||
(unsigned long long)inserted_);
|
||||
for (const auto& m : callMap_) {
|
||||
std::printf("call %u -> %llu\n", m.first, (unsigned long long)m.second);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Pass 0: which buffers need relocating, and the sizes involved.
|
||||
void Scan(const char* in) {
|
||||
trace::Parser p;
|
||||
if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in);
|
||||
trace::Call* call;
|
||||
while ((call = p.parse_call())) {
|
||||
maxSigId_ = std::max(maxSigId_, call->sig->id);
|
||||
if (Is(call, "glBindBufferRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
uint64_t offset = uint64_t(call->arg(3).toSInt());
|
||||
if (IsRangeTarget(target) && offset % align != 0) {
|
||||
relocSrc_.insert(unsigned(call->arg(2).toUInt()));
|
||||
maxRelocSize_ = std::max(maxRelocSize_, uint64_t(call->arg(4).toSInt()));
|
||||
}
|
||||
} else if (Is(call, "glBindBuffersRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
const trace::Array* offsets = call->arg(4).toArray();
|
||||
if (IsRangeTarget(target) && offsets) {
|
||||
for (const trace::Value* v : offsets->values) {
|
||||
if (uint64_t(v->toSInt()) % align != 0)
|
||||
Fail(call, "misaligned glBindBuffersRange is not handled");
|
||||
}
|
||||
}
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
}
|
||||
|
||||
void Pass(const char *in, const char *out) {
|
||||
trace::Parser p;
|
||||
if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in);
|
||||
trace::Writer writer;
|
||||
writer_ = nullptr;
|
||||
if (out) {
|
||||
if (!writer.open(out, p.getVersion(), p.getProperties())) Fail(nullptr, std::string("cannot create ") + out);
|
||||
writer_ = &writer;
|
||||
void Pass(const char* in, const char* out) {
|
||||
trace::Parser p;
|
||||
if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in);
|
||||
trace::Writer writer;
|
||||
writer_ = nullptr;
|
||||
if (out) {
|
||||
if (!writer.open(out, p.getVersion(), p.getProperties()))
|
||||
Fail(nullptr, std::string("cannot create ") + out);
|
||||
writer_ = &writer;
|
||||
}
|
||||
shadow_.clear();
|
||||
maps_.clear();
|
||||
generic_.clear();
|
||||
relocs_.clear();
|
||||
slotsUsed_ = 0;
|
||||
relocBinds_ = 0;
|
||||
inserted_ = 0;
|
||||
created_ = false;
|
||||
|
||||
trace::Call* call;
|
||||
while ((call = p.parse_call())) {
|
||||
Process(call);
|
||||
delete call;
|
||||
}
|
||||
if (writer_) writer.close();
|
||||
writer_ = nullptr;
|
||||
}
|
||||
shadow_.clear();
|
||||
maps_.clear();
|
||||
generic_.clear();
|
||||
relocs_.clear();
|
||||
slotsUsed_ = 0;
|
||||
relocBinds_ = 0;
|
||||
inserted_ = 0;
|
||||
created_ = false;
|
||||
|
||||
trace::Call *call;
|
||||
while ((call = p.parse_call())) {
|
||||
Process(call);
|
||||
delete call;
|
||||
unsigned BufferArg(trace::Call* call, unsigned index) { return unsigned(call->arg(index).toUInt()); }
|
||||
|
||||
unsigned Generic(unsigned target) {
|
||||
auto it = generic_.find(target);
|
||||
return it == generic_.end() ? 0 : it->second;
|
||||
}
|
||||
if (writer_) writer.close();
|
||||
writer_ = nullptr;
|
||||
}
|
||||
|
||||
unsigned BufferArg(trace::Call *call, unsigned index) { return unsigned(call->arg(index).toUInt()); }
|
||||
|
||||
unsigned Generic(unsigned target) {
|
||||
auto it = generic_.find(target);
|
||||
return it == generic_.end() ? 0 : it->second;
|
||||
}
|
||||
|
||||
std::vector<char> *Shadow(unsigned buffer) {
|
||||
auto it = shadow_.find(buffer);
|
||||
return it == shadow_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
void Specify(trace::Call *call, unsigned buffer, uint64_t size, trace::Value *data) {
|
||||
if (!relocSrc_.count(buffer)) return;
|
||||
std::vector<char> &s = shadow_[buffer];
|
||||
s.assign(size, 0);
|
||||
const trace::Blob *blob = data ? data->toBlob() : nullptr;
|
||||
if (blob) std::memcpy(s.data(), blob->buf, std::min<uint64_t>(size, blob->size));
|
||||
for (auto &r : relocs_) {
|
||||
if (r.second.src == buffer) r.second.dirty = true;
|
||||
std::vector<char>* Shadow(unsigned buffer) {
|
||||
auto it = shadow_.find(buffer);
|
||||
return it == shadow_.end() ? nullptr : &it->second;
|
||||
}
|
||||
(void)call;
|
||||
}
|
||||
|
||||
void Write(trace::Call *call, unsigned buffer, uint64_t offset, const char *bytes, uint64_t size) {
|
||||
std::vector<char> *s = Shadow(buffer);
|
||||
if (!s) return;
|
||||
if (offset + size > s->size()) Fail(call, "write past the end of a tracked buffer");
|
||||
std::memcpy(s->data() + offset, bytes, size);
|
||||
}
|
||||
void Specify(trace::Call* call, unsigned buffer, uint64_t size, trace::Value* data) {
|
||||
if (!relocSrc_.count(buffer)) return;
|
||||
std::vector<char>& s = shadow_[buffer];
|
||||
s.assign(size, 0);
|
||||
const trace::Blob* blob = data ? data->toBlob() : nullptr;
|
||||
if (blob) std::memcpy(s.data(), blob->buf, std::min<uint64_t>(size, blob->size));
|
||||
for (auto& r : relocs_) {
|
||||
if (r.second.src == buffer) r.second.dirty = true;
|
||||
}
|
||||
(void)call;
|
||||
}
|
||||
|
||||
void Refuse(trace::Call *call, unsigned buffer) {
|
||||
if (relocSrc_.count(buffer)) Fail(call, "writes a relocated source buffer in a way the trace does not carry the bytes of");
|
||||
}
|
||||
void Write(trace::Call* call, unsigned buffer, uint64_t offset, const char* bytes, uint64_t size) {
|
||||
std::vector<char>* s = Shadow(buffer);
|
||||
if (!s) return;
|
||||
if (offset + size > s->size()) Fail(call, "write past the end of a tracked buffer");
|
||||
std::memcpy(s->data() + offset, bytes, size);
|
||||
}
|
||||
|
||||
void Emit(trace::Call *call) {
|
||||
if (!writer_) return;
|
||||
if (mapCalls.count(call->no)) callMap_[call->no] = writtenCalls_;
|
||||
writer_->writeCall(call);
|
||||
++writtenCalls_;
|
||||
}
|
||||
void Refuse(trace::Call* call, unsigned buffer) {
|
||||
if (relocSrc_.count(buffer))
|
||||
Fail(call, "writes a relocated source buffer in a way the trace does not carry the bytes of");
|
||||
}
|
||||
|
||||
void EmitNew(trace::Call *call) {
|
||||
++inserted_;
|
||||
if (writer_) {
|
||||
void Emit(trace::Call* call) {
|
||||
if (!writer_) return;
|
||||
if (mapCalls.count(call->no)) callMap_[call->no] = writtenCalls_;
|
||||
writer_->writeCall(call);
|
||||
++writtenCalls_;
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
|
||||
trace::Call *NewCall(const trace::FunctionSig *sig, unsigned thread) {
|
||||
return new trace::Call(sig, trace::CallFlags(0), thread);
|
||||
}
|
||||
|
||||
void EmitBind(unsigned thread, const trace::EnumSig *targetSig, unsigned target, unsigned buffer) {
|
||||
trace::Call *c = NewCall(&sigs_.bind, thread);
|
||||
c->args[0].value = targetSig ? static_cast<trace::Value *>(new trace::Enum(targetSig, target))
|
||||
: static_cast<trace::Value *>(new trace::SInt(target));
|
||||
c->args[1].value = new trace::UInt(buffer);
|
||||
EmitNew(c);
|
||||
}
|
||||
|
||||
void CreateRelocBuffer(unsigned thread) {
|
||||
trace::Call *c = NewCall(&sigs_.create, thread);
|
||||
c->args[0].value = new trace::SInt(1);
|
||||
trace::Array *names = new trace::Array(1);
|
||||
names->values[0] = new trace::UInt(kRelocBufferName);
|
||||
c->args[1].value = names;
|
||||
EmitNew(c);
|
||||
|
||||
c = NewCall(&sigs_.storage, thread);
|
||||
c->args[0].value = new trace::UInt(kRelocBufferName);
|
||||
c->args[1].value = new trace::SInt(int64_t(slotCap_ * stride_));
|
||||
c->args[2].value = new trace::Null();
|
||||
c->args[3].value = new trace::UInt(kDynamicStorageBit);
|
||||
EmitNew(c);
|
||||
created_ = true;
|
||||
}
|
||||
|
||||
void Upload(trace::Call *before, unsigned target, unsigned index, Reloc &r) {
|
||||
std::vector<char> *s = Shadow(r.src);
|
||||
if (!s) Fail(before, "a relocated range reads a buffer whose storage the trace never specified");
|
||||
if (r.offset + r.size > s->size()) Fail(before, "a relocated range runs past its buffer");
|
||||
std::vector<char> bytes(s->begin() + r.offset, s->begin() + r.offset + r.size);
|
||||
if (!r.dirty && bytes == r.last) return;
|
||||
|
||||
unsigned thread = before->thread_id;
|
||||
if (writer_ && slotsUsed_ >= slotCap_) Fail(before, "relocation slots exceeded the dry run");
|
||||
uint64_t slotOffset = slotsUsed_++ * stride_;
|
||||
if (!created_) CreateRelocBuffer(thread);
|
||||
|
||||
trace::Call *c = NewCall(&sigs_.subData, thread);
|
||||
c->args[0].value = new trace::UInt(kRelocBufferName);
|
||||
c->args[1].value = new trace::SInt(int64_t(slotOffset));
|
||||
c->args[2].value = new trace::SInt(int64_t(r.size));
|
||||
trace::Blob *blob = new trace::Blob(r.size);
|
||||
std::memcpy(blob->buf, bytes.data(), r.size);
|
||||
c->args[3].value = blob;
|
||||
EmitNew(c);
|
||||
|
||||
c = NewCall(&sigs_.bindRange, thread);
|
||||
c->args[0].value = r.targetSig ? static_cast<trace::Value *>(new trace::Enum(r.targetSig, target))
|
||||
: static_cast<trace::Value *>(new trace::SInt(target));
|
||||
c->args[1].value = new trace::UInt(index);
|
||||
c->args[2].value = new trace::UInt(kRelocBufferName);
|
||||
c->args[3].value = new trace::SInt(int64_t(slotOffset));
|
||||
c->args[4].value = new trace::SInt(int64_t(r.size));
|
||||
EmitNew(c);
|
||||
|
||||
// glBindBufferRange also moved the generic binding; put back the app's.
|
||||
EmitBind(thread, r.targetSig, target, Generic(target));
|
||||
|
||||
r.last = std::move(bytes);
|
||||
r.dirty = false;
|
||||
}
|
||||
|
||||
void Process(trace::Call *call) {
|
||||
const char *n = call->name();
|
||||
|
||||
if (Is(call, "glBindBuffer")) {
|
||||
generic_[unsigned(call->arg(0).toSInt())] = BufferArg(call, 1);
|
||||
} else if (Is(call, "glBindBufferBase")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
generic_[target] = BufferArg(call, 2);
|
||||
relocs_.erase({target, unsigned(call->arg(1).toUInt())});
|
||||
} else if (Is(call, "glBindBufferRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
unsigned index = unsigned(call->arg(1).toUInt());
|
||||
unsigned buffer = BufferArg(call, 2);
|
||||
uint64_t offset = uint64_t(call->arg(3).toSInt());
|
||||
generic_[target] = buffer;
|
||||
if (IsRangeTarget(target) && offset % align != 0) {
|
||||
Reloc r;
|
||||
r.src = buffer;
|
||||
r.offset = offset;
|
||||
r.size = uint64_t(call->arg(4).toSInt());
|
||||
trace::Value &t = call->arg(0);
|
||||
trace::Enum *e = nullptr;
|
||||
// Enum values print by name in dumps; keep the trace's own enum signature when it has one.
|
||||
struct EnumProbe : trace::Visitor {
|
||||
trace::Enum *found = nullptr;
|
||||
void visit(trace::Enum *node) override { found = node; }
|
||||
} probe;
|
||||
t.visit(probe);
|
||||
e = probe.found;
|
||||
r.targetSig = e ? e->sig : nullptr;
|
||||
relocs_[{target, index}] = std::move(r);
|
||||
++relocBinds_;
|
||||
if (mapCalls.count(call->no) && writer_) callMap_[call->no] = writtenCalls_;
|
||||
// Only the generic binding survives at this point; the indexed one is bound before its first read.
|
||||
EmitBind(call->thread_id, e ? e->sig : nullptr, target, buffer);
|
||||
--inserted_; // a replacement, not an insertion
|
||||
return;
|
||||
void EmitNew(trace::Call* call) {
|
||||
++inserted_;
|
||||
if (writer_) {
|
||||
writer_->writeCall(call);
|
||||
++writtenCalls_;
|
||||
}
|
||||
relocs_.erase({target, index});
|
||||
} else if (Is(call, "glBindBuffersBase") || Is(call, "glBindBuffersRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
unsigned first = unsigned(call->arg(1).toUInt());
|
||||
unsigned count = unsigned(call->arg(2).toSInt());
|
||||
for (unsigned i = first; i < first + count; ++i) relocs_.erase({target, i});
|
||||
} else if (Is(call, "glNamedBufferStorage") || Is(call, "glNamedBufferData")) {
|
||||
Specify(call, BufferArg(call, 0), uint64_t(call->arg(1).toSInt()), &call->arg(2));
|
||||
} else if (Is(call, "glBufferStorage") || Is(call, "glBufferData")) {
|
||||
Specify(call, Generic(unsigned(call->arg(0).toSInt())), uint64_t(call->arg(1).toSInt()), &call->arg(2));
|
||||
} else if (Is(call, "glNamedBufferSubData") || Is(call, "glBufferSubData")) {
|
||||
unsigned buffer = Is(call, "glNamedBufferSubData") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
if (relocSrc_.count(buffer)) {
|
||||
const trace::Blob *blob = call->arg(3).toBlob();
|
||||
if (!blob) Fail(call, "subdata without bytes");
|
||||
Write(call, buffer, uint64_t(call->arg(1).toSInt()), blob->buf,
|
||||
std::min<uint64_t>(uint64_t(call->arg(2).toSInt()), blob->size));
|
||||
}
|
||||
} else if (Is(call, "glCopyNamedBufferSubData")) {
|
||||
Refuse(call, BufferArg(call, 1));
|
||||
} else if (Is(call, "glCopyBufferSubData")) {
|
||||
Refuse(call, Generic(unsigned(call->arg(1).toSInt())));
|
||||
} else if (StartsWith(n, "glClearNamedBuffer")) {
|
||||
Refuse(call, BufferArg(call, 0));
|
||||
} else if (StartsWith(n, "glClearBuffer") && (StartsWith(n, "glClearBufferData") || StartsWith(n, "glClearBufferSubData"))) {
|
||||
Refuse(call, Generic(unsigned(call->arg(0).toSInt())));
|
||||
} else if (Is(call, "glMapNamedBufferRange") || Is(call, "glMapBufferRange")) {
|
||||
unsigned buffer = Is(call, "glMapNamedBufferRange") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
if (relocSrc_.count(buffer) && call->ret) {
|
||||
maps_.push_back({buffer, uint64_t(call->arg(1).toSInt()), uint64_t(call->arg(2).toSInt()),
|
||||
call->ret->toUIntPtr()});
|
||||
}
|
||||
} else if (Is(call, "glMapNamedBuffer") || Is(call, "glMapBuffer")) {
|
||||
unsigned buffer = Is(call, "glMapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
std::vector<char> *s = Shadow(buffer);
|
||||
if (s && call->ret) maps_.push_back({buffer, 0, s->size(), call->ret->toUIntPtr()});
|
||||
} else if (Is(call, "glUnmapNamedBuffer") || Is(call, "glUnmapBuffer")) {
|
||||
unsigned buffer = Is(call, "glUnmapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
maps_.erase(std::remove_if(maps_.begin(), maps_.end(), [&](const MapRegion &m) { return m.buffer == buffer; }),
|
||||
maps_.end());
|
||||
} else if (Is(call, "memcpy")) {
|
||||
uint64_t dest = call->arg(0).toUIntPtr();
|
||||
const trace::Blob *blob = call->arg(1).toBlob();
|
||||
uint64_t size = call->arg(2).toUInt();
|
||||
if (blob) {
|
||||
size = std::min<uint64_t>(size, blob->size);
|
||||
for (const MapRegion &m : maps_) {
|
||||
uint64_t lo = std::max(dest, m.ptr);
|
||||
uint64_t hi = std::min(dest + size, m.ptr + m.length);
|
||||
if (lo >= hi) continue;
|
||||
Write(call, m.buffer, m.offset + (lo - m.ptr), blob->buf + (lo - dest), hi - lo);
|
||||
}
|
||||
}
|
||||
} else if (ReadsIndexedBuffers(call)) {
|
||||
for (auto &r : relocs_) Upload(call, r.first.first, r.first.second, r.second);
|
||||
delete call;
|
||||
}
|
||||
|
||||
Emit(call);
|
||||
}
|
||||
trace::Call* NewCall(const trace::FunctionSig* sig, unsigned thread) {
|
||||
return new trace::Call(sig, trace::CallFlags(0), thread);
|
||||
}
|
||||
|
||||
Sigs sigs_;
|
||||
trace::Id maxSigId_ = 0;
|
||||
std::set<unsigned> relocSrc_;
|
||||
uint64_t maxRelocSize_ = 0;
|
||||
uint64_t stride_ = 0;
|
||||
uint64_t slotCap_ = 0;
|
||||
uint64_t slotsUsed_ = 0;
|
||||
uint64_t relocBinds_ = 0;
|
||||
uint64_t inserted_ = 0;
|
||||
uint64_t writtenCalls_ = 0;
|
||||
bool created_ = false;
|
||||
trace::Writer *writer_ = nullptr;
|
||||
std::unordered_map<unsigned, std::vector<char>> shadow_;
|
||||
std::vector<MapRegion> maps_;
|
||||
std::unordered_map<unsigned, unsigned> generic_;
|
||||
std::map<std::pair<unsigned, unsigned>, Reloc> relocs_;
|
||||
std::map<unsigned, uint64_t> callMap_;
|
||||
};
|
||||
void EmitBind(unsigned thread, const trace::EnumSig* targetSig, unsigned target, unsigned buffer) {
|
||||
trace::Call* c = NewCall(&sigs_.bind, thread);
|
||||
c->args[0].value = targetSig ? static_cast<trace::Value*>(new trace::Enum(targetSig, target))
|
||||
: static_cast<trace::Value*>(new trace::SInt(target));
|
||||
c->args[1].value = new trace::UInt(buffer);
|
||||
EmitNew(c);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
void CreateRelocBuffer(unsigned thread) {
|
||||
trace::Call* c = NewCall(&sigs_.create, thread);
|
||||
c->args[0].value = new trace::SInt(1);
|
||||
trace::Array* names = new trace::Array(1);
|
||||
names->values[0] = new trace::UInt(kRelocBufferName);
|
||||
c->args[1].value = names;
|
||||
EmitNew(c);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
c = NewCall(&sigs_.storage, thread);
|
||||
c->args[0].value = new trace::UInt(kRelocBufferName);
|
||||
c->args[1].value = new trace::SInt(int64_t(slotCap_ * stride_));
|
||||
c->args[2].value = new trace::Null();
|
||||
c->args[3].value = new trace::UInt(kDynamicStorageBit);
|
||||
EmitNew(c);
|
||||
created_ = true;
|
||||
}
|
||||
|
||||
void Upload(trace::Call* before, unsigned target, unsigned index, Reloc& r) {
|
||||
std::vector<char>* s = Shadow(r.src);
|
||||
if (!s) Fail(before, "a relocated range reads a buffer whose storage the trace never specified");
|
||||
if (r.offset + r.size > s->size()) Fail(before, "a relocated range runs past its buffer");
|
||||
std::vector<char> bytes(s->begin() + r.offset, s->begin() + r.offset + r.size);
|
||||
if (!r.dirty && bytes == r.last) return;
|
||||
|
||||
unsigned thread = before->thread_id;
|
||||
if (writer_ && slotsUsed_ >= slotCap_) Fail(before, "relocation slots exceeded the dry run");
|
||||
uint64_t slotOffset = slotsUsed_++ * stride_;
|
||||
if (!created_) CreateRelocBuffer(thread);
|
||||
|
||||
trace::Call* c = NewCall(&sigs_.subData, thread);
|
||||
c->args[0].value = new trace::UInt(kRelocBufferName);
|
||||
c->args[1].value = new trace::SInt(int64_t(slotOffset));
|
||||
c->args[2].value = new trace::SInt(int64_t(r.size));
|
||||
trace::Blob* blob = new trace::Blob(r.size);
|
||||
std::memcpy(blob->buf, bytes.data(), r.size);
|
||||
c->args[3].value = blob;
|
||||
EmitNew(c);
|
||||
|
||||
c = NewCall(&sigs_.bindRange, thread);
|
||||
c->args[0].value = r.targetSig ? static_cast<trace::Value*>(new trace::Enum(r.targetSig, target))
|
||||
: static_cast<trace::Value*>(new trace::SInt(target));
|
||||
c->args[1].value = new trace::UInt(index);
|
||||
c->args[2].value = new trace::UInt(kRelocBufferName);
|
||||
c->args[3].value = new trace::SInt(int64_t(slotOffset));
|
||||
c->args[4].value = new trace::SInt(int64_t(r.size));
|
||||
EmitNew(c);
|
||||
|
||||
// glBindBufferRange also moved the generic binding; put back the app's.
|
||||
EmitBind(thread, r.targetSig, target, Generic(target));
|
||||
|
||||
r.last = std::move(bytes);
|
||||
r.dirty = false;
|
||||
}
|
||||
|
||||
void Process(trace::Call* call) {
|
||||
const char* n = call->name();
|
||||
|
||||
if (Is(call, "glBindBuffer")) {
|
||||
generic_[unsigned(call->arg(0).toSInt())] = BufferArg(call, 1);
|
||||
} else if (Is(call, "glBindBufferBase")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
generic_[target] = BufferArg(call, 2);
|
||||
relocs_.erase({target, unsigned(call->arg(1).toUInt())});
|
||||
} else if (Is(call, "glBindBufferRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
unsigned index = unsigned(call->arg(1).toUInt());
|
||||
unsigned buffer = BufferArg(call, 2);
|
||||
uint64_t offset = uint64_t(call->arg(3).toSInt());
|
||||
generic_[target] = buffer;
|
||||
if (IsRangeTarget(target) && offset % align != 0) {
|
||||
Reloc r;
|
||||
r.src = buffer;
|
||||
r.offset = offset;
|
||||
r.size = uint64_t(call->arg(4).toSInt());
|
||||
trace::Value& t = call->arg(0);
|
||||
trace::Enum* e = nullptr;
|
||||
// Enum values print by name in dumps; keep the trace's own enum signature when it has one.
|
||||
struct EnumProbe : trace::Visitor {
|
||||
trace::Enum* found = nullptr;
|
||||
void visit(trace::Enum* node) override { found = node; }
|
||||
} probe;
|
||||
t.visit(probe);
|
||||
e = probe.found;
|
||||
r.targetSig = e ? e->sig : nullptr;
|
||||
relocs_[{target, index}] = std::move(r);
|
||||
++relocBinds_;
|
||||
if (mapCalls.count(call->no) && writer_) callMap_[call->no] = writtenCalls_;
|
||||
// Only the generic binding survives at this point; the indexed one is bound before its first read.
|
||||
EmitBind(call->thread_id, e ? e->sig : nullptr, target, buffer);
|
||||
--inserted_; // a replacement, not an insertion
|
||||
return;
|
||||
}
|
||||
relocs_.erase({target, index});
|
||||
} else if (Is(call, "glBindBuffersBase") || Is(call, "glBindBuffersRange")) {
|
||||
unsigned target = unsigned(call->arg(0).toSInt());
|
||||
unsigned first = unsigned(call->arg(1).toUInt());
|
||||
unsigned count = unsigned(call->arg(2).toSInt());
|
||||
for (unsigned i = first; i < first + count; ++i)
|
||||
relocs_.erase({target, i});
|
||||
} else if (Is(call, "glNamedBufferStorage") || Is(call, "glNamedBufferData")) {
|
||||
Specify(call, BufferArg(call, 0), uint64_t(call->arg(1).toSInt()), &call->arg(2));
|
||||
} else if (Is(call, "glBufferStorage") || Is(call, "glBufferData")) {
|
||||
Specify(call, Generic(unsigned(call->arg(0).toSInt())), uint64_t(call->arg(1).toSInt()), &call->arg(2));
|
||||
} else if (Is(call, "glNamedBufferSubData") || Is(call, "glBufferSubData")) {
|
||||
unsigned buffer =
|
||||
Is(call, "glNamedBufferSubData") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
if (relocSrc_.count(buffer)) {
|
||||
const trace::Blob* blob = call->arg(3).toBlob();
|
||||
if (!blob) Fail(call, "subdata without bytes");
|
||||
Write(call, buffer, uint64_t(call->arg(1).toSInt()), blob->buf,
|
||||
std::min<uint64_t>(uint64_t(call->arg(2).toSInt()), blob->size));
|
||||
}
|
||||
} else if (Is(call, "glCopyNamedBufferSubData")) {
|
||||
Refuse(call, BufferArg(call, 1));
|
||||
} else if (Is(call, "glCopyBufferSubData")) {
|
||||
Refuse(call, Generic(unsigned(call->arg(1).toSInt())));
|
||||
} else if (StartsWith(n, "glClearNamedBuffer")) {
|
||||
Refuse(call, BufferArg(call, 0));
|
||||
} else if (StartsWith(n, "glClearBuffer") &&
|
||||
(StartsWith(n, "glClearBufferData") || StartsWith(n, "glClearBufferSubData"))) {
|
||||
Refuse(call, Generic(unsigned(call->arg(0).toSInt())));
|
||||
} else if (Is(call, "glMapNamedBufferRange") || Is(call, "glMapBufferRange")) {
|
||||
unsigned buffer =
|
||||
Is(call, "glMapNamedBufferRange") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
if (relocSrc_.count(buffer) && call->ret) {
|
||||
maps_.push_back({buffer, uint64_t(call->arg(1).toSInt()), uint64_t(call->arg(2).toSInt()),
|
||||
call->ret->toUIntPtr()});
|
||||
}
|
||||
} else if (Is(call, "glMapNamedBuffer") || Is(call, "glMapBuffer")) {
|
||||
unsigned buffer =
|
||||
Is(call, "glMapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
std::vector<char>* s = Shadow(buffer);
|
||||
if (s && call->ret) maps_.push_back({buffer, 0, s->size(), call->ret->toUIntPtr()});
|
||||
} else if (Is(call, "glUnmapNamedBuffer") || Is(call, "glUnmapBuffer")) {
|
||||
unsigned buffer =
|
||||
Is(call, "glUnmapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt()));
|
||||
maps_.erase(
|
||||
std::remove_if(maps_.begin(), maps_.end(), [&](const MapRegion& m) { return m.buffer == buffer; }),
|
||||
maps_.end());
|
||||
} else if (Is(call, "memcpy")) {
|
||||
uint64_t dest = call->arg(0).toUIntPtr();
|
||||
const trace::Blob* blob = call->arg(1).toBlob();
|
||||
uint64_t size = call->arg(2).toUInt();
|
||||
if (blob) {
|
||||
size = std::min<uint64_t>(size, blob->size);
|
||||
for (const MapRegion& m : maps_) {
|
||||
uint64_t lo = std::max(dest, m.ptr);
|
||||
uint64_t hi = std::min(dest + size, m.ptr + m.length);
|
||||
if (lo >= hi) continue;
|
||||
Write(call, m.buffer, m.offset + (lo - m.ptr), blob->buf + (lo - dest), hi - lo);
|
||||
}
|
||||
}
|
||||
} else if (ReadsIndexedBuffers(call)) {
|
||||
for (auto& r : relocs_)
|
||||
Upload(call, r.first.first, r.first.second, r.second);
|
||||
}
|
||||
|
||||
Emit(call);
|
||||
}
|
||||
|
||||
Sigs sigs_;
|
||||
trace::Id maxSigId_ = 0;
|
||||
std::set<unsigned> relocSrc_;
|
||||
uint64_t maxRelocSize_ = 0;
|
||||
uint64_t stride_ = 0;
|
||||
uint64_t slotCap_ = 0;
|
||||
uint64_t slotsUsed_ = 0;
|
||||
uint64_t relocBinds_ = 0;
|
||||
uint64_t inserted_ = 0;
|
||||
uint64_t writtenCalls_ = 0;
|
||||
bool created_ = false;
|
||||
trace::Writer* writer_ = nullptr;
|
||||
std::unordered_map<unsigned, std::vector<char>> shadow_;
|
||||
std::vector<MapRegion> maps_;
|
||||
std::unordered_map<unsigned, unsigned> generic_;
|
||||
std::map<std::pair<unsigned, unsigned>, Reloc> relocs_;
|
||||
std::map<unsigned, uint64_t> callMap_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Rewriter rw;
|
||||
std::vector<const char *> files;
|
||||
std::vector<const char*> files;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!std::strcmp(argv[i], "--align") && i + 1 < argc) {
|
||||
rw.align = std::strtoull(argv[++i], nullptr, 0);
|
||||
|
||||
@@ -26,50 +26,47 @@
|
||||
|
||||
namespace {
|
||||
|
||||
int gFailures = 0;
|
||||
int gFailures = 0;
|
||||
|
||||
void ExpectSplit(const std::string& input, const std::vector<std::string>& expected) {
|
||||
const std::vector<std::string> actual = mobilegl_trace::SplitSemicolonList(input);
|
||||
if (actual == expected) {
|
||||
return;
|
||||
void ExpectSplit(const std::string& input, const std::vector<std::string>& expected) {
|
||||
const std::vector<std::string> actual = mobilegl_trace::SplitSemicolonList(input);
|
||||
if (actual == expected) {
|
||||
return;
|
||||
}
|
||||
++gFailures;
|
||||
std::cerr << "SplitSemicolonList(\"" << input << "\") gave " << actual.size() << " entries, expected "
|
||||
<< expected.size() << ":";
|
||||
for (const std::string& entry : actual) {
|
||||
std::cerr << " [" << entry << "]";
|
||||
}
|
||||
std::cerr << "\n";
|
||||
}
|
||||
++gFailures;
|
||||
std::cerr << "SplitSemicolonList(\"" << input << "\") gave " << actual.size()
|
||||
<< " entries, expected " << expected.size() << ":";
|
||||
for (const std::string& entry : actual) {
|
||||
std::cerr << " [" << entry << "]";
|
||||
}
|
||||
std::cerr << "\n";
|
||||
}
|
||||
|
||||
const char* ActionName(mobilegl_trace::EnvOverrideAction action) {
|
||||
switch (action) {
|
||||
const char* ActionName(mobilegl_trace::EnvOverrideAction action) {
|
||||
switch (action) {
|
||||
case mobilegl_trace::EnvOverrideAction::Ignore:
|
||||
return "Ignore";
|
||||
case mobilegl_trace::EnvOverrideAction::Set:
|
||||
return "Set";
|
||||
case mobilegl_trace::EnvOverrideAction::Unset:
|
||||
return "Unset";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
void ExpectParse(const std::string& entry,
|
||||
mobilegl_trace::EnvOverrideAction expectedAction,
|
||||
const std::string& expectedKey,
|
||||
const std::string& expectedValue) {
|
||||
std::string key = "<untouched>";
|
||||
std::string value = "<untouched>";
|
||||
const mobilegl_trace::EnvOverrideAction action =
|
||||
mobilegl_trace::ParseEnvOverride(entry, &key, &value);
|
||||
if (action == expectedAction && key == expectedKey && value == expectedValue) {
|
||||
return;
|
||||
void ExpectParse(const std::string& entry, mobilegl_trace::EnvOverrideAction expectedAction,
|
||||
const std::string& expectedKey, const std::string& expectedValue) {
|
||||
std::string key = "<untouched>";
|
||||
std::string value = "<untouched>";
|
||||
const mobilegl_trace::EnvOverrideAction action = mobilegl_trace::ParseEnvOverride(entry, &key, &value);
|
||||
if (action == expectedAction && key == expectedKey && value == expectedValue) {
|
||||
return;
|
||||
}
|
||||
++gFailures;
|
||||
std::cerr << "ParseEnvOverride(\"" << entry << "\") gave " << ActionName(action) << " key=[" << key
|
||||
<< "] value=[" << value << "], expected " << ActionName(expectedAction) << " key=[" << expectedKey
|
||||
<< "] value=[" << expectedValue << "]\n";
|
||||
}
|
||||
++gFailures;
|
||||
std::cerr << "ParseEnvOverride(\"" << entry << "\") gave " << ActionName(action) << " key=["
|
||||
<< key << "] value=[" << value << "], expected " << ActionName(expectedAction)
|
||||
<< " key=[" << expectedKey << "] value=[" << expectedValue << "]\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -79,8 +76,7 @@ int main() {
|
||||
// Splitting.
|
||||
ExpectSplit("", {});
|
||||
ExpectSplit("MOBILEGL_PIPE_PUSH=1", {"MOBILEGL_PIPE_PUSH=1"});
|
||||
ExpectSplit("MOBILEGL_PIPE_PUSH=1;MOBILEGL_PIPE_VERIFY=1",
|
||||
{"MOBILEGL_PIPE_PUSH=1", "MOBILEGL_PIPE_VERIFY=1"});
|
||||
ExpectSplit("MOBILEGL_PIPE_PUSH=1;MOBILEGL_PIPE_VERIFY=1", {"MOBILEGL_PIPE_PUSH=1", "MOBILEGL_PIPE_VERIFY=1"});
|
||||
// A trailing ';' is what a caller that joins a list gets for free, and an empty entry
|
||||
// must not be turned into an unsetenv("") - the whole passthrough would then depend on
|
||||
// how carefully the shell script trimmed its own string.
|
||||
|
||||
@@ -17,206 +17,206 @@ extern "C" void mobilegl_trace_set_requested_size(int width, int height);
|
||||
|
||||
namespace {
|
||||
|
||||
void PrintUsage(const char *argv0) {
|
||||
std::cerr
|
||||
<< "Usage: " << argv0 << " --trace PATH --target-call N --output DIR [options]\n"
|
||||
<< "\n"
|
||||
<< "Options:\n"
|
||||
<< " --golden PATH Golden PNG to compare against\n"
|
||||
<< " --alternate-golden PATH Additional acceptable golden PNG\n"
|
||||
<< " --diff PATH Difference PNG output path\n"
|
||||
<< " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n"
|
||||
<< " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
|
||||
<< " --width N Replay surface width override\n"
|
||||
<< " --height N Replay surface height override\n"
|
||||
<< " --window-surface Replay to a native window surface\n"
|
||||
<< " --pbuffer-surface Replay to an EGL pbuffer surface (default)\n"
|
||||
<< " --hold-ms N Keep the replay process alive after retrace (default: 0)\n"
|
||||
<< " --ssim-threshold N Minimum SSIM required to pass (default: 0.99)\n"
|
||||
<< " --crop-x N Compare crop x\n"
|
||||
<< " --crop-y N Compare crop y\n"
|
||||
<< " --crop-width N Compare crop width\n"
|
||||
<< " --crop-height N Compare crop height\n"
|
||||
<< " --coherent-as-flush Set MOBILEGL_COHERENT_AS_FLUSH=1 for the replay\n"
|
||||
<< " --benchmark Frame-timing mode: replay the whole trace, record a\n"
|
||||
<< " wall-clock timestamp at every frame boundary, and skip\n"
|
||||
<< " the snapshot and the golden comparison. --target-call is\n"
|
||||
<< " not required in this mode.\n"
|
||||
<< " --benchmark-tail-frames=N Frames at the end of the run the mean/median/p95 are\n"
|
||||
<< " computed over (default: 200, clamped to the frame count)\n"
|
||||
<< " --benchmark-finish=0/1 glFinish at every frame boundary so a frame time covers\n"
|
||||
<< " GPU completion and not just CPU submission (default: 1)\n"
|
||||
<< " --benchmark-result=PATH Timing JSON output (default: OUTPUT/benchmark.json)\n"
|
||||
<< " --dump-fbo-attachments CALL:DIR[:FBO,FBO,...]\n"
|
||||
<< " At CALL, write every colour attachment and the depth\n"
|
||||
<< " attachment of every live framebuffer object into DIR as\n"
|
||||
<< " fbo<N>-att<M>.png / fbo<N>-depth.png, plus a manifest.txt\n"
|
||||
<< " of formats and per-channel statistics. Repeatable.\n";
|
||||
}
|
||||
|
||||
bool ReadValue(int argc, char **argv, int &index, std::string &out) {
|
||||
if (index + 1 >= argc) {
|
||||
std::cerr << "Missing value for " << argv[index] << "\n";
|
||||
return false;
|
||||
}
|
||||
out = argv[++index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadInt(int argc, char **argv, int &index, int &out) {
|
||||
std::string value;
|
||||
if (!ReadValue(argc, argv, index, value)) {
|
||||
return false;
|
||||
}
|
||||
out = std::atoi(value.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadLongLong(int argc, char **argv, int &index, long long &out) {
|
||||
std::string value;
|
||||
if (!ReadValue(argc, argv, index, value)) {
|
||||
return false;
|
||||
}
|
||||
out = std::strtoll(value.c_str(), nullptr, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadDouble(int argc, char **argv, int &index, double &out) {
|
||||
std::string value;
|
||||
if (!ReadValue(argc, argv, index, value)) {
|
||||
return false;
|
||||
}
|
||||
out = std::strtod(value.c_str(), nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The benchmark options are documented as --name=VALUE. The space-separated spelling every
|
||||
// other option here uses is accepted too, so --benchmark-tail-frames=300 and
|
||||
// --benchmark-tail-frames 300 both work; hasInlineValue says which one was given.
|
||||
bool MatchOption(const std::string &arg, const char *name, std::string &inlineValue, bool &hasInlineValue) {
|
||||
if (arg == name) {
|
||||
hasInlineValue = false;
|
||||
return true;
|
||||
}
|
||||
const std::string prefix = std::string(name) + "=";
|
||||
if (arg.compare(0, prefix.size(), prefix) == 0) {
|
||||
inlineValue = arg.substr(prefix.size());
|
||||
hasInlineValue = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) {
|
||||
request.backend = "DirectGLES";
|
||||
// MOBILEGL_ITRP_FIX is a tri-state (Auto/ForceOn/ForceOff); the request only carries ForceOn.
|
||||
if (const char *itrpFix = std::getenv("MOBILEGL_ITRP_FIX")) {
|
||||
std::string value = itrpFix;
|
||||
for (char &ch : value) ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
request.iterationRPFix = value == "1" || value == "true" || value == "on" || value == "forceon";
|
||||
void PrintUsage(const char* argv0) {
|
||||
std::cerr << "Usage: " << argv0 << " --trace PATH --target-call N --output DIR [options]\n"
|
||||
<< "\n"
|
||||
<< "Options:\n"
|
||||
<< " --golden PATH Golden PNG to compare against\n"
|
||||
<< " --alternate-golden PATH Additional acceptable golden PNG\n"
|
||||
<< " --diff PATH Difference PNG output path\n"
|
||||
<< " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n"
|
||||
<< " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
|
||||
<< " --width N Replay surface width override\n"
|
||||
<< " --height N Replay surface height override\n"
|
||||
<< " --window-surface Replay to a native window surface\n"
|
||||
<< " --pbuffer-surface Replay to an EGL pbuffer surface (default)\n"
|
||||
<< " --hold-ms N Keep the replay process alive after retrace (default: 0)\n"
|
||||
<< " --ssim-threshold N Minimum SSIM required to pass (default: 0.99)\n"
|
||||
<< " --crop-x N Compare crop x\n"
|
||||
<< " --crop-y N Compare crop y\n"
|
||||
<< " --crop-width N Compare crop width\n"
|
||||
<< " --crop-height N Compare crop height\n"
|
||||
<< " --coherent-as-flush Set MOBILEGL_COHERENT_AS_FLUSH=1 for the replay\n"
|
||||
<< " --benchmark Frame-timing mode: replay the whole trace, record a\n"
|
||||
<< " wall-clock timestamp at every frame boundary, and skip\n"
|
||||
<< " the snapshot and the golden comparison. --target-call is\n"
|
||||
<< " not required in this mode.\n"
|
||||
<< " --benchmark-tail-frames=N Frames at the end of the run the mean/median/p95 are\n"
|
||||
<< " computed over (default: 200, clamped to the frame count)\n"
|
||||
<< " --benchmark-finish=0/1 glFinish at every frame boundary so a frame time covers\n"
|
||||
<< " GPU completion and not just CPU submission (default: 1)\n"
|
||||
<< " --benchmark-result=PATH Timing JSON output (default: OUTPUT/benchmark.json)\n"
|
||||
<< " --dump-fbo-attachments CALL:DIR[:FBO,FBO,...]\n"
|
||||
<< " At CALL, write every colour attachment and the depth\n"
|
||||
<< " attachment of every live framebuffer object into DIR as\n"
|
||||
<< " fbo<N>-att<M>.png / fbo<N>-depth.png, plus a manifest.txt\n"
|
||||
<< " of formats and per-channel statistics. Repeatable.\n";
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
std::string optionValue;
|
||||
bool optionHasValue = false;
|
||||
if (arg == "--trace") {
|
||||
if (!ReadValue(argc, argv, i, request.tracePath)) return false;
|
||||
} else if (arg == "--golden") {
|
||||
if (!ReadValue(argc, argv, i, request.goldenPath)) return false;
|
||||
} else if (arg == "--alternate-golden") {
|
||||
std::string alternateGolden;
|
||||
if (!ReadValue(argc, argv, i, alternateGolden)) return false;
|
||||
if (!alternateGolden.empty()) {
|
||||
request.alternateGoldenPaths.push_back(alternateGolden);
|
||||
}
|
||||
} else if (arg == "--diff") {
|
||||
if (!ReadValue(argc, argv, i, request.diffPath)) return false;
|
||||
} else if (arg == "--output") {
|
||||
if (!ReadValue(argc, argv, i, request.outputDir)) return false;
|
||||
} else if (arg == "--backend") {
|
||||
if (!ReadValue(argc, argv, i, request.backend)) return false;
|
||||
} else if (arg == "--mobilegl-library") {
|
||||
if (!ReadValue(argc, argv, i, request.mobileGlLibrary)) return false;
|
||||
} else if (arg == "--target-frame") {
|
||||
if (!ReadInt(argc, argv, i, request.targetFrame)) return false;
|
||||
} else if (arg == "--target-call") {
|
||||
if (!ReadLongLong(argc, argv, i, request.targetCall)) return false;
|
||||
} else if (arg == "--width") {
|
||||
if (!ReadInt(argc, argv, i, request.width)) return false;
|
||||
} else if (arg == "--height") {
|
||||
if (!ReadInt(argc, argv, i, request.height)) return false;
|
||||
} else if (arg == "--window-surface") {
|
||||
request.usePbuffer = false;
|
||||
} else if (arg == "--pbuffer-surface") {
|
||||
request.usePbuffer = true;
|
||||
} else if (arg == "--hold-ms") {
|
||||
if (!ReadInt(argc, argv, i, request.holdMs)) return false;
|
||||
} else if (arg == "--ssim-threshold") {
|
||||
if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false;
|
||||
} else if (arg == "--crop-x") {
|
||||
if (!ReadInt(argc, argv, i, request.cropX)) return false;
|
||||
} else if (arg == "--crop-y") {
|
||||
if (!ReadInt(argc, argv, i, request.cropY)) return false;
|
||||
} else if (arg == "--crop-width") {
|
||||
if (!ReadInt(argc, argv, i, request.cropWidth)) return false;
|
||||
} else if (arg == "--crop-height") {
|
||||
if (!ReadInt(argc, argv, i, request.cropHeight)) return false;
|
||||
} else if (arg == "--coherent-as-flush") {
|
||||
request.coherentAsFlush = true;
|
||||
} else if (arg == "--benchmark") {
|
||||
request.benchmark = true;
|
||||
} else if (MatchOption(arg, "--benchmark-tail-frames", optionValue, optionHasValue)) {
|
||||
if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false;
|
||||
request.benchmarkTailFrames = std::atoi(optionValue.c_str());
|
||||
} else if (MatchOption(arg, "--benchmark-finish", optionValue, optionHasValue)) {
|
||||
if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false;
|
||||
request.benchmarkFinish = optionValue != "0";
|
||||
} else if (MatchOption(arg, "--benchmark-result", optionValue, optionHasValue)) {
|
||||
if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false;
|
||||
request.benchmarkResultPath = optionValue;
|
||||
} else if (arg == "--dump-fbo-attachments") {
|
||||
std::string dumpPoint;
|
||||
if (!ReadValue(argc, argv, i, dumpPoint)) return false;
|
||||
if (dumpPoint.find(':') == std::string::npos) {
|
||||
std::cerr << "--dump-fbo-attachments expects CALL:DIR[:FBO,FBO,...]\n";
|
||||
return false;
|
||||
}
|
||||
request.fboAttachmentDumps.push_back(dumpPoint);
|
||||
} else if (arg == "--help" || arg == "-h") {
|
||||
return false;
|
||||
} else {
|
||||
std::cerr << "Unknown argument: " << arg << "\n";
|
||||
bool ReadValue(int argc, char** argv, int& index, std::string& out) {
|
||||
if (index + 1 >= argc) {
|
||||
std::cerr << "Missing value for " << argv[index] << "\n";
|
||||
return false;
|
||||
}
|
||||
out = argv[++index];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request.tracePath.empty()) {
|
||||
std::cerr << "--trace is required\n";
|
||||
bool ReadInt(int argc, char** argv, int& index, int& out) {
|
||||
std::string value;
|
||||
if (!ReadValue(argc, argv, index, value)) {
|
||||
return false;
|
||||
}
|
||||
out = std::atoi(value.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadLongLong(int argc, char** argv, int& index, long long& out) {
|
||||
std::string value;
|
||||
if (!ReadValue(argc, argv, index, value)) {
|
||||
return false;
|
||||
}
|
||||
out = std::strtoll(value.c_str(), nullptr, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadDouble(int argc, char** argv, int& index, double& out) {
|
||||
std::string value;
|
||||
if (!ReadValue(argc, argv, index, value)) {
|
||||
return false;
|
||||
}
|
||||
out = std::strtod(value.c_str(), nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The benchmark options are documented as --name=VALUE. The space-separated spelling every
|
||||
// other option here uses is accepted too, so --benchmark-tail-frames=300 and
|
||||
// --benchmark-tail-frames 300 both work; hasInlineValue says which one was given.
|
||||
bool MatchOption(const std::string& arg, const char* name, std::string& inlineValue, bool& hasInlineValue) {
|
||||
if (arg == name) {
|
||||
hasInlineValue = false;
|
||||
return true;
|
||||
}
|
||||
const std::string prefix = std::string(name) + "=";
|
||||
if (arg.compare(0, prefix.size(), prefix) == 0) {
|
||||
inlineValue = arg.substr(prefix.size());
|
||||
hasInlineValue = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (request.outputDir.empty()) {
|
||||
std::cerr << "--output is required\n";
|
||||
return false;
|
||||
|
||||
bool ParseArgs(int argc, char** argv, mobilegl_trace::Request& request) {
|
||||
request.backend = "DirectGLES";
|
||||
// MOBILEGL_ITRP_FIX is a tri-state (Auto/ForceOn/ForceOff); the request only carries ForceOn.
|
||||
if (const char* itrpFix = std::getenv("MOBILEGL_ITRP_FIX")) {
|
||||
std::string value = itrpFix;
|
||||
for (char& ch : value)
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
request.iterationRPFix = value == "1" || value == "true" || value == "on" || value == "forceon";
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
std::string optionValue;
|
||||
bool optionHasValue = false;
|
||||
if (arg == "--trace") {
|
||||
if (!ReadValue(argc, argv, i, request.tracePath)) return false;
|
||||
} else if (arg == "--golden") {
|
||||
if (!ReadValue(argc, argv, i, request.goldenPath)) return false;
|
||||
} else if (arg == "--alternate-golden") {
|
||||
std::string alternateGolden;
|
||||
if (!ReadValue(argc, argv, i, alternateGolden)) return false;
|
||||
if (!alternateGolden.empty()) {
|
||||
request.alternateGoldenPaths.push_back(alternateGolden);
|
||||
}
|
||||
} else if (arg == "--diff") {
|
||||
if (!ReadValue(argc, argv, i, request.diffPath)) return false;
|
||||
} else if (arg == "--output") {
|
||||
if (!ReadValue(argc, argv, i, request.outputDir)) return false;
|
||||
} else if (arg == "--backend") {
|
||||
if (!ReadValue(argc, argv, i, request.backend)) return false;
|
||||
} else if (arg == "--mobilegl-library") {
|
||||
if (!ReadValue(argc, argv, i, request.mobileGlLibrary)) return false;
|
||||
} else if (arg == "--target-frame") {
|
||||
if (!ReadInt(argc, argv, i, request.targetFrame)) return false;
|
||||
} else if (arg == "--target-call") {
|
||||
if (!ReadLongLong(argc, argv, i, request.targetCall)) return false;
|
||||
} else if (arg == "--width") {
|
||||
if (!ReadInt(argc, argv, i, request.width)) return false;
|
||||
} else if (arg == "--height") {
|
||||
if (!ReadInt(argc, argv, i, request.height)) return false;
|
||||
} else if (arg == "--window-surface") {
|
||||
request.usePbuffer = false;
|
||||
} else if (arg == "--pbuffer-surface") {
|
||||
request.usePbuffer = true;
|
||||
} else if (arg == "--hold-ms") {
|
||||
if (!ReadInt(argc, argv, i, request.holdMs)) return false;
|
||||
} else if (arg == "--ssim-threshold") {
|
||||
if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false;
|
||||
} else if (arg == "--crop-x") {
|
||||
if (!ReadInt(argc, argv, i, request.cropX)) return false;
|
||||
} else if (arg == "--crop-y") {
|
||||
if (!ReadInt(argc, argv, i, request.cropY)) return false;
|
||||
} else if (arg == "--crop-width") {
|
||||
if (!ReadInt(argc, argv, i, request.cropWidth)) return false;
|
||||
} else if (arg == "--crop-height") {
|
||||
if (!ReadInt(argc, argv, i, request.cropHeight)) return false;
|
||||
} else if (arg == "--coherent-as-flush") {
|
||||
request.coherentAsFlush = true;
|
||||
} else if (arg == "--benchmark") {
|
||||
request.benchmark = true;
|
||||
} else if (MatchOption(arg, "--benchmark-tail-frames", optionValue, optionHasValue)) {
|
||||
if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false;
|
||||
request.benchmarkTailFrames = std::atoi(optionValue.c_str());
|
||||
} else if (MatchOption(arg, "--benchmark-finish", optionValue, optionHasValue)) {
|
||||
if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false;
|
||||
request.benchmarkFinish = optionValue != "0";
|
||||
} else if (MatchOption(arg, "--benchmark-result", optionValue, optionHasValue)) {
|
||||
if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false;
|
||||
request.benchmarkResultPath = optionValue;
|
||||
} else if (arg == "--dump-fbo-attachments") {
|
||||
std::string dumpPoint;
|
||||
if (!ReadValue(argc, argv, i, dumpPoint)) return false;
|
||||
if (dumpPoint.find(':') == std::string::npos) {
|
||||
std::cerr << "--dump-fbo-attachments expects CALL:DIR[:FBO,FBO,...]\n";
|
||||
return false;
|
||||
}
|
||||
request.fboAttachmentDumps.push_back(dumpPoint);
|
||||
} else if (arg == "--help" || arg == "-h") {
|
||||
return false;
|
||||
} else {
|
||||
std::cerr << "Unknown argument: " << arg << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.tracePath.empty()) {
|
||||
std::cerr << "--trace is required\n";
|
||||
return false;
|
||||
}
|
||||
if (request.outputDir.empty()) {
|
||||
std::cerr << "--output is required\n";
|
||||
return false;
|
||||
}
|
||||
if (request.targetCall < 0 && !request.benchmark) {
|
||||
std::cerr << "--target-call is required\n";
|
||||
return false;
|
||||
}
|
||||
if (request.holdMs < 0) {
|
||||
std::cerr << "--hold-ms must be non-negative\n";
|
||||
return false;
|
||||
}
|
||||
if (request.benchmarkTailFrames <= 0) {
|
||||
std::cerr << "--benchmark-tail-frames must be positive\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (request.targetCall < 0 && !request.benchmark) {
|
||||
std::cerr << "--target-call is required\n";
|
||||
return false;
|
||||
}
|
||||
if (request.holdMs < 0) {
|
||||
std::cerr << "--hold-ms must be non-negative\n";
|
||||
return false;
|
||||
}
|
||||
if (request.benchmarkTailFrames <= 0) {
|
||||
std::cerr << "--benchmark-tail-frames must be positive\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char** argv) {
|
||||
mobilegl_trace::Request request;
|
||||
if (!ParseArgs(argc, argv, request)) {
|
||||
PrintUsage(argv[0]);
|
||||
|
||||
@@ -68,8 +68,8 @@ static LONG WINAPI CrashDump(EXCEPTION_POINTERS* ep) {
|
||||
static volatile LONG entered = 0;
|
||||
if (ep->ExceptionRecord->ExceptionCode != 0xC0000005) return EXCEPTION_CONTINUE_SEARCH;
|
||||
if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH;
|
||||
HANDLE f = CreateFileA("glfw-crash-stack.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
HANDLE f =
|
||||
CreateFileA("glfw-crash-stack.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (f == INVALID_HANDLE_VALUE) TerminateProcess(GetCurrentProcess(), 0x43);
|
||||
RawPrint(f, "AV at ");
|
||||
RawHex(f, (unsigned long long)ep->ExceptionRecord->ExceptionAddress);
|
||||
|
||||
@@ -184,9 +184,9 @@ static LONG WINAPI CrashDump(EXCEPTION_POINTERS* ep) {
|
||||
static volatile LONG entered = 0;
|
||||
if (ep->ExceptionRecord->ExceptionCode != 0xC0000005) return EXCEPTION_CONTINUE_SEARCH;
|
||||
if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH;
|
||||
HANDLE f = CreateFileA(
|
||||
"C:\\Users\\yello\\AndroidStudioProjects\\FoldCraftLauncher\\MobileGL\\.claude\\worktrees\\wgl-host\\wgl-smoke\\crash-stack.txt",
|
||||
GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
HANDLE f = CreateFileA("C:\\Users\\yello\\AndroidStudioProjects\\FoldCraftLauncher\\MobileGL\\."
|
||||
"claude\\worktrees\\wgl-host\\wgl-smoke\\crash-stack.txt",
|
||||
GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (f == INVALID_HANDLE_VALUE) {
|
||||
TerminateProcess(GetCurrentProcess(), 0x43);
|
||||
}
|
||||
@@ -290,10 +290,8 @@ int main() {
|
||||
eglGetError_ ? eglGetError_() : -1);
|
||||
}
|
||||
|
||||
auto wglGetExtensionsStringARB =
|
||||
(PFN_wglGetExtensionsStringARB)p_wglGetProcAddress("wglGetExtensionsStringARB");
|
||||
auto wglCreateContextAttribsARB =
|
||||
(PFN_wglCreateContextAttribsARB)p_wglGetProcAddress("wglCreateContextAttribsARB");
|
||||
auto wglGetExtensionsStringARB = (PFN_wglGetExtensionsStringARB)p_wglGetProcAddress("wglGetExtensionsStringARB");
|
||||
auto wglCreateContextAttribsARB = (PFN_wglCreateContextAttribsARB)p_wglGetProcAddress("wglCreateContextAttribsARB");
|
||||
auto wglSwapIntervalEXT = (PFN_wglSwapIntervalEXT)p_wglGetProcAddress("wglSwapIntervalEXT");
|
||||
auto wglGetPixelFormatAttribivARB =
|
||||
(PFN_wglGetPixelFormatAttribivARB)p_wglGetProcAddress("wglGetPixelFormatAttribivARB");
|
||||
@@ -326,7 +324,7 @@ int main() {
|
||||
int chosenFormat = 0;
|
||||
for (int i = 1; i <= nativeCount; ++i) {
|
||||
const int attribs[] = {WGL_SUPPORT_OPENGL_ARB, WGL_DRAW_TO_WINDOW_ARB, WGL_PIXEL_TYPE_ARB,
|
||||
WGL_DOUBLE_BUFFER_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB};
|
||||
WGL_DOUBLE_BUFFER_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB};
|
||||
int values[6] = {};
|
||||
if (!wglGetPixelFormatAttribivARB(dc, i, 0, 6, attribs, values)) continue;
|
||||
if (values[0] && values[1] && values[2] == WGL_TYPE_RGBA_ARB && values[3] && values[4] >= 24 &&
|
||||
@@ -342,11 +340,16 @@ int main() {
|
||||
if (!SetPixelFormat(dc, chosenFormat, &realPfd)) FAIL("SetPixelFormat (real) failed");
|
||||
|
||||
const int ctxAttribs[] = {
|
||||
WGL_CONTEXT_OPENGL_NO_ERROR_ARB, 1,
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, 2,
|
||||
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
||||
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
||||
WGL_CONTEXT_OPENGL_NO_ERROR_ARB,
|
||||
1,
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB,
|
||||
3,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB,
|
||||
2,
|
||||
WGL_CONTEXT_FLAGS_ARB,
|
||||
WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
||||
WGL_CONTEXT_PROFILE_MASK_ARB,
|
||||
WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
||||
0,
|
||||
};
|
||||
HGLRC ctx = wglCreateContextAttribsARB(dc, nullptr, ctxAttribs);
|
||||
@@ -389,8 +392,7 @@ int main() {
|
||||
GLint major = 0, minor = 0;
|
||||
glGetIntegerv_(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv_(GL_MINOR_VERSION, &minor);
|
||||
std::printf("real ctx: GL %d.%d | %s | %s\n", major, minor, glGetString_(GL_RENDERER),
|
||||
glGetString_(GL_VERSION));
|
||||
std::printf("real ctx: GL %d.%d | %s | %s\n", major, minor, glGetString_(GL_RENDERER), glGetString_(GL_VERSION));
|
||||
if (major < 3 || (major == 3 && minor < 2)) FAIL("context version below 3.2");
|
||||
|
||||
const char* vsSrc = "#version 150 core\n"
|
||||
|
||||
Reference in New Issue
Block a user