diff options
author | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-04 18:09:08 +0000 |
---|---|---|
committer | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-04 18:09:08 +0000 |
commit | bc0cfe2aa039e23039e58bc397bdfeb818659610 (patch) | |
tree | ab0168f85836070cc7356be279ac8d2c77634daf /ui/surface | |
parent | f486d205d3bfc35ab35a46e4a448d94b6dc0b8e8 (diff) | |
download | chromium_src-bc0cfe2aa039e23039e58bc397bdfeb818659610.zip chromium_src-bc0cfe2aa039e23039e58bc397bdfeb818659610.tar.gz chromium_src-bc0cfe2aa039e23039e58bc397bdfeb818659610.tar.bz2 |
[ui/surface] Separate image processing logic from presentation logic.
The D3D image processing code from AcceleratedPresenter moves to its own class, "ui/surface/accelerated_surface_transformer_win.h". This split allows the image processing code to be tested independently of the present-scheduling system, and so we add unit tests doing exactly that. Utility functions (loading d3d, creating temp surfaces) doing things commonly required by test, transform, and present code -- these functions are moved to a third location, "ui/surface/d3d9_utils_win.h"
The new unit tests -- which live in the ui_unittests binary -- make extensive use of pseudorandom image content, which I think is kind of neat. The tests use D3D HAL devices; I tried to use REF, but it didn't work, as StretchRect+LINEAR is not supported by refrast. So as a result we may have a GPU vendor dependency in these results.
BUG=161537
TEST=new ui_unittests
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=174028
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=174338
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=174943
Review URL: https://codereview.chromium.org/11464017
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@175152 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/surface')
-rw-r--r-- | ui/surface/accelerated_surface_transformer_win.cc | 261 | ||||
-rw-r--r-- | ui/surface/accelerated_surface_transformer_win.h | 81 | ||||
-rw-r--r-- | ui/surface/accelerated_surface_transformer_win.hlsl (renamed from ui/surface/accelerated_surface_win.hlsl) | 4 | ||||
-rw-r--r-- | ui/surface/accelerated_surface_transformer_win_unittest.cc | 525 | ||||
-rw-r--r-- | ui/surface/accelerated_surface_win.cc | 280 | ||||
-rw-r--r-- | ui/surface/d3d9_utils_win.cc | 119 | ||||
-rw-r--r-- | ui/surface/d3d9_utils_win.h | 71 | ||||
-rw-r--r-- | ui/surface/surface.gyp | 6 |
8 files changed, 1107 insertions, 240 deletions
diff --git a/ui/surface/accelerated_surface_transformer_win.cc b/ui/surface/accelerated_surface_transformer_win.cc new file mode 100644 index 0000000..cfc031b --- /dev/null +++ b/ui/surface/accelerated_surface_transformer_win.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/surface/accelerated_surface_transformer_win.h"
+
+#include <vector>
+
+#include "accelerated_surface_transformer_win_hlsl_compiled.h"
+#include "base/debug/trace_event.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/win/scoped_comptr.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/surface/d3d9_utils_win.h"
+#include "ui/surface/surface_export.h"
+
+using base::win::ScopedComPtr;
+using std::vector;
+
+namespace d3d_utils = ui_surface_d3d9_utils;
+
+namespace {
+
+struct Vertex {
+ float x, y, z, w;
+ float u, v;
+};
+
+const static D3DVERTEXELEMENT9 g_vertexElements[] = {
+ { 0, 0, D3DDECLTYPE_FLOAT4, 0, D3DDECLUSAGE_POSITION, 0 },
+ { 0, 16, D3DDECLTYPE_FLOAT2, 0, D3DDECLUSAGE_TEXCOORD, 0 },
+ D3DDECL_END()
+};
+
+// Calculate the number necessary to transform |src_subrect| into |dst_size|
+// by repeating downsampling of the image of |src_subrect| by a factor no more
+// than 2.
+int GetResampleCount(const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const gfx::Size& back_buffer_size) {
+ // At least one copy is required, since the back buffer itself is not
+ // lockable.
+ int min_resample_count = 1;
+ int width_count = 0;
+ int width = src_subrect.width();
+ while (width > dst_size.width()) {
+ ++width_count;
+ width >>= 1;
+ }
+ int height_count = 0;
+ int height = src_subrect.height();
+ while (height > dst_size.height()) {
+ ++height_count;
+ height >>= 1;
+ }
+ return std::max(std::max(width_count, height_count),
+ min_resample_count);
+}
+
+// Returns half the size of |size| no smaller than |min_size|.
+gfx::Size GetHalfSizeNoLessThan(const gfx::Size& size,
+ const gfx::Size& min_size) {
+ return gfx::Size(std::max(min_size.width(), size.width() / 2),
+ std::max(min_size.height(), size.height() / 2));
+}
+
+gfx::Size GetSize(IDirect3DSurface9* surface) {
+ D3DSURFACE_DESC surface_description;
+ HRESULT hr = surface->GetDesc(&surface_description);
+ if (FAILED(hr))
+ return gfx::Size(0, 0);
+ return gfx::Size(surface_description.Width, surface_description.Height);
+}
+
+} // namespace
+
+
+AcceleratedSurfaceTransformer::AcceleratedSurfaceTransformer() {}
+
+bool AcceleratedSurfaceTransformer::Init(IDirect3DDevice9* device) {
+ device_ = device;
+ if (!InitShaderCombo(
+ ui_surface::AcceleratedSurfaceTransformerWinHLSL::kVsOneTexture,
+ ui_surface::AcceleratedSurfaceTransformerWinHLSL::kPsOneTexture,
+ SIMPLE_TEXTURE)) {
+ ReleaseAll();
+ return false;
+ }
+
+ base::win::ScopedComPtr<IDirect3DVertexDeclaration9> vertex_declaration;
+ HRESULT hr = device_->CreateVertexDeclaration(g_vertexElements,
+ vertex_declaration.Receive());
+ if (!SUCCEEDED(hr)) {
+ ReleaseAll();
+ return false;
+ }
+ device_->SetVertexDeclaration(vertex_declaration);
+
+ return true;
+}
+
+bool AcceleratedSurfaceTransformer::InitShaderCombo(
+ const BYTE vertex_shader_instructions[],
+ const BYTE pixel_shader_instructions[],
+ ShaderCombo shader_combo_name) {
+ HRESULT hr = device_->CreateVertexShader(
+ reinterpret_cast<const DWORD*>(vertex_shader_instructions),
+ vertex_shaders_[shader_combo_name].Receive());
+
+ if (FAILED(hr))
+ return false;
+
+ hr = device_->CreatePixelShader(
+ reinterpret_cast<const DWORD*>(pixel_shader_instructions),
+ pixel_shaders_[shader_combo_name].Receive());
+
+ return SUCCEEDED(hr);
+}
+
+
+void AcceleratedSurfaceTransformer::ReleaseAll() {
+ for (int i = 0; i < NUM_SHADERS; i++) {
+ vertex_shaders_[i] = NULL;
+ vertex_shaders_[i] = NULL;
+ }
+ device_ = NULL;
+}
+void AcceleratedSurfaceTransformer::DetachAll() {
+ for (int i = 0; i < NUM_SHADERS; i++) {
+ vertex_shaders_[i].Detach();
+ vertex_shaders_[i].Detach();
+ }
+ device_.Detach();
+}
+
+// Draw a textured quad to a surface.
+bool AcceleratedSurfaceTransformer::CopyInverted(
+ IDirect3DTexture9* src_texture,
+ IDirect3DSurface9* dst_surface,
+ const gfx::Size& dst_size) {
+ base::win::ScopedComPtr<IDirect3DSurface9> default_color_target;
+ device()->GetRenderTarget(0, default_color_target.Receive());
+
+ if (!SetShaderCombo(SIMPLE_TEXTURE))
+ return false;
+
+ device()->SetRenderTarget(0, dst_surface);
+ device()->SetTexture(0, src_texture);
+
+ D3DVIEWPORT9 viewport = {
+ 0, 0,
+ dst_size.width(), dst_size.height(),
+ 0, 1
+ };
+ device()->SetViewport(&viewport);
+
+ float halfPixelX = -1.0f / dst_size.width();
+ float halfPixelY = 1.0f / dst_size.height();
+ Vertex vertices[] = {
+ { halfPixelX - 1, halfPixelY + 1, 0.5f, 1, 0, 1 },
+ { halfPixelX + 1, halfPixelY + 1, 0.5f, 1, 1, 1 },
+ { halfPixelX + 1, halfPixelY - 1, 0.5f, 1, 1, 0 },
+ { halfPixelX - 1, halfPixelY - 1, 0.5f, 1, 0, 0 }
+ };
+
+ device()->BeginScene();
+ device()->DrawPrimitiveUP(D3DPT_TRIANGLEFAN,
+ 2,
+ vertices,
+ sizeof(vertices[0]));
+ device()->EndScene();
+
+ // Clear surface references.
+ device()->SetRenderTarget(0, default_color_target);
+ device()->SetTexture(0, NULL);
+ return true;
+}
+
+// Resize an RGB surface using repeated linear interpolation.
+bool AcceleratedSurfaceTransformer::ResizeBilinear(
+ IDirect3DSurface9* src_surface,
+ const gfx::Rect& src_subrect,
+ IDirect3DSurface9* dst_surface) {
+ gfx::Size src_size = GetSize(src_surface);
+ gfx::Size dst_size = GetSize(dst_surface);
+
+ if (src_size.IsEmpty() || dst_size.IsEmpty())
+ return false;
+
+ HRESULT hr = S_OK;
+ // Set up intermediate buffers needed for downsampling.
+ const int resample_count =
+ GetResampleCount(src_subrect, dst_size, src_size);
+ base::win::ScopedComPtr<IDirect3DSurface9> temp_buffer[2];
+ const gfx::Size half_size =
+ GetHalfSizeNoLessThan(src_subrect.size(), dst_size);
+ if (resample_count > 1) {
+ TRACE_EVENT0("gpu", "CreateTemporarySurface");
+ if (!d3d_utils::CreateTemporaryLockableSurface(device(),
+ half_size,
+ temp_buffer[0].Receive()))
+ return false;
+ }
+ if (resample_count > 2) {
+ TRACE_EVENT0("gpu", "CreateTemporarySurface");
+ const gfx::Size quarter_size = GetHalfSizeNoLessThan(half_size, dst_size);
+ if (!d3d_utils::CreateTemporaryLockableSurface(device(),
+ quarter_size,
+ temp_buffer[1].Receive()))
+ return false;
+ }
+
+ // Repeat downsampling the surface until its size becomes identical to
+ // |dst_size|. We keep the factor of each downsampling no more than two
+ // because using a factor more than two can introduce aliasing.
+ RECT read_rect = src_subrect.ToRECT();
+ gfx::Size write_size = half_size;
+ int read_buffer_index = 1;
+ int write_buffer_index = 0;
+ for (int i = 0; i < resample_count; ++i) {
+ TRACE_EVENT0("gpu", "StretchRect");
+ IDirect3DSurface9* read_buffer =
+ (i == 0) ? src_surface : temp_buffer[read_buffer_index];
+ IDirect3DSurface9* write_buffer =
+ (i == resample_count - 1) ? dst_surface :
+ temp_buffer[write_buffer_index];
+ RECT write_rect = gfx::Rect(write_size).ToRECT();
+ hr = device()->StretchRect(read_buffer,
+ &read_rect,
+ write_buffer,
+ &write_rect,
+ D3DTEXF_LINEAR);
+
+ if (FAILED(hr))
+ return false;
+ read_rect = write_rect;
+ write_size = GetHalfSizeNoLessThan(write_size, dst_size);
+ std::swap(read_buffer_index, write_buffer_index);
+ }
+
+ return true;
+}
+
+IDirect3DDevice9* AcceleratedSurfaceTransformer::device() {
+ return device_;
+}
+
+bool AcceleratedSurfaceTransformer::SetShaderCombo(ShaderCombo combo) {
+ HRESULT hr = device()->SetVertexShader(vertex_shaders_[combo]);
+ if (!SUCCEEDED(hr))
+ return false;
+ hr = device()->SetPixelShader(pixel_shaders_[combo]);
+ if (!SUCCEEDED(hr))
+ return false;
+ return true;
+}
\ No newline at end of file diff --git a/ui/surface/accelerated_surface_transformer_win.h b/ui/surface/accelerated_surface_transformer_win.h new file mode 100644 index 0000000..e01804a --- /dev/null +++ b/ui/surface/accelerated_surface_transformer_win.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_SURFACE_ACCELERATED_SURFACE_TRANSFORMER_WIN_H_
+#define UI_SURFACE_ACCELERATED_SURFACE_TRANSFORMER_WIN_H_
+
+#include <d3d9.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/win/scoped_comptr.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+#include "ui/surface/surface_export.h"
+
+namespace gfx {
+class Size;
+class Rect;
+} // namespace gfx
+
+// Provides useful image filtering operations that are implemented
+// efficiently on DirectX9-class hardware using fragment programs.
+class SURFACE_EXPORT AcceleratedSurfaceTransformer {
+ public:
+ // Constructs an uninitialized surface transformer. Call Init() before
+ // using the resulting object.
+ AcceleratedSurfaceTransformer();
+
+ // Init() initializes the transformer to operate on a device. This must be
+ // called before any other method of this class, and it must be called
+ // again after ReleaseAll() or DetachAll() before the class is used.
+ //
+ // Returns true if successful.
+ bool Init(IDirect3DDevice9* device);
+
+ // ReleaseAll() releases all direct3d resource references.
+ void ReleaseAll();
+
+ // DetachAll() leaks all direct3d resource references. This exists in order to
+ // work around particular driver bugs, and should only be called at shutdown.
+ // TODO(ncarter): Update the leak expectations before checkin.
+ void DetachAll();
+
+ // Draw a textured quad to a surface, flipping orientation in the y direction.
+ bool CopyInverted(
+ IDirect3DTexture9* src_texture,
+ IDirect3DSurface9* dst_surface,
+ const gfx::Size& dst_size);
+
+ // Resize a surface using repeated bilinear interpolation.
+ bool ResizeBilinear(
+ IDirect3DSurface9* src_surface,
+ const gfx::Rect& src_subrect,
+ IDirect3DSurface9* dst_surface);
+
+ private:
+ enum ShaderCombo {
+ SIMPLE_TEXTURE,
+ NUM_SHADERS
+ };
+
+ // Set the active vertex and pixel shader combination.
+ bool SetShaderCombo(ShaderCombo combo);
+
+ // Intitializes a vertex and pixel shader combination from compiled bytecode.
+ bool InitShaderCombo(const BYTE vertex_shader_instructions[],
+ const BYTE pixel_shader_instructions[],
+ ShaderCombo shader_combo_name);
+
+ IDirect3DDevice9* device();
+
+ base::win::ScopedComPtr<IDirect3DDevice9> device_;
+ base::win::ScopedComPtr<IDirect3DVertexShader9> vertex_shaders_[NUM_SHADERS];
+ base::win::ScopedComPtr<IDirect3DPixelShader9> pixel_shaders_[NUM_SHADERS];
+ DISALLOW_COPY_AND_ASSIGN(AcceleratedSurfaceTransformer);
+};
+
+#endif // UI_SURFACE_ACCELERATED_SURFACE_TRANSFORMER_WIN_H_
\ No newline at end of file diff --git a/ui/surface/accelerated_surface_win.hlsl b/ui/surface/accelerated_surface_transformer_win.hlsl index bc87dee..0bd6fda 100644 --- a/ui/surface/accelerated_surface_win.hlsl +++ b/ui/surface/accelerated_surface_transformer_win.hlsl @@ -3,7 +3,7 @@ // found in the LICENSE file. // @gyp_namespace(ui_surface) -// Compiles into C++ as 'accelerated_surface_win_hlsl_compiled.h' +// Compiles into C++ as 'accelerated_surface_transformer_win_hlsl_compiled.h' struct Vertex { float4 position : POSITION; @@ -25,4 +25,4 @@ Vertex vsOneTexture(Vertex input) { // Samples a texture at the given texture coordinate and returns the result. float4 psOneTexture(float2 texCoord : TEXCOORD0) : COLOR0 { return tex2D(s, texCoord); -};
\ No newline at end of file +}; diff --git a/ui/surface/accelerated_surface_transformer_win_unittest.cc b/ui/surface/accelerated_surface_transformer_win_unittest.cc new file mode 100644 index 0000000..0e8fed6 --- /dev/null +++ b/ui/surface/accelerated_surface_transformer_win_unittest.cc @@ -0,0 +1,525 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <d3d9.h> +#include <random> + +#include "base/basictypes.h" +#include "base/hash.h" +#include "base/scoped_native_library.h" +#include "base/stringprintf.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest-param-test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "ui/surface/accelerated_surface_transformer_win.h" +#include "ui/surface/accelerated_surface_win.h" +#include "ui/surface/d3d9_utils_win.h" + +namespace d3d_utils = ui_surface_d3d9_utils; + +using base::win::ScopedComPtr; +using std::uniform_int_distribution; + +// Provides a reference rasterizer (all rendering done by software emulation) +// Direct3D device, for use by unit tests. +// +// This class is parameterized so that it runs only on Vista+. See +// WindowsVersionIfVistaOrBetter() for details on this works. +class AcceleratedSurfaceTransformerTest : public testing::TestWithParam<int> { + public: + AcceleratedSurfaceTransformerTest() {}; + + IDirect3DDevice9Ex* device() { return device_.get(); } + + virtual void SetUp() { + if (!d3d_module_.is_valid()) { + if (!d3d_utils::LoadD3D9(&d3d_module_)) { + GTEST_FAIL() << "Could not load d3d9.dll"; + return; + } + } + if (!d3d_utils::CreateDevice(d3d_module_, + D3DDEVTYPE_HAL, + D3DPRESENT_INTERVAL_IMMEDIATE, + device_.Receive())) { + GTEST_FAIL() << "Could not create Direct3D device."; + return; + } + + SeedRandom("default"); + } + + virtual void TearDown() { + device_ = NULL; + } + + // Gets a human-readable identifier of the graphics hardware being used, + // intended for use inside of SCOPED_TRACE(). + std::string GetAdapterInfo() { + ScopedComPtr<IDirect3D9> d3d; + EXPECT_HRESULT_SUCCEEDED(device()->GetDirect3D(d3d.Receive())); + D3DADAPTER_IDENTIFIER9 info; + EXPECT_HRESULT_SUCCEEDED(d3d->GetAdapterIdentifier(0, 0, &info)); + return StringPrintf("Running on graphics hardware: %s", info.Description); + } + + void SeedRandom(const char* seed) { + rng_.seed(base::Hash(seed)); + random_dword_.reset(); + } + + // Driver workaround: on an Intel GPU (Mobile Intel 965 Express), it seems + // necessary to flush between drawing and locking, for the synchronization + // to behave properly. + void BeforeLockWorkaround() { + EXPECT_HRESULT_SUCCEEDED( + device()->Present(0, 0, 0, 0)); + } + + // Locks and fills a surface with a checkerboard pattern where the colors + // are random but the total image pattern is horizontally and vertically + // symmetric. + void FillSymmetricRandomCheckerboard( + IDirect3DSurface9* lockable_surface, + const gfx::Size& size, + int checker_square_size) { + + D3DLOCKED_RECT locked_rect; + ASSERT_HRESULT_SUCCEEDED( + lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_DISCARD)); + DWORD* surface = reinterpret_cast<DWORD*>(locked_rect.pBits); + ASSERT_EQ(0, locked_rect.Pitch % sizeof(DWORD)); + int pitch = locked_rect.Pitch / sizeof(DWORD); + + for (int y = 0; y <= size.height() / 2; y += checker_square_size) { + for (int x = 0; x <= size.width() / 2; x += checker_square_size) { + DWORD color = RandomColor(); + int y_limit = std::min(size.height() / 2, y + checker_square_size - 1); + int x_limit = std::min(size.width() / 2, x + checker_square_size - 1); + for (int y_lo = y; y_lo <= y_limit; y_lo++) { + for (int x_lo = x; x_lo <= x_limit; x_lo++) { + int y_hi = size.height() - 1 - y_lo; + int x_hi = size.width() - 1 - x_lo; + surface[x_lo + y_lo*pitch] = color; + surface[x_lo + y_hi*pitch] = color; + surface[x_hi + y_lo*pitch] = color; + surface[x_hi + y_hi*pitch] = color; + } + } + } + } + + lockable_surface->UnlockRect(); + } + + void FillRandomCheckerboard( + IDirect3DSurface9* lockable_surface, + const gfx::Size& size, + int checker_square_size) { + + D3DLOCKED_RECT locked_rect; + ASSERT_HRESULT_SUCCEEDED( + lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_DISCARD)); + DWORD* surface = reinterpret_cast<DWORD*>(locked_rect.pBits); + ASSERT_EQ(0, locked_rect.Pitch % sizeof(DWORD)); + int pitch = locked_rect.Pitch / sizeof(DWORD); + + for (int y = 0; y <= size.height(); y += checker_square_size) { + for (int x = 0; x <= size.width(); x += checker_square_size) { + DWORD color = RandomColor(); + int y_limit = std::min(size.height(), y + checker_square_size); + int x_limit = std::min(size.width(), x + checker_square_size); + for (int square_y = y; square_y < y_limit; square_y++) { + for (int square_x = x; square_x < x_limit; square_x++) { + surface[square_x + square_y*pitch] = color; + } + } + } + } + + lockable_surface->UnlockRect(); + } + + // Approximate color-equality check. Allows for some rounding error. + bool AssertSameColor(DWORD color_a, DWORD color_b) { + if (color_a == color_b) + return true; + uint8* a = reinterpret_cast<uint8*>(&color_a); + uint8* b = reinterpret_cast<uint8*>(&color_b); + int max_error = 0; + for (int i = 0; i < 4; i++) + max_error = std::max(max_error, + std::abs(static_cast<int>(a[i]) - b[i])); + + if (max_error <= kAbsoluteColorErrorTolerance) + return true; + + std::string expected_color = + StringPrintf("%3d, %3d, %3d, %3d", a[0], a[1], a[2], a[3]); + std::string actual_color = + StringPrintf("%3d, %3d, %3d, %3d", b[0], b[1], b[2], b[3]); + EXPECT_EQ(expected_color, actual_color) + << "Componentwise color difference was " + << max_error << "; max allowed is " << kAbsoluteColorErrorTolerance; + + return false; + } + + // Asserts that an image is symmetric with respect to itself: both + // horizontally and vertically, within the tolerance of AssertSameColor. + void AssertSymmetry(IDirect3DSurface9* lockable_surface, + const gfx::Size& size) { + BeforeLockWorkaround(); + + D3DLOCKED_RECT locked_rect; + ASSERT_HRESULT_SUCCEEDED( + lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_READONLY)); + ASSERT_EQ(0, locked_rect.Pitch % sizeof(DWORD)); + int pitch = locked_rect.Pitch / sizeof(DWORD); + DWORD* surface = reinterpret_cast<DWORD*>(locked_rect.pBits); + for (int y_lo = 0; y_lo < size.height() / 2; y_lo++) { + int y_hi = size.height() - 1 - y_lo; + for (int x_lo = 0; x_lo < size.width() / 2; x_lo++) { + int x_hi = size.width() - 1 - x_lo; + if (!AssertSameColor(surface[x_lo + y_lo*pitch], + surface[x_hi + y_lo*pitch])) { + lockable_surface->UnlockRect(); + GTEST_FAIL() << "Pixels (" << x_lo << ", " << y_lo << ") vs. " + << "(" << x_hi << ", " << y_lo << ")"; + } + if (!AssertSameColor(surface[x_hi + y_lo*pitch], + surface[x_hi + y_hi*pitch])) { + lockable_surface->UnlockRect(); + GTEST_FAIL() << "Pixels (" << x_hi << ", " << y_lo << ") vs. " + << "(" << x_hi << ", " << y_hi << ")"; + } + if (!AssertSameColor(surface[x_hi + y_hi*pitch], + surface[x_lo + y_hi*pitch])) { + lockable_surface->UnlockRect(); + GTEST_FAIL() << "Pixels (" << x_hi << ", " << y_hi << ") vs. " + << "(" << x_lo << ", " << y_hi << ")"; + } + } + } + lockable_surface->UnlockRect(); + } + + // Asserts that the actual image is a bit-identical, vertically mirrored + // copy of the expected image. + void AssertIsInvertedCopy(const gfx::Size& size, + IDirect3DSurface9* expected, + IDirect3DSurface9* actual) { + BeforeLockWorkaround(); + + D3DLOCKED_RECT locked_expected, locked_actual; + ASSERT_HRESULT_SUCCEEDED( + expected->LockRect(&locked_expected, NULL, D3DLOCK_READONLY)); + ASSERT_HRESULT_SUCCEEDED( + actual->LockRect(&locked_actual, NULL, D3DLOCK_READONLY)); + ASSERT_EQ(0, locked_expected.Pitch % sizeof(DWORD)); + int pitch = locked_expected.Pitch / sizeof(DWORD); + DWORD* expected_image = reinterpret_cast<DWORD*>(locked_expected.pBits); + DWORD* actual_image = reinterpret_cast<DWORD*>(locked_actual.pBits); + for (int y = 0; y < size.height(); y++) { + int y_actual = size.height() - 1 - y; + for (int x = 0; x < size.width(); ++x) + if (!AssertSameColor(expected_image[y*pitch + x], + actual_image[y_actual*pitch + x])) { + expected->UnlockRect(); + actual->UnlockRect(); + GTEST_FAIL() << "Pixels (" << x << ", " << y << ") vs. " + << "(" << x << ", " << y_actual << ")"; + } + } + expected->UnlockRect(); + actual->UnlockRect(); + } + + protected: + static const int kAbsoluteColorErrorTolerance = 5; + + DWORD RandomColor() { + return random_dword_(rng_); + } + + void DoResizeBilinearTest(AcceleratedSurfaceTransformer* gpu_ops, + const gfx::Size& src_size, + const gfx::Size& dst_size, + int checkerboard_size) { + + SCOPED_TRACE( + StringPrintf("Resizing %dx%d -> %dx%d at checkerboard size of %d", + src_size.width(), src_size.height(), + dst_size.width(), dst_size.height(), + checkerboard_size)); + + base::win::ScopedComPtr<IDirect3DSurface9> src, dst; + ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface( + device(), src_size, src.Receive())) + << "Could not create src render target"; + ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface( + device(), dst_size, dst.Receive())) + << "Could not create dst render target"; + + FillSymmetricRandomCheckerboard(src, src_size, checkerboard_size); + + ASSERT_TRUE(gpu_ops->ResizeBilinear(src, gfx::Rect(src_size), dst)); + + AssertSymmetry(dst, dst_size); + } + + void DoCopyInvertedTest(AcceleratedSurfaceTransformer* gpu_ops, + const gfx::Size& size) { + + SCOPED_TRACE( + StringPrintf("CopyInverted @ %dx%d", size.width(), size.height())); + + base::win::ScopedComPtr<IDirect3DSurface9> checkerboard, src, dst; + base::win::ScopedComPtr<IDirect3DTexture9> src_texture; + ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface(device(), size, + checkerboard.Receive())) << "Could not create src render target";; + ASSERT_TRUE(d3d_utils::CreateTemporaryRenderTargetTexture(device(), size, + src_texture.Receive(), src.Receive())) + << "Could not create src texture."; + ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface(device(), size, + dst.Receive())) << "Could not create dst render target."; + + FillRandomCheckerboard(checkerboard, size, 1); + ASSERT_HRESULT_SUCCEEDED( + device()->StretchRect(checkerboard, NULL, src, NULL, D3DTEXF_NONE)); + ASSERT_TRUE(gpu_ops->CopyInverted(src_texture, dst, size)); + AssertIsInvertedCopy(size, checkerboard, dst); + } + + uniform_int_distribution<DWORD> random_dword_; + std::mt19937 rng_; + base::ScopedNativeLibrary d3d_module_; + base::win::ScopedComPtr<IDirect3DDevice9Ex> device_; +}; + +// Fails on some bots because Direct3D isn't allowed. +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_Init) { + SCOPED_TRACE(GetAdapterInfo()); + AcceleratedSurfaceTransformer gpu_ops; + ASSERT_TRUE(gpu_ops.Init(device())); +}; + +// Fails on some bots because Direct3D isn't allowed. +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_TestConsistentRandom) { + // This behavior should be the same for every execution on every machine. + // Otherwise tests might be flaky and impossible to debug. + SeedRandom("AcceleratedSurfaceTransformerTest.TestConsistentRandom"); + ASSERT_EQ(2922058934, RandomColor()); + + SeedRandom("AcceleratedSurfaceTransformerTest.TestConsistentRandom"); + ASSERT_EQ(2922058934, RandomColor()); + ASSERT_EQ(4050239976, RandomColor()); + + SeedRandom("DifferentSeed"); + ASSERT_EQ(3904108833, RandomColor()); +} + +// Fails on some bots because Direct3D isn't allowed. +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_CopyInverted) { + // This behavior should be the same for every execution on every machine. + // Otherwise tests might be flaky and impossible to debug. + SCOPED_TRACE(GetAdapterInfo()); + SeedRandom("CopyInverted"); + + AcceleratedSurfaceTransformer t; + ASSERT_TRUE(t.Init(device())); + + uniform_int_distribution<int> size(1, 512); + + for (int i = 0; i < 100; ++i) { + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(size(rng_), size(rng_)))) + << "At iteration " << i; + } +} + + +// Fails on some bots because Direct3D isn't allowed. +// Fails on other bots because of ResizeBilinear symmetry failures. +// Should pass, at least, on NVIDIA Quadro 600. +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_MixedOperations) { + SCOPED_TRACE(GetAdapterInfo()); + SeedRandom("MixedOperations"); + + AcceleratedSurfaceTransformer t; + ASSERT_TRUE(t.Init(device())); + + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 1)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 2)); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(20, 107))); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 5)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(64, 64), 5)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(3, 3), 1)); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(1412, 124))); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 1)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 2)); + + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(1512, 7))); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 5)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 256), 8)); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(1521, 3))); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 256), 1)); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(33, 712))); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 8), 8)); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&t, gfx::Size(33, 2))); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&t, gfx::Size(200, 256), gfx::Size(126, 8), 8)); +} + +// Tests ResizeBilinear with 16K wide/hight src and dst surfaces. +// +// Fails on some bots because Direct3D isn't allowed. +// Should pass, at least, on NVIDIA Quadro 600. +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_LargeSurfaces) { + SCOPED_TRACE(GetAdapterInfo()); + SeedRandom("LargeSurfaces"); + + AcceleratedSurfaceTransformer gpu_ops; + ASSERT_TRUE(gpu_ops.Init(device())); + + D3DCAPS9 caps; + ASSERT_HRESULT_SUCCEEDED( + device()->GetDeviceCaps(&caps)); + + SCOPED_TRACE(StringPrintf("max texture size: %dx%d, max texture aspect: %d", + caps.MaxTextureWidth, caps.MaxTextureHeight, caps.MaxTextureAspectRatio)); + + const int w = caps.MaxTextureWidth; + const int h = caps.MaxTextureHeight; + const int lo = 256; + + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&gpu_ops, gfx::Size(w, lo), gfx::Size(lo, lo), 1)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, h), gfx::Size(lo, lo), 1)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, lo), gfx::Size(w, lo), lo)); + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, lo), gfx::Size(lo, h), lo)); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&gpu_ops, gfx::Size(w, lo))); + ASSERT_NO_FATAL_FAILURE( + DoCopyInvertedTest(&gpu_ops, gfx::Size(lo, h))); +} + +// Exercises ResizeBilinear with random minification cases where the +// aspect ratio does not change. +// +// Fails on some bots because Direct3D isn't allowed. +// Fails on other bots because of StretchRect symmetry failures. +// Should pass, at least, on NVIDIA Quadro 600. +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_MinifyUniform) { + SCOPED_TRACE(GetAdapterInfo()); + SeedRandom("MinifyUniform"); + + AcceleratedSurfaceTransformer gpu_ops; + ASSERT_TRUE(gpu_ops.Init(device())); + + int dims[] = { 21, 63, 64, 65, 99, 127, 128, 129, 192, 255, 256, 257}; + int checkerboards[] = {1, 2, 3, 9}; + uniform_int_distribution<int> dim(0, arraysize(dims) - 1); + uniform_int_distribution<int> checkerboard(0, arraysize(checkerboards) - 1); + + for (int i = 0; i < 300; i++) { + // Widths are picked so that dst is smaller than src. + int dst_width = dims[dim(rng_)]; + int src_width = dims[dim(rng_)]; + if (src_width < dst_width) + std::swap(dst_width, src_width); + + // src_width is picked to preserve aspect ratio. + int dst_height = dims[dim(rng_)]; + int src_height = static_cast<int>( + static_cast<int64>(src_width) * dst_height / dst_width); + + int checkerboard_size = checkerboards[checkerboard(rng_)]; + + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&gpu_ops, + gfx::Size(src_width, src_height), // Src size (larger) + gfx::Size(dst_width, dst_height), // Dst size (smaller) + checkerboard_size)) << "Failed on iteration " << i; + } +}; + +// Exercises ResizeBilinear with random magnification cases where the +// aspect ratio does not change. +// +// This test relies on an assertion that resizing preserves symmetry in the +// image, but for the current implementation of ResizeBilinear, this does not +// seem to be true (fails on NVIDIA Quadro 600; passes on +// Intel Mobile 965 Express) +TEST_P(AcceleratedSurfaceTransformerTest, FLAKY_MagnifyUniform) { + SCOPED_TRACE(GetAdapterInfo()); + SeedRandom("MagnifyUniform"); + + AcceleratedSurfaceTransformer gpu_ops; + ASSERT_TRUE(gpu_ops.Init(device())); + + int dims[] = {63, 64, 65, 99, 127, 128, 129, 192, 255, 256, 257}; + int checkerboards[] = {1, 2, 3, 9}; + uniform_int_distribution<int> dim(0, arraysize(dims) - 1); + uniform_int_distribution<int> checkerboard(0, arraysize(checkerboards) - 1); + + for (int i = 0; i < 50; i++) { + // Widths are picked so that b is smaller than a. + int dst_width = dims[dim(rng_)]; + int src_width = dims[dim(rng_)]; + if (dst_width < src_width) + std::swap(src_width, dst_width); + + int dst_height = dims[dim(rng_)]; + int src_height = static_cast<int>( + static_cast<int64>(src_width) * dst_height / dst_width); + + int checkerboard_size = checkerboards[checkerboard(rng_)]; + + ASSERT_NO_FATAL_FAILURE( + DoResizeBilinearTest(&gpu_ops, + gfx::Size(src_width, src_height), // Src size (smaller) + gfx::Size(dst_width, dst_height), // Dst size (larger) + checkerboard_size)) << "Failed on iteration " << i; + } +}; + +namespace { + +// Used to suppress test on Windows versions prior to Vista. +std::vector<int> WindowsVersionIfVistaOrBetter() { + std::vector<int> result; + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + result.push_back(base::win::GetVersion()); + } + return result; +} + +} // namespace + +INSTANTIATE_TEST_CASE_P(VistaAndUp, + AcceleratedSurfaceTransformerTest, + ::testing::ValuesIn(WindowsVersionIfVistaOrBetter())); diff --git a/ui/surface/accelerated_surface_win.cc b/ui/surface/accelerated_surface_win.cc index 0fa247d7..0ac8f1c 100644 --- a/ui/surface/accelerated_surface_win.cc +++ b/ui/surface/accelerated_surface_win.cc @@ -8,7 +8,6 @@ #include <windows.h> #include <algorithm> -#include "accelerated_surface_win_hlsl_compiled.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" @@ -30,33 +29,15 @@ #include "ui/base/win/hwnd_util.h" #include "ui/gfx/rect.h" #include "ui/gl/gl_switches.h" +#include "ui/surface/accelerated_surface_transformer_win.h" +#include "ui/surface/d3d9_utils_win.h" - -using ui_surface::AcceleratedSurfaceWinHLSL::kVsOneTexture; -using ui_surface::AcceleratedSurfaceWinHLSL::kPsOneTexture; - +namespace d3d_utils = ui_surface_d3d9_utils; namespace { -typedef HRESULT (WINAPI *Direct3DCreate9ExFunc)(UINT sdk_version, - IDirect3D9Ex **d3d); - -const wchar_t kD3D9ModuleName[] = L"d3d9.dll"; -const char kCreate3D9DeviceExName[] = "Direct3DCreate9Ex"; - const char kUseOcclusionQuery[] = "use-occlusion-query"; -struct Vertex { - float x, y, z, w; - float u, v; -}; - -const static D3DVERTEXELEMENT9 g_vertexElements[] = { - { 0, 0, D3DDECLTYPE_FLOAT4, 0, D3DDECLUSAGE_POSITION, 0 }, - { 0, 16, D3DDECLTYPE_FLOAT2, 0, D3DDECLUSAGE_TEXCOORD, 0 }, - D3DDECL_END() -}; - UINT GetPresentationInterval() { if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync)) return D3DPRESENT_INTERVAL_IMMEDIATE; @@ -68,53 +49,6 @@ bool UsingOcclusionQuery() { return CommandLine::ForCurrentProcess()->HasSwitch(kUseOcclusionQuery); } -// Calculate the number necessary to transform |src_subrect| into |dst_size| -// by repeating downsampling of the image of |src_subrect| by a factor no more -// than 2. -int GetResampleCount(const gfx::Rect& src_subrect, - const gfx::Size& dst_size, - const gfx::Size& back_buffer_size) { - // At least one copy is required, since the back buffer itself is not - // lockable. - int min_resample_count = 1; - int width_count = 0; - int width = src_subrect.width(); - while (width > dst_size.width()) { - ++width_count; - width >>= 1; - } - int height_count = 0; - int height = src_subrect.height(); - while (height > dst_size.height()) { - ++height_count; - height >>= 1; - } - return std::max(std::max(width_count, height_count), - min_resample_count); -} - -// Returns half the size of |size| no smaller than |min_size|. -gfx::Size GetHalfSizeNoLessThan(const gfx::Size& size, - const gfx::Size& min_size) { - return gfx::Size(std::max(min_size.width(), size.width() / 2), - std::max(min_size.height(), size.height() / 2)); -} - -bool CreateTemporarySurface(IDirect3DDevice9* device, - const gfx::Size& size, - IDirect3DSurface9** surface) { - HRESULT hr = device->CreateRenderTarget( - size.width(), - size.height(), - D3DFMT_A8R8G8B8, - D3DMULTISAMPLE_NONE, - 0, - TRUE, - surface, - NULL); - return SUCCEEDED(hr); -} - } // namespace // A PresentThread is a thread that is dedicated to presenting surfaces to a @@ -126,6 +60,9 @@ class PresentThread : public base::Thread, IDirect3DDevice9Ex* device() { return device_.get(); } IDirect3DQuery9* query() { return query_.get(); } + AcceleratedSurfaceTransformer* surface_transformer() { + return &surface_transformer_; + } void InitDevice(); void ResetDevice(); @@ -141,11 +78,11 @@ class PresentThread : public base::Thread, base::ScopedNativeLibrary d3d_module_; base::win::ScopedComPtr<IDirect3DDevice9Ex> device_; - // This query is used to wait until a certain amount of progress has been // made by the GPU and it is safe for the producer to modify its shared // texture again. base::win::ScopedComPtr<IDirect3DQuery9> query_; + AcceleratedSurfaceTransformer surface_transformer_; DISALLOW_COPY_AND_ASSIGN(PresentThread); }; @@ -198,7 +135,7 @@ void PresentThread::InitDevice() { return; TRACE_EVENT0("gpu", "PresentThread::Init"); - d3d_module_.Reset(base::LoadNativeLibrary(FilePath(kD3D9ModuleName), NULL)); + d3d_utils::LoadD3D9(&d3d_module_); ResetDevice(); } @@ -210,92 +147,32 @@ void PresentThread::ResetDevice() { query_ = NULL; device_ = NULL; - Direct3DCreate9ExFunc create_func = reinterpret_cast<Direct3DCreate9ExFunc>( - d3d_module_.GetFunctionPointer(kCreate3D9DeviceExName)); - if (!create_func) - return; - - base::win::ScopedComPtr<IDirect3D9Ex> d3d; - HRESULT hr = create_func(D3D_SDK_VERSION, d3d.Receive()); - if (FAILED(hr)) - return; - - // Any old window will do to create the device. In practice the window to - // present to is an argument to IDirect3DDevice9::Present. - HWND window = GetShellWindow(); - - D3DPRESENT_PARAMETERS parameters = { 0 }; - parameters.BackBufferWidth = 1; - parameters.BackBufferHeight = 1; - parameters.BackBufferCount = 1; - parameters.BackBufferFormat = D3DFMT_A8R8G8B8; - parameters.hDeviceWindow = window; - parameters.Windowed = TRUE; - parameters.Flags = 0; - parameters.PresentationInterval = GetPresentationInterval(); - parameters.SwapEffect = D3DSWAPEFFECT_COPY; - - hr = d3d->CreateDeviceEx( - D3DADAPTER_DEFAULT, - D3DDEVTYPE_HAL, - window, - D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING | - D3DCREATE_DISABLE_PSGP_THREADING | D3DCREATE_MULTITHREADED, - ¶meters, - NULL, - device_.Receive()); - if (FAILED(hr)) + if (!d3d_utils::CreateDevice(d3d_module_, + D3DDEVTYPE_HAL, + GetPresentationInterval(), + device_.Receive())) { return; + } if (UsingOcclusionQuery()) { - hr = device_->CreateQuery(D3DQUERYTYPE_OCCLUSION, query_.Receive()); + HRESULT hr = device_->CreateQuery(D3DQUERYTYPE_OCCLUSION, query_.Receive()); if (FAILED(hr)) { device_ = NULL; return; } } else { - hr = device_->CreateQuery(D3DQUERYTYPE_EVENT, query_.Receive()); + HRESULT hr = device_->CreateQuery(D3DQUERYTYPE_EVENT, query_.Receive()); if (FAILED(hr)) { device_ = NULL; return; } } - base::win::ScopedComPtr<IDirect3DVertexShader9> vertex_shader; - hr = device_->CreateVertexShader( - reinterpret_cast<const DWORD*>(kVsOneTexture), - vertex_shader.Receive()); - if (FAILED(hr)) { - device_ = NULL; + if (!surface_transformer_.Init(device_)) { query_ = NULL; - return; - } - - device_->SetVertexShader(vertex_shader); - - base::win::ScopedComPtr<IDirect3DPixelShader9> pixel_shader; - hr = device_->CreatePixelShader( - reinterpret_cast<const DWORD*>(kPsOneTexture), - pixel_shader.Receive()); - - if (FAILED(hr)) { device_ = NULL; - query_ = NULL; return; } - - device_->SetPixelShader(pixel_shader); - - base::win::ScopedComPtr<IDirect3DVertexDeclaration9> vertex_declaration; - hr = device_->CreateVertexDeclaration(g_vertexElements, - vertex_declaration.Receive()); - if (FAILED(hr)) { - device_ = NULL; - query_ = NULL; - return; - } - - device_->SetVertexDeclaration(vertex_declaration); } void PresentThread::Init() { @@ -305,6 +182,7 @@ void PresentThread::Init() { void PresentThread::CleanUp() { // The D3D device and query are leaked because destroying the associated D3D // query crashes some Intel drivers. + surface_transformer_.DetachAll(); device_.Detach(); query_.Detach(); } @@ -382,6 +260,7 @@ AcceleratedPresenter::AcceleratedPresenter(gfx::PluginWindowHandle window) hidden_(true) { } +// static scoped_refptr<AcceleratedPresenter> AcceleratedPresenter::GetForWindow( gfx::PluginWindowHandle window) { return g_accelerated_presenter_map.Pointer()->GetPresenter(window); @@ -470,6 +349,9 @@ bool AcceleratedPresenter::DoCopyTo(const gfx::Rect& requested_src_subrect, if (!swap_chain_) return false; + AcceleratedSurfaceTransformer* gpu_ops = + present_thread_->surface_transformer(); + base::win::ScopedComPtr<IDirect3DSurface9> back_buffer; HRESULT hr = swap_chain_->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, @@ -490,65 +372,23 @@ bool AcceleratedPresenter::DoCopyTo(const gfx::Rect& requested_src_subrect, // the requested src subset. Clip to the actual back buffer. gfx::Rect src_subrect = requested_src_subrect; src_subrect.Intersect(gfx::Rect(back_buffer_size)); - - // Set up intermediate buffers needed for downsampling. - const int resample_count = - GetResampleCount(src_subrect, dst_size, back_buffer_size); base::win::ScopedComPtr<IDirect3DSurface9> final_surface; - base::win::ScopedComPtr<IDirect3DSurface9> temp_buffer[2]; - if (resample_count == 0) - final_surface = back_buffer; - if (resample_count > 0) { - TRACE_EVENT0("gpu", "CreateTemporarySurface"); - if (!CreateTemporarySurface(present_thread_->device(), - dst_size, - final_surface.Receive())) - return false; - } - const gfx::Size half_size = - GetHalfSizeNoLessThan(src_subrect.size(), dst_size); - if (resample_count > 1) { - TRACE_EVENT0("gpu", "CreateTemporarySurface"); - if (!CreateTemporarySurface(present_thread_->device(), - half_size, - temp_buffer[0].Receive())) - return false; - } - if (resample_count > 2) { - TRACE_EVENT0("gpu", "CreateTemporarySurface"); - const gfx::Size quarter_size = GetHalfSizeNoLessThan(half_size, dst_size); - if (!CreateTemporarySurface(present_thread_->device(), - quarter_size, - temp_buffer[1].Receive())) + { + TRACE_EVENT0("gpu", "CreateTemporaryLockableSurface"); + if (!d3d_utils::CreateTemporaryLockableSurface(present_thread_->device(), + dst_size, + final_surface.Receive())) { return false; + } } - // Repeat downsampling the surface until its size becomes identical to - // |dst_size|. We keep the factor of each downsampling no more than two - // because using a factor more than two can introduce aliasing. - RECT read_rect = src_subrect.ToRECT(); - gfx::Size write_size = half_size; - int read_buffer_index = 1; - int write_buffer_index = 0; - for (int i = 0; i < resample_count; ++i) { - TRACE_EVENT0("gpu", "StretchRect"); - base::win::ScopedComPtr<IDirect3DSurface9> read_buffer = - (i == 0) ? back_buffer : temp_buffer[read_buffer_index]; - base::win::ScopedComPtr<IDirect3DSurface9> write_buffer = - (i == resample_count - 1) ? final_surface : - temp_buffer[write_buffer_index]; - RECT write_rect = gfx::Rect(write_size).ToRECT(); - hr = present_thread_->device()->StretchRect(read_buffer, - &read_rect, - write_buffer, - &write_rect, - D3DTEXF_LINEAR); - if (FAILED(hr)) + { + // Let the surface transformer start the resize into |final_surface|. + TRACE_EVENT0("gpu", "ResizeBilinear"); + if (!gpu_ops->ResizeBilinear(back_buffer, src_subrect, final_surface)) return false; - read_rect = write_rect; - write_size = GetHalfSizeNoLessThan(write_size, dst_size); - std::swap(read_buffer_index, write_buffer_index); } + D3DLOCKED_RECT locked_rect; // Empirical evidence seems to suggest that LockRect and memcpy are faster @@ -716,18 +556,13 @@ void AcceleratedPresenter::DoPresentAndAcknowledge( } if (!source_texture_.get()) { - TRACE_EVENT0("gpu", "CreateTexture"); - HANDLE handle = reinterpret_cast<HANDLE>(surface_handle); - hr = present_thread_->device()->CreateTexture(size.width(), - size.height(), - 1, - D3DUSAGE_RENDERTARGET, - D3DFMT_A8R8G8B8, - D3DPOOL_DEFAULT, - source_texture_.Receive(), - &handle); - if (FAILED(hr)) + TRACE_EVENT0("gpu", "OpenSharedTexture"); + if (!d3d_utils::OpenSharedTexture(present_thread_->device(), + surface_handle, + size, + source_texture_.Receive())) { return; + } } base::win::ScopedComPtr<IDirect3DSurface9> source_surface; @@ -754,44 +589,15 @@ void AcceleratedPresenter::DoPresentAndAcknowledge( { TRACE_EVENT0("gpu", "Copy"); - // Use a simple pixel / vertex shader pair to render a quad that flips the - // source texture on the vertical axis. - IDirect3DSurface9 *default_render_target = NULL; - present_thread_->device()->GetRenderTarget(0, &default_render_target); - - present_thread_->device()->SetRenderTarget(0, dest_surface); - present_thread_->device()->SetTexture(0, source_texture_); - - D3DVIEWPORT9 viewport = { - 0, 0, - size.width(), size.height(), - 0, 1 - }; - present_thread_->device()->SetViewport(&viewport); - - float halfPixelX = -1.0f / size.width(); - float halfPixelY = 1.0f / size.height(); - Vertex vertices[] = { - { halfPixelX - 1, halfPixelY + 1, 0.5f, 1, 0, 1 }, - { halfPixelX + 1, halfPixelY + 1, 0.5f, 1, 1, 1 }, - { halfPixelX + 1, halfPixelY - 1, 0.5f, 1, 1, 0 }, - { halfPixelX - 1, halfPixelY - 1, 0.5f, 1, 0, 0 } - }; - if (UsingOcclusionQuery()) { present_thread_->query()->Issue(D3DISSUE_BEGIN); } - present_thread_->device()->BeginScene(); - present_thread_->device()->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, - 2, - vertices, - sizeof(vertices[0])); - present_thread_->device()->EndScene(); - - present_thread_->device()->SetTexture(0, NULL); - present_thread_->device()->SetRenderTarget(0, default_render_target); - default_render_target->Release(); + // Copy while flipping the source texture on the vertical axis. + bool result = present_thread_->surface_transformer()->CopyInverted( + source_texture_, dest_surface, size); + if (!result) + return; } hr = present_thread_->query()->Issue(D3DISSUE_END); diff --git a/ui/surface/d3d9_utils_win.cc b/ui/surface/d3d9_utils_win.cc new file mode 100644 index 0000000..d5cab8e --- /dev/null +++ b/ui/surface/d3d9_utils_win.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/surface/d3d9_utils_win.h"
+
+#include "base/file_path.h"
+#include "base/scoped_native_library.h"
+#include "base/win/scoped_comptr.h"
+#include "ui/gfx/size.h"
+
+namespace {
+
+const wchar_t kD3D9ModuleName[] = L"d3d9.dll";
+const char kCreate3D9DeviceExName[] = "Direct3DCreate9Ex";
+typedef HRESULT (WINAPI *Direct3DCreate9ExFunc)(UINT sdk_version,
+ IDirect3D9Ex **d3d);
+} // namespace
+
+namespace ui_surface_d3d9_utils {
+
+bool LoadD3D9(base::ScopedNativeLibrary* storage) {
+ storage->Reset(base::LoadNativeLibrary(FilePath(kD3D9ModuleName), NULL));
+ return storage->is_valid();
+}
+
+bool CreateDevice(const base::ScopedNativeLibrary& d3d_module,
+ D3DDEVTYPE device_type,
+ uint32 presentation_interval,
+ IDirect3DDevice9Ex** device) {
+
+ Direct3DCreate9ExFunc create_func = reinterpret_cast<Direct3DCreate9ExFunc>(
+ d3d_module.GetFunctionPointer(kCreate3D9DeviceExName));
+ if (!create_func)
+ return false;
+
+ base::win::ScopedComPtr<IDirect3D9Ex> d3d;
+ HRESULT hr = create_func(D3D_SDK_VERSION, d3d.Receive());
+ if (FAILED(hr))
+ return false;
+
+ // Any old window will do to create the device. In practice the window to
+ // present to is an argument to IDirect3DDevice9::Present.
+ HWND window = GetShellWindow();
+
+ D3DPRESENT_PARAMETERS parameters = { 0 };
+ parameters.BackBufferWidth = 1;
+ parameters.BackBufferHeight = 1;
+ parameters.BackBufferCount = 1;
+ parameters.BackBufferFormat = D3DFMT_A8R8G8B8;
+ parameters.hDeviceWindow = window;
+ parameters.Windowed = TRUE;
+ parameters.Flags = 0;
+ parameters.PresentationInterval = presentation_interval;
+ parameters.SwapEffect = D3DSWAPEFFECT_COPY;
+
+ hr = d3d->CreateDeviceEx(
+ D3DADAPTER_DEFAULT,
+ device_type,
+ window,
+ D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING |
+ D3DCREATE_DISABLE_PSGP_THREADING | D3DCREATE_MULTITHREADED,
+ ¶meters,
+ NULL,
+ device);
+ return SUCCEEDED(hr);
+}
+
+bool OpenSharedTexture(IDirect3DDevice9* device,
+ int64 surface_handle,
+ const gfx::Size& size,
+ IDirect3DTexture9** opened_texture) {
+ HANDLE handle = reinterpret_cast<HANDLE>(surface_handle);
+ HRESULT hr = device->CreateTexture(size.width(),
+ size.height(),
+ 1,
+ D3DUSAGE_RENDERTARGET,
+ D3DFMT_A8R8G8B8,
+ D3DPOOL_DEFAULT,
+ opened_texture,
+ &handle);
+ return SUCCEEDED(hr);
+}
+
+bool CreateTemporaryLockableSurface(IDirect3DDevice9* device,
+ const gfx::Size& size,
+ IDirect3DSurface9** surface) {
+ HRESULT hr = device->CreateRenderTarget(
+ size.width(),
+ size.height(),
+ D3DFMT_A8R8G8B8,
+ D3DMULTISAMPLE_NONE,
+ 0,
+ TRUE,
+ surface,
+ NULL);
+ return SUCCEEDED(hr);
+}
+
+bool CreateTemporaryRenderTargetTexture(IDirect3DDevice9* device,
+ const gfx::Size& size,
+ IDirect3DTexture9** texture,
+ IDirect3DSurface9** render_target) {
+ HRESULT hr = device->CreateTexture(
+ size.width(),
+ size.height(),
+ 1, // Levels
+ D3DUSAGE_RENDERTARGET,
+ D3DFMT_A8R8G8B8,
+ D3DPOOL_DEFAULT,
+ texture,
+ NULL);
+ if (!SUCCEEDED(hr))
+ return false;
+ hr = (*texture)->GetSurfaceLevel(0, render_target);
+ return SUCCEEDED(hr);
+}
+
+} // namespace ui_surface_d3d9_utils
diff --git a/ui/surface/d3d9_utils_win.h b/ui/surface/d3d9_utils_win.h new file mode 100644 index 0000000..a9554dc --- /dev/null +++ b/ui/surface/d3d9_utils_win.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Helper functions that Direct3D 9Ex code a little easier to work with for
+// the ui/surface code.
+
+#ifndef UI_SURFACE_D3D9_UTILS_WIN_H_
+#define UI_SURFACE_D3D9_UTILS_WIN_H_
+
+#include <d3d9.h>
+
+#include "base/basictypes.h"
+#include "ui/surface/surface_export.h"
+
+namespace base {
+class ScopedNativeLibrary;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace ui_surface_d3d9_utils {
+
+// Visible for testing. Loads the Direct3D9 library. Returns true on success.
+SURFACE_EXPORT
+bool LoadD3D9(base::ScopedNativeLibrary* storage);
+
+// Visible for testing. Creates a Direct3D9 device suitable for use with the
+// accelerated surface code. Returns true on success.
+SURFACE_EXPORT
+bool CreateDevice(const base::ScopedNativeLibrary& d3d_module,
+ D3DDEVTYPE device_type,
+ uint32 presentation_interval,
+ IDirect3DDevice9Ex** device);
+
+// Calls the Vista+ (WDDM1.0) variant of CreateTexture that semantically
+// opens a texture allocated (possibly in another process) as shared. The
+// shared texture is identified by its surface handle. The resulting texture
+// is written into |opened_texture|.
+//
+// Returns true on success.
+SURFACE_EXPORT
+bool OpenSharedTexture(IDirect3DDevice9* device,
+ int64 surface_handle,
+ const gfx::Size& size,
+ IDirect3DTexture9** opened_texture);
+
+// Create a one-off lockable surface of a specified size.
+//
+// Returns true on success.
+SURFACE_EXPORT
+bool CreateTemporaryLockableSurface(IDirect3DDevice9* device,
+ const gfx::Size& size,
+ IDirect3DSurface9** surface);
+
+// Create a one-off renderable texture of a specified size. The texture object
+// as well as the surface object for the texture's level 0 is returned (callers
+// almost always need to use both).
+//
+// Returns true on success.
+SURFACE_EXPORT
+bool CreateTemporaryRenderTargetTexture(IDirect3DDevice9* device,
+ const gfx::Size& size,
+ IDirect3DTexture9** texture,
+ IDirect3DSurface9** render_target);
+
+} // namespace ui_surface_d3d9_utils
+
+#endif // UI_SURFACE_D3D9_UTILS_WIN_H_
\ No newline at end of file diff --git a/ui/surface/surface.gyp b/ui/surface/surface.gyp index d0ef432..fa08607 100644 --- a/ui/surface/surface.gyp +++ b/ui/surface/surface.gyp @@ -66,9 +66,13 @@ 'sources': [ 'accelerated_surface_mac.cc', 'accelerated_surface_mac.h', + 'accelerated_surface_transformer_win.cc', + 'accelerated_surface_transformer_win.h', + 'accelerated_surface_transformer_win.hlsl', 'accelerated_surface_win.cc', 'accelerated_surface_win.h', - 'accelerated_surface_win.hlsl', + 'd3d9_utils_win.cc', + 'd3d9_utils_win.h', 'io_surface_support_mac.cc', 'io_surface_support_mac.h', 'surface_export.h', |