Commit 137b1517 by Austin Kinross Committed by Jamie Madill

Improve D3D GetProgramBinary chipset validation

- Check chipset identifying info before trying to compile shaders - Check device feature level when loading a binary - Use chipset VendorID/DeviceID etc instead of LUID so that program binaries remain valid across system reboots Change-Id: I88ba4543bb990956d1d8fb324abf9784d72950cd Reviewed-on: https://chromium-review.googlesource.com/280428Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Tested-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 5a6dec50
......@@ -436,6 +436,16 @@ bool ProgramD3D::validateSamplers(gl::InfoLog *infoLog, const gl::Caps &caps)
LinkResult ProgramD3D::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream)
{
DeviceIdentifier binaryDeviceIdentifier = { 0 };
stream->readBytes(reinterpret_cast<unsigned char*>(&binaryDeviceIdentifier), sizeof(DeviceIdentifier));
DeviceIdentifier identifier = mRenderer->getAdapterIdentifier();
if (memcmp(&identifier, &binaryDeviceIdentifier, sizeof(DeviceIdentifier)) != 0)
{
infoLog << "Invalid program binary, device configuration has changed.";
return LinkResult(false, gl::Error(GL_NO_ERROR));
}
int compileFlags = stream->readInt<int>();
if (compileFlags != ANGLE_COMPILE_OPTIMIZATION_LEVEL)
{
......@@ -678,16 +688,6 @@ LinkResult ProgramD3D::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream)
stream->skip(geometryShaderSize);
}
GUID binaryIdentifier = {0};
stream->readBytes(reinterpret_cast<unsigned char*>(&binaryIdentifier), sizeof(GUID));
GUID identifier = mRenderer->getAdapterIdentifier();
if (memcmp(&identifier, &binaryIdentifier, sizeof(GUID)) != 0)
{
infoLog << "Invalid program binary.";
return LinkResult(false, gl::Error(GL_NO_ERROR));
}
initializeUniformStorage();
initAttributesByLayout();
......@@ -696,6 +696,11 @@ LinkResult ProgramD3D::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream)
gl::Error ProgramD3D::save(gl::BinaryOutputStream *stream)
{
// Output the DeviceIdentifier before we output any shader code
// When we load the binary again later, we can validate the device identifier before trying to compile any HLSL
DeviceIdentifier binaryIdentifier = mRenderer->getAdapterIdentifier();
stream->writeBytes(reinterpret_cast<unsigned char*>(&binaryIdentifier), sizeof(DeviceIdentifier));
stream->writeInt(ANGLE_COMPILE_OPTIMIZATION_LEVEL);
stream->writeInt(mShaderVersion);
......@@ -849,9 +854,6 @@ gl::Error ProgramD3D::save(gl::BinaryOutputStream *stream)
stream->writeBytes(geometryBlob, geometryShaderSize);
}
GUID binaryIdentifier = mRenderer->getAdapterIdentifier();
stream->writeBytes(reinterpret_cast<unsigned char*>(&binaryIdentifier), sizeof(GUID));
return gl::Error(GL_NO_ERROR);
}
......
......@@ -51,6 +51,15 @@ enum ShaderType
SHADER_GEOMETRY
};
struct DeviceIdentifier
{
UINT VendorId;
UINT DeviceId;
UINT SubSysId;
UINT Revision;
UINT FeatureLevel;
};
enum RendererClass
{
RENDERER_D3D11,
......@@ -98,7 +107,7 @@ class RendererD3D : public Renderer, public BufferFactoryD3D
virtual std::string getShaderModelSuffix() const = 0;
// Direct3D Specific methods
virtual GUID getAdapterIdentifier() const = 0;
virtual DeviceIdentifier getAdapterIdentifier() const = 0;
virtual SwapChainD3D *createSwapChain(NativeWindow nativeWindow, HANDLE shareHandle, GLenum backBufferFormat, GLenum depthBufferFormat) = 0;
......
......@@ -2445,14 +2445,17 @@ std::string Renderer11::getRendererDescription() const
return rendererString.str();
}
GUID Renderer11::getAdapterIdentifier() const
{
// Use the adapter LUID as our adapter ID
// This number is local to a machine is only guaranteed to be unique between restarts
static_assert(sizeof(LUID) <= sizeof(GUID), "Size of GUID must be at least as large as LUID.");
GUID adapterId = {0};
memcpy(&adapterId, &mAdapterDescription.AdapterLuid, sizeof(LUID));
return adapterId;
DeviceIdentifier Renderer11::getAdapterIdentifier() const
{
// Don't use the AdapterLuid here, since that doesn't persist across reboot.
DeviceIdentifier deviceIdentifier = { 0 };
deviceIdentifier.VendorId = mAdapterDescription.VendorId;
deviceIdentifier.DeviceId = mAdapterDescription.DeviceId;
deviceIdentifier.SubSysId = mAdapterDescription.SubSysId;
deviceIdentifier.Revision = mAdapterDescription.Revision;
deviceIdentifier.FeatureLevel = static_cast<UINT>(mRenderer11DeviceCaps.featureLevel);
return deviceIdentifier;
}
unsigned int Renderer11::getReservedVertexUniformVectors() const
......
......@@ -150,7 +150,7 @@ class Renderer11 : public RendererD3D
VendorID getVendorId() const override;
std::string getRendererDescription() const override;
GUID getAdapterIdentifier() const override;
DeviceIdentifier getAdapterIdentifier() const override;
virtual unsigned int getReservedVertexUniformVectors() const;
virtual unsigned int getReservedFragmentUniformVectors() const;
......
......@@ -2461,9 +2461,16 @@ std::string Renderer9::getRendererDescription() const
return rendererString.str();
}
GUID Renderer9::getAdapterIdentifier() const
DeviceIdentifier Renderer9::getAdapterIdentifier() const
{
return mAdapterIdentifier.DeviceIdentifier;
DeviceIdentifier deviceIdentifier = { 0 };
deviceIdentifier.VendorId = static_cast<UINT>(mAdapterIdentifier.VendorId);
deviceIdentifier.DeviceId = static_cast<UINT>(mAdapterIdentifier.DeviceId);
deviceIdentifier.SubSysId = static_cast<UINT>(mAdapterIdentifier.SubSysId);
deviceIdentifier.Revision = static_cast<UINT>(mAdapterIdentifier.Revision);
deviceIdentifier.FeatureLevel = 0;
return deviceIdentifier;
}
unsigned int Renderer9::getReservedVertexUniformVectors() const
......
......@@ -131,7 +131,7 @@ class Renderer9 : public RendererD3D
VendorID getVendorId() const override;
std::string getRendererDescription() const override;
GUID getAdapterIdentifier() const override;
DeviceIdentifier getAdapterIdentifier() const override;
IDirect3DDevice9 *getDevice() { return mDevice; }
void *getD3DDevice() override;
......
......@@ -9,6 +9,10 @@
#include <memory>
#include <stdint.h>
#include "EGLWindow.h"
#include "OSWindow.h"
#include "test_utils/angle_test_configs.h"
using namespace angle;
class ProgramBinaryTest : public ANGLETest
......@@ -160,3 +164,278 @@ TEST_P(ProgramBinaryTest, SaveAndLoadBinary)
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
ANGLE_INSTANTIATE_TEST(ProgramBinaryTest, ES2_D3D9(), ES2_D3D11());
// For the ProgramBinariesAcrossPlatforms tests, we need two sets of params:
// - a set to save the program binary
// - a set to load the program binary
// We combine these into one struct extending PlatformParameters so we can reuse existing ANGLE test macros
struct PlatformsWithLinkResult : PlatformParameters
{
PlatformsWithLinkResult(PlatformParameters saveParams, PlatformParameters loadParamsIn, bool expectedLinkResultIn)
{
majorVersion = saveParams.majorVersion;
minorVersion = saveParams.minorVersion;
eglParameters = saveParams.eglParameters;
loadParams = loadParamsIn;
expectedLinkResult = expectedLinkResultIn;
}
PlatformParameters loadParams;
bool expectedLinkResult;
};
class ProgramBinariesAcrossPlatforms : public testing::TestWithParam<PlatformsWithLinkResult>
{
public:
void SetUp() override
{
mOSWindow = CreateOSWindow();
bool result = mOSWindow->initialize("ProgramBinariesAcrossRenderersTests", 100, 100);
if (result == false)
{
FAIL() << "Failed to create OS window";
}
}
EGLWindow *createAndInitEGLWindow(angle::PlatformParameters &param)
{
EGLWindow *eglWindow = new EGLWindow(1, 1, param.majorVersion, param.eglParameters);
bool result = eglWindow->initializeGL(mOSWindow);
if (result == false)
{
SafeDelete(eglWindow);
eglWindow = nullptr;
}
return eglWindow;
}
void destroyEGLWindow(EGLWindow **eglWindow)
{
ASSERT(*eglWindow != nullptr);
(*eglWindow)->destroyGL();
SafeDelete(*eglWindow);
*eglWindow = nullptr;
}
GLuint createES2ProgramFromSource()
{
const std::string testVertexShaderSource = SHADER_SOURCE
(
attribute highp vec4 position;
void main(void)
{
gl_Position = position;
}
);
const std::string testFragmentShaderSource = SHADER_SOURCE
(
void main(void)
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
);
return CompileProgram(testVertexShaderSource, testFragmentShaderSource);
}
GLuint createES3ProgramFromSource()
{
const std::string testVertexShaderSource = SHADER_SOURCE
( #version 300 es\n
precision highp float;
in highp vec4 position;
void main(void)
{
gl_Position = position;
}
);
const std::string testFragmentShaderSource = SHADER_SOURCE
( #version 300 es \n
precision highp float;
out vec4 out_FragColor;
void main(void)
{
out_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
);
return CompileProgram(testVertexShaderSource, testFragmentShaderSource);
}
void drawWithProgram(GLuint program)
{
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
GLint positionLocation = glGetAttribLocation(program, "position");
glUseProgram(program);
const GLfloat vertices[] =
{
-1.0f, 1.0f, 0.5f,
-1.0f, -1.0f, 0.5f,
1.0f, -1.0f, 0.5f,
-1.0f, 1.0f, 0.5f,
1.0f, -1.0f, 0.5f,
1.0f, 1.0f, 0.5f,
};
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, NULL);
EXPECT_PIXEL_EQ(mOSWindow->getWidth() / 2, mOSWindow->getHeight() / 2, 255, 0, 0, 255);
}
void TearDown() override
{
mOSWindow->destroy();
SafeDelete(mOSWindow);
}
OSWindow *mOSWindow;
};
// Tries to create a program binary using one set of platform params, then load it using a different sent of params
TEST_P(ProgramBinariesAcrossPlatforms, CreateAndReloadBinary)
{
angle::PlatformParameters firstRenderer = GetParam();
angle::PlatformParameters secondRenderer = GetParam().loadParams;
bool expectedLinkResult = GetParam().expectedLinkResult;
if (!(IsPlatformAvailable(firstRenderer)))
{
std::cout << "First renderer not supported, skipping test";
return;
}
if (!(IsPlatformAvailable(secondRenderer)))
{
std::cout << "Second renderer not supported, skipping test";
return;
}
EGLWindow *eglWindow = nullptr;
std::vector<uint8_t> binary(0);
GLuint program = 0;
GLint programLength = 0;
GLint writtenLength = 0;
GLenum binaryFormat = 0;
// Create a EGL window with the first renderer
eglWindow = createAndInitEGLWindow(firstRenderer);
if (eglWindow == nullptr)
{
FAIL() << "Failed to create EGL window";
return;
}
// If the test is trying to use both the default GPU and WARP, but the default GPU *IS* WARP,
// then our expectations for the test results will be invalid.
if (firstRenderer.eglParameters.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE &&
secondRenderer.eglParameters.deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE)
{
std::string rendererString = std::string(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
std::transform(rendererString.begin(), rendererString.end(), rendererString.begin(), ::tolower);
auto basicRenderPos = rendererString.find(std::string("microsoft basic render"));
auto softwareAdapterPos = rendererString.find(std::string("software adapter"));
if (basicRenderPos != std::string::npos || softwareAdapterPos != std::string::npos)
{
// The first renderer is using WARP, even though we didn't explictly request it
// We should skip this test
std::cout << "Test skipped on when default GPU is WARP." << std::endl;
return;
}
}
// Create a program
if (firstRenderer.majorVersion == 3)
{
program = createES3ProgramFromSource();
}
else
{
program = createES2ProgramFromSource();
}
if (program == 0)
{
FAIL() << "Failed to create program from source";
destroyEGLWindow(&eglWindow);
return;
}
// Draw using the program to ensure it works as expected
drawWithProgram(program);
EXPECT_GL_NO_ERROR();
// Save the program binary out from this renderer
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH_OES, &programLength);
EXPECT_GL_NO_ERROR();
binary.resize(programLength);
glGetProgramBinaryOES(program, programLength, &writtenLength, &binaryFormat, binary.data());
EXPECT_GL_NO_ERROR();
// Destroy the first renderer
glDeleteProgram(program);
destroyEGLWindow(&eglWindow);
// Create an EGL window with the second renderer
eglWindow = createAndInitEGLWindow(secondRenderer);
if (eglWindow == nullptr)
{
FAIL() << "Failed to create EGL window";
return;
}
program = glCreateProgram();
glProgramBinaryOES(program, binaryFormat, binary.data(), writtenLength);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_EQ(expectedLinkResult, (linkStatus != 0));
if (linkStatus != 0)
{
// If the link was successful, then we should try to draw using the program to ensure it works as expected
drawWithProgram(program);
EXPECT_GL_NO_ERROR();
}
// Destroy the second renderer
glDeleteProgram(program);
destroyEGLWindow(&eglWindow);
}
ANGLE_INSTANTIATE_TEST(ProgramBinariesAcrossPlatforms,
// | Save the program | Load the program | Expected
// | using these params | using these params | link result
PlatformsWithLinkResult(ES2_D3D11(), ES2_D3D11(), true ), // Loading + reloading binary should work
PlatformsWithLinkResult(ES3_D3D11(), ES3_D3D11(), true ), // Loading + reloading binary should work
PlatformsWithLinkResult(ES2_D3D11_FL11_0(), ES2_D3D11_FL9_3(), false ), // Switching feature level shouldn't work
PlatformsWithLinkResult(ES2_D3D11(), ES2_D3D11_WARP(), false ), // Switching from hardware to software shouldn't work
PlatformsWithLinkResult(ES2_D3D11_FL9_3(), ES2_D3D11_FL9_3_WARP(), false ), // Switching from hardware to software shouldn't work for FL9 either
PlatformsWithLinkResult(ES2_D3D11(), ES2_D3D9(), false ), // Switching from D3D11 to D3D9 shouldn't work
PlatformsWithLinkResult(ES2_D3D9(), ES2_D3D11(), false ), // Switching from D3D9 to D3D11 shouldn't work
PlatformsWithLinkResult(ES2_D3D11(), ES3_D3D11(), true ) // Switching to newer client version should work
// TODO: ANGLE issue 523
// Compiling a program with client version 3, saving the binary, then loading it with client version 2 should not work
// PlatformsWithLinkResult(ES3_D3D11(), ES2_D3D11(), false )
);
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment