feat: GPU shared texture offscreen rendering (#42953)

* feat: GPU shared texture offscreen rendering

* docs: clarify texture infos that passed by the paint event.

* feat: make gpu osr spec test optional

* fix: osr image compare

* fix: remove duplicate test

* fix: update patch file

* fix: code review

* feat: expose more metadata

* feat: use better switch design

* feat: add warning when user forget to release the texture.

* fix: typo

* chore: update patch

* fix: update patch

* fix: update patch description

* fix: update docs

* fix: apply suggestions from code review

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* fix: apply suggested fixes

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
reito 2024-08-23 08:23:13 +08:00 committed by GitHub
parent b481966f02
commit 1aeca6fd0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1009 additions and 102 deletions

View file

@ -15,6 +15,7 @@ import { HexColors, hasCapturableScreen, ScreenCapture } from './lib/screen-help
import { once } from 'node:events';
import { setTimeout } from 'node:timers/promises';
import { setTimeout as syncSetTimeout } from 'node:timers';
import { nativeImage } from 'electron';
const fixtures = path.resolve(__dirname, 'fixtures');
const mainFixtures = path.resolve(__dirname, 'fixtures');
@ -374,7 +375,7 @@ describe('BrowserWindow module', () => {
it('should emit did-fail-load event for files that do not exist', async () => {
const didFailLoad = once(w.webContents, 'did-fail-load');
w.loadURL('file://a.txt');
const [, code, desc,, isMainFrame] = await didFailLoad;
const [, code, desc, , isMainFrame] = await didFailLoad;
expect(code).to.equal(-6);
expect(desc).to.equal('ERR_FILE_NOT_FOUND');
expect(isMainFrame).to.equal(true);
@ -382,7 +383,7 @@ describe('BrowserWindow module', () => {
it('should emit did-fail-load event for invalid URL', async () => {
const didFailLoad = once(w.webContents, 'did-fail-load');
w.loadURL('http://example:port');
const [, code, desc,, isMainFrame] = await didFailLoad;
const [, code, desc, , isMainFrame] = await didFailLoad;
expect(desc).to.equal('ERR_INVALID_URL');
expect(code).to.equal(-300);
expect(isMainFrame).to.equal(true);
@ -399,7 +400,7 @@ describe('BrowserWindow module', () => {
it('should set `mainFrame = false` on did-fail-load events in iframes', async () => {
const didFailLoad = once(w.webContents, 'did-fail-load');
w.loadFile(path.join(fixtures, 'api', 'did-fail-load-iframe.html'));
const [,,,, isMainFrame] = await didFailLoad;
const [, , , , isMainFrame] = await didFailLoad;
expect(isMainFrame).to.equal(false);
});
it('does not crash in did-fail-provisional-load handler', (done) => {
@ -413,7 +414,7 @@ describe('BrowserWindow module', () => {
const data = Buffer.alloc(2 * 1024 * 1024).toString('base64');
const didFailLoad = once(w.webContents, 'did-fail-load');
w.loadURL(`data:image/png;base64,${data}`);
const [, code, desc,, isMainFrame] = await didFailLoad;
const [, code, desc, , isMainFrame] = await didFailLoad;
expect(desc).to.equal('ERR_INVALID_URL');
expect(code).to.equal(-300);
expect(isMainFrame).to.equal(true);
@ -4542,7 +4543,7 @@ describe('BrowserWindow module', () => {
fs.unlinkSync(savePageHtmlPath);
fs.rmdirSync(path.join(savePageDir, 'save_page_files'));
fs.rmdirSync(savePageDir);
} catch {}
} catch { }
});
it('should throw when passing relative paths', async () => {
@ -4590,7 +4591,7 @@ describe('BrowserWindow module', () => {
try {
await fs.promises.unlink(savePageMHTMLPath);
await fs.promises.rmdir(tmpDir);
} catch {}
} catch { }
});
it('should save page to disk with HTMLComplete', async () => {
@ -6367,7 +6368,7 @@ describe('BrowserWindow module', () => {
it('creates offscreen window with correct size', async () => {
const paint = once(w.webContents, 'paint') as Promise<[any, Electron.Rectangle, Electron.NativeImage]>;
w.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));
const [,, data] = await paint;
const [, , data] = await paint;
expect(data.constructor.name).to.equal('NativeImage');
expect(data.isEmpty()).to.be.false('data is empty');
const size = data.getSize();
@ -6465,6 +6466,52 @@ describe('BrowserWindow module', () => {
});
});
describe('offscreen rendering image', () => {
afterEach(closeAllWindows);
const imagePath = path.join(fixtures, 'assets', 'osr.png');
const targetImage = nativeImage.createFromPath(imagePath);
const nativeModulesEnabled = !process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS;
ifit(nativeModulesEnabled && ['win32'].includes(process.platform))('use shared texture, hardware acceleration enabled', (done) => {
const { ExtractPixels, InitializeGpu } = require('@electron-ci/osr-gpu');
try {
InitializeGpu();
} catch (e) {
console.log('Failed to initialize GPU, this spec needs a valid GPU device. Skipping...');
console.error(e);
done();
return;
}
const w = new BrowserWindow({
show: false,
webPreferences: {
offscreen: {
useSharedTexture: true
}
},
transparent: true,
frame: false,
width: 128,
height: 128
});
w.webContents.once('paint', async (e, dirtyRect) => {
try {
expect(e.texture).to.be.not.null();
const pixels = ExtractPixels(e.texture!.textureInfo);
const img = nativeImage.createFromBitmap(pixels, { width: dirtyRect.width, height: dirtyRect.height, scaleFactor: 1 });
expect(img.toBitmap().equals(targetImage.toBitmap())).to.equal(true);
done();
} catch (e) {
done(e);
}
});
w.loadFile(imagePath);
});
});
describe('"transparent" option', () => {
afterEach(closeAllWindows);