-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDemo.java
More file actions
340 lines (291 loc) · 12.8 KB
/
Copy pathDemo.java
File metadata and controls
340 lines (291 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
package fastscreen;
import fastscreen.FastScreen;
import fasttheme.FastTheme;
import fastproportion.Proportion;
import fastproportion.ProportionMode;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* FastScreen 0.1.2 — High-FPS Scalable Desktop Duplication Demo.
*
* Features:
* - High-FPS DirectX 11 DXGI Desktop Duplication & GDI Fallback
* - Native Window Capture Exclusion (WDA_EXCLUDEFROMCAPTURE)
* - FastProportion COVER Mode: Zero borders, 100% edge-to-edge scaling
* - Anti-Aliasing toggle [A] for smooth downsampling vs raw pixel speed
* - Decoupled Producer-Consumer Architecture for Maximum Frame Rate
* - Freely resizable window with dynamic native title bar telemetry
*/
public class Demo extends Canvas {
private static final int BASE_HEIGHT = 720;
private final FastScreen screen;
private final JFrame parentFrame;
private long hwnd = 0;
// Desktop Resolution
private final int screenW;
private final int screenH;
// Double-Buffered Desktop Frames for Zero-Lock Decoupled Rendering
private final int[] bufferA;
private final int[] bufferB;
private volatile int[] activeFrontBuffer;
private final AtomicBoolean newFrameAvailable = new AtomicBoolean(false);
// FastProportion Zero-Allocation Math Context
private final Proportion proportion;
private final float[] renderBounds = new float[4];
// Offscreen Image for Display
private final BufferedImage displayImage;
private final int[] displayPixels;
// Interactive State
private volatile boolean isExcluded = true;
private volatile boolean isPaused = false;
private volatile boolean isAntiAliasing = true;
private volatile boolean running = true;
// Telemetry
private volatile double currentFps = 0.0;
private volatile double avgCaptureTimeMs = 0.8;
public Demo(JFrame parentFrame) {
this.parentFrame = parentFrame;
// 1. Detect physical desktop resolution
Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
this.screenW = screenDim.width;
this.screenH = screenDim.height;
// 2. Initialize FastProportion context
this.proportion = new Proportion(0, 0, screenW, screenH);
// 3. Compute initial window size (using desktop ratio)
int initialWidth = (int) Math.round(BASE_HEIGHT * ((double) screenW / screenH));
setPreferredSize(new Dimension(initialWidth, BASE_HEIGHT));
setMinimumSize(new Dimension(320, 180));
setIgnoreRepaint(true);
// 4. Double buffer pool for capture
int totalPixels = screenW * screenH;
this.bufferA = new int[totalPixels];
this.bufferB = new int[totalPixels];
this.activeFrontBuffer = bufferA;
// Display image for canvas blit
this.displayImage = new BufferedImage(screenW, screenH, BufferedImage.TYPE_INT_RGB);
this.displayPixels = ((DataBufferInt) displayImage.getRaster().getDataBuffer()).getData();
// 5. Initialize FastScreen
this.screen = new FastScreen();
// 6. Register Keyboard Controls
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_E -> toggleExclusion();
case KeyEvent.VK_A -> {
isAntiAliasing = !isAntiAliasing;
updateTitleBar();
}
case KeyEvent.VK_SPACE -> {
isPaused = !isPaused;
updateTitleBar();
}
case KeyEvent.VK_ESCAPE -> exitApp();
}
}
});
}
private void toggleExclusion() {
isExcluded = !isExcluded;
if (hwnd != 0) {
if (isExcluded) {
FastScreen.excludeWindow(hwnd);
} else {
FastScreen.includeWindow(hwnd);
}
}
updateTitleBar();
}
private void updateTitleBar() {
SwingUtilities.invokeLater(() -> {
int curW = getWidth();
int curH = getHeight();
String aaStr = isAntiAliasing ? "Bilinear AA [A]" : "Point [A]";
if (isPaused) {
parentFrame.setTitle(String.format(
"FastScreen 0.1.2 — PAUSED | %dx%d | AA: %s | Excl: %s [E] | [SPACE] Resume | [ESC] Exit",
curW, curH, aaStr,
isExcluded ? "ON" : "OFF"
));
} else {
parentFrame.setTitle(String.format(
"FastScreen 0.1.2 — %.1f FPS | %.2f ms | %dx%d (COVER) | AA: %s | Excl: %s [E] | [SPACE] Pause",
currentFps, avgCaptureTimeMs, curW, curH, aaStr,
isExcluded ? "ON" : "OFF"
));
}
});
}
private void exitApp() {
running = false;
if (screen != null) {
screen.stopStream();
screen.dispose();
}
parentFrame.dispose();
System.exit(0);
}
public void start() {
createBufferStrategy(2);
// 1. Retrieve native HWND and apply initial window exclusion
try {
hwnd = FastTheme.getWindowHandle(parentFrame);
if (hwnd != 0) {
FastScreen.excludeWindow(hwnd);
}
} catch (Throwable t) {
System.err.println("[FastScreen Demo] HWND note: " + t.getMessage());
}
// 2. Start Desktop Streaming
boolean streamStarted = screen.startStream(0, 0, screenW, screenH);
updateTitleBar();
// -------------------------------------------------------------
// WORKER THREAD: Dedicated High-Speed Capture Pipeline
// -------------------------------------------------------------
Thread captureThread = new Thread(() -> {
int[] backBuffer = bufferB;
while (running) {
if (isPaused) {
try { Thread.sleep(15); } catch (InterruptedException ignored) {}
continue;
}
long t0 = System.nanoTime();
int[] rawFrame = screen.getNextFrame();
long t1 = System.nanoTime();
if (rawFrame != null && rawFrame.length == backBuffer.length) {
System.arraycopy(rawFrame, 0, backBuffer, 0, rawFrame.length);
// Atomic pointer swap to present latest frame without blocking
activeFrontBuffer = backBuffer;
backBuffer = (backBuffer == bufferA) ? bufferB : bufferA;
newFrameAvailable.set(true);
double captureMs = (t1 - t0) / 1_000_000.0;
avgCaptureTimeMs = avgCaptureTimeMs * 0.9 + captureMs * 0.1;
} else if (!streamStarted) {
BufferedImage shot = screen.captureScreen();
if (shot != null) {
int[] shotPx = ((DataBufferInt) shot.getRaster().getDataBuffer()).getData();
System.arraycopy(shotPx, 0, backBuffer, 0, Math.min(shotPx.length, backBuffer.length));
activeFrontBuffer = backBuffer;
backBuffer = (backBuffer == bufferA) ? bufferB : bufferA;
newFrameAvailable.set(true);
}
}
// Yield to prevent burning 100% of a CPU core
Thread.yield();
}
}, "FastScreen-Capture-Worker");
captureThread.setDaemon(true);
captureThread.setPriority(Thread.MAX_PRIORITY);
captureThread.start();
// -------------------------------------------------------------
// RENDER THREAD: Smooth FastProportion COVER Display Loop
// -------------------------------------------------------------
new Thread(() -> {
long lastFpsTime = System.nanoTime();
int frameCount = 0;
while (running) {
// If a fresh capture frame is ready, update display image
if (newFrameAvailable.compareAndSet(true, false)) {
int[] front = activeFrontBuffer;
if (front != null) {
System.arraycopy(front, 0, displayPixels, 0, displayPixels.length);
}
}
BufferStrategy bs = getBufferStrategy();
if (bs == null || bs.contentsLost()) {
createBufferStrategy(2);
bs = getBufferStrategy();
}
if (bs != null) {
int cw = getWidth();
int ch = getHeight();
if (cw > 0 && ch > 0) {
// Compute FastProportion COVER Bounds (NEVER shows a border!)
proportion.width = cw;
proportion.height = ch;
proportion.compute(ProportionMode.COVER, renderBounds);
int drawX = Math.round(renderBounds[0]);
int drawY = Math.round(renderBounds[1]);
int drawW = Math.round(renderBounds[2]);
int drawH = Math.round(renderBounds[3]);
Graphics g = bs.getDrawGraphics();
if (g instanceof Graphics2D g2) {
if (isAntiAliasing) {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
} else {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
}
// Render edge-to-edge desktop image with zero margin
g.drawImage(displayImage, drawX, drawY, drawW, drawH, null);
g.dispose();
if (!bs.contentsLost()) {
bs.show();
}
}
}
frameCount++;
long now = System.nanoTime();
if (now - lastFpsTime >= 500_000_000L) {
currentFps = (frameCount * 1_000_000_000.0) / (now - lastFpsTime);
frameCount = 0;
lastFpsTime = now;
updateTitleBar();
}
// Smooth frame pacing (~200 FPS cap)
Thread.yield();
}
}, "FastScreen-Render-Loop").start();
}
private static BufferedImage createRoundIcon() {
BufferedImage icon = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = icon.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(new Color(0, 255, 200));
g.fillOval(4, 4, 56, 56);
g.setColor(new Color(16, 20, 24));
g.fillOval(14, 14, 36, 36);
g.setColor(new Color(0, 230, 118));
g.fillOval(24, 24, 16, 16);
g.dispose();
return icon;
}
public static void main(String[] args) {
System.setProperty("sun.awt.noerasebackground", "true");
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("FastScreen 0.1.1 — Desktop Duplication");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setIgnoreRepaint(true);
frame.setIconImage(createRoundIcon());
// Freely resizable window
frame.setResizable(true);
Demo demo = new Demo(frame);
frame.add(demo);
frame.pack();
frame.setLocationRelativeTo(null);
frame.addNotify();
// Native Windows 11 Dark Title Bar via FastTheme
try {
long hwnd = FastTheme.getWindowHandle(frame);
if (hwnd != 0) {
FastTheme.setTitleBarDarkMode(hwnd, true);
FastTheme.setTitleBarColor(hwnd, 16, 20, 24);
FastTheme.setTitleBarTextColor(hwnd, 240, 245, 250);
FastTheme.setWindowTransparency(hwnd, 255);
FastTheme.enableMica(hwnd, true);
}
} catch (Throwable t) {
System.err.println("[FastScreen Demo] FastTheme note: " + t.getMessage());
}
frame.setVisible(true);
demo.start();
demo.requestFocus();
});
}
}