Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 + tools/ci) with a "Connect to a Different Server" menu item and popout that lets the desktop app switch to any self-hosted Fluxer instance, plus fixes for well-known discovery on single-domain self-hosted deployments and a false-positive ERR_ABORTED on same-origin client redirects during the switch. Defaults to chat.fluxr.chat and uses an isolated userData directory from the official build.
This commit is contained in:
+1581
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "fluxer_nv12_gpu_pack"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-or-later"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
wgpu = "29"
|
||||
bytemuck = {version = "1.25", features = ["derive"]}
|
||||
pollster = "0.4"
|
||||
parking_lot = "0.12"
|
||||
fluxer_gpu_rebuild = {path = "../gpu-rebuild", features = ["wgpu"]}
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.8"
|
||||
|
||||
[[bench]]
|
||||
name = "pack"
|
||||
harness = false
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"measured_at": "85e057a273fd",
|
||||
"host": "darwin-arm64-apple-silicon",
|
||||
"regression_budget_percent": 5.0,
|
||||
"criterion_args": {
|
||||
"warm_up_time_sec": 2,
|
||||
"measurement_time_sec": 5
|
||||
},
|
||||
"benches": {
|
||||
"nv12_pack_1080p_cache_hit": {
|
||||
"median_ns": 1517400.0,
|
||||
"low_ns": 1492800.0,
|
||||
"high_ns": 1542000.0,
|
||||
"budget_percent_override": 20.0,
|
||||
"note": "Requires a wgpu adapter (Metal on macOS). Bench function returns early without an adapter. GPU jobs are noisy; cross-run sigma 10-15% on a loaded host."
|
||||
},
|
||||
"nv12_pack_1080p_cache_miss": {
|
||||
"median_ns": 3081300.0,
|
||||
"low_ns": 3026300.0,
|
||||
"high_ns": 3139400.0,
|
||||
"budget_percent_override": 20.0,
|
||||
"note": "Wgpu adapter required; same caveat as cache_hit."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use fluxer_nv12_gpu_pack::{Nv12Packer, PackJob, try_acquire_device};
|
||||
|
||||
struct BenchFixture {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
view: wgpu::TextureView,
|
||||
y_buf: wgpu::Buffer,
|
||||
uv_buf: wgpu::Buffer,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
fn make_fixture(width: u32, height: u32) -> Option<BenchFixture> {
|
||||
let (device, queue, _instance) = try_acquire_device()?;
|
||||
let pixels = vec![128u8; (width * height * 4) as usize];
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("nv12_gpu_pack.bench_source"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&pixels,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(width * 4),
|
||||
rows_per_image: Some(height),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let y_size = Nv12Packer::y_plane_size(width, height);
|
||||
let uv_size = Nv12Packer::uv_plane_size(width, height);
|
||||
let y_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("nv12_gpu_pack.bench_y"),
|
||||
size: y_size,
|
||||
usage: wgpu::BufferUsages::STORAGE
|
||||
| wgpu::BufferUsages::COPY_SRC
|
||||
| wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let uv_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("nv12_gpu_pack.bench_uv"),
|
||||
size: uv_size,
|
||||
usage: wgpu::BufferUsages::STORAGE
|
||||
| wgpu::BufferUsages::COPY_SRC
|
||||
| wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
Some(BenchFixture {
|
||||
device,
|
||||
queue,
|
||||
view,
|
||||
y_buf,
|
||||
uv_buf,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
fn submit_pack_with_buffers(
|
||||
fixture: &BenchFixture,
|
||||
packer: &Nv12Packer,
|
||||
y_buf: &wgpu::Buffer,
|
||||
uv_buf: &wgpu::Buffer,
|
||||
) {
|
||||
let mut encoder = fixture
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("nv12_gpu_pack.bench_encoder"),
|
||||
});
|
||||
let result = packer.pack(PackJob {
|
||||
device: &fixture.device,
|
||||
queue: &fixture.queue,
|
||||
encoder: &mut encoder,
|
||||
source: &fixture.view,
|
||||
y_out: y_buf,
|
||||
uv_out: uv_buf,
|
||||
dims: (fixture.width, fixture.height),
|
||||
});
|
||||
assert!(result.is_ok());
|
||||
fixture.queue.submit(std::iter::once(encoder.finish()));
|
||||
let _ = fixture.device.poll(wgpu::PollType::Wait {
|
||||
submission_index: None,
|
||||
timeout: None,
|
||||
});
|
||||
}
|
||||
|
||||
fn make_buffer_pair(fixture: &BenchFixture, label: &str) -> (wgpu::Buffer, wgpu::Buffer) {
|
||||
let y_size = Nv12Packer::y_plane_size(fixture.width, fixture.height);
|
||||
let uv_size = Nv12Packer::uv_plane_size(fixture.width, fixture.height);
|
||||
let y_buf = fixture.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size: y_size,
|
||||
usage: wgpu::BufferUsages::STORAGE
|
||||
| wgpu::BufferUsages::COPY_SRC
|
||||
| wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let uv_buf = fixture.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size: uv_size,
|
||||
usage: wgpu::BufferUsages::STORAGE
|
||||
| wgpu::BufferUsages::COPY_SRC
|
||||
| wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
(y_buf, uv_buf)
|
||||
}
|
||||
|
||||
fn bench_pack_1080p_cache_hit(c: &mut Criterion) {
|
||||
let Some(fixture) = make_fixture(1920, 1080) else {
|
||||
eprintln!("nv12-gpu-pack bench: no wgpu adapter, skipping");
|
||||
return;
|
||||
};
|
||||
let packer = Nv12Packer::new(&fixture.device, fixture.width, fixture.height);
|
||||
submit_pack_with_buffers(&fixture, &packer, &fixture.y_buf, &fixture.uv_buf);
|
||||
c.bench_function("nv12_pack_1080p_cache_hit", |b| {
|
||||
b.iter(|| submit_pack_with_buffers(&fixture, &packer, &fixture.y_buf, &fixture.uv_buf));
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_pack_1080p_cache_miss(c: &mut Criterion) {
|
||||
let Some(fixture) = make_fixture(1920, 1080) else {
|
||||
eprintln!("nv12-gpu-pack bench: no wgpu adapter, skipping");
|
||||
return;
|
||||
};
|
||||
let packer = Nv12Packer::new(&fixture.device, fixture.width, fixture.height);
|
||||
let (y_b, uv_b) = make_buffer_pair(&fixture, "nv12_gpu_pack.bench_alt");
|
||||
c.bench_function("nv12_pack_1080p_cache_miss", |b| {
|
||||
b.iter(|| {
|
||||
submit_pack_with_buffers(&fixture, &packer, &fixture.y_buf, &fixture.uv_buf);
|
||||
submit_pack_with_buffers(&fixture, &packer, &y_b, &uv_b);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_pack_1080p_cache_hit,
|
||||
bench_pack_1080p_cache_miss
|
||||
);
|
||||
criterion_main!(benches);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
struct PackUniforms {
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride_y: u32,
|
||||
stride_uv: u32,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var source_tex: texture_2d<f32>;
|
||||
@group(0) @binding(1) var<storage, read_write> y_plane: array<atomic<u32>>;
|
||||
@group(0) @binding(2) var<storage, read_write> uv_plane: array<atomic<u32>>;
|
||||
@group(0) @binding(3) var<uniform> dims: PackUniforms;
|
||||
|
||||
fn rgb_to_y_full(r: f32, g: f32, b: f32) -> f32 {
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
|
||||
fn limited_y(y_full: f32) -> u32 {
|
||||
let scaled = round(y_full * 219.0) + 16.0;
|
||||
return u32(clamp(scaled, 16.0, 235.0));
|
||||
}
|
||||
|
||||
fn limited_u(b: f32, y_full: f32) -> u32 {
|
||||
let centered = (b - y_full) / 1.8556;
|
||||
let scaled = round(centered * 224.0) + 128.0;
|
||||
return u32(clamp(scaled, 16.0, 240.0));
|
||||
}
|
||||
|
||||
fn limited_v(r: f32, y_full: f32) -> u32 {
|
||||
let centered = (r - y_full) / 1.5748;
|
||||
let scaled = round(centered * 224.0) + 128.0;
|
||||
return u32(clamp(scaled, 16.0, 240.0));
|
||||
}
|
||||
|
||||
fn write_y_byte(byte_offset: u32, value: u32) {
|
||||
let word_index = byte_offset / 4u;
|
||||
let shift = (byte_offset % 4u) * 8u;
|
||||
let payload = (value & 0xFFu) << shift;
|
||||
atomicOr(&y_plane[word_index], payload);
|
||||
}
|
||||
|
||||
fn write_uv_byte(byte_offset: u32, value: u32) {
|
||||
let word_index = byte_offset / 4u;
|
||||
let shift = (byte_offset % 4u) * 8u;
|
||||
let payload = (value & 0xFFu) << shift;
|
||||
atomicOr(&uv_plane[word_index], payload);
|
||||
}
|
||||
|
||||
@compute @workgroup_size(8, 8, 1)
|
||||
fn pack_nv12(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let block_x = gid.x;
|
||||
let block_y = gid.y;
|
||||
let pixel_x = block_x * 2u;
|
||||
let pixel_y = block_y * 2u;
|
||||
if (pixel_x + 1u >= dims.width) {
|
||||
return;
|
||||
}
|
||||
if (pixel_y + 1u >= dims.height) {
|
||||
return;
|
||||
}
|
||||
let p00 = textureLoad(source_tex, vec2<i32>(i32(pixel_x), i32(pixel_y)), 0);
|
||||
let p10 = textureLoad(source_tex, vec2<i32>(i32(pixel_x + 1u), i32(pixel_y)), 0);
|
||||
let p01 = textureLoad(source_tex, vec2<i32>(i32(pixel_x), i32(pixel_y + 1u)), 0);
|
||||
let p11 = textureLoad(source_tex, vec2<i32>(i32(pixel_x + 1u), i32(pixel_y + 1u)), 0);
|
||||
let y00_full = rgb_to_y_full(p00.r, p00.g, p00.b);
|
||||
let y10_full = rgb_to_y_full(p10.r, p10.g, p10.b);
|
||||
let y01_full = rgb_to_y_full(p01.r, p01.g, p01.b);
|
||||
let y11_full = rgb_to_y_full(p11.r, p11.g, p11.b);
|
||||
let y00 = limited_y(y00_full);
|
||||
let y10 = limited_y(y10_full);
|
||||
let y01 = limited_y(y01_full);
|
||||
let y11 = limited_y(y11_full);
|
||||
let row0 = pixel_y * dims.stride_y;
|
||||
let row1 = (pixel_y + 1u) * dims.stride_y;
|
||||
write_y_byte(row0 + pixel_x, y00);
|
||||
write_y_byte(row0 + pixel_x + 1u, y10);
|
||||
write_y_byte(row1 + pixel_x, y01);
|
||||
write_y_byte(row1 + pixel_x + 1u, y11);
|
||||
let r_avg = (p00.r + p10.r + p01.r + p11.r) * 0.25;
|
||||
let g_avg = (p00.g + p10.g + p01.g + p11.g) * 0.25;
|
||||
let b_avg = (p00.b + p10.b + p01.b + p11.b) * 0.25;
|
||||
let y_full_avg = rgb_to_y_full(r_avg, g_avg, b_avg);
|
||||
let u = limited_u(b_avg, y_full_avg);
|
||||
let v = limited_v(r_avg, y_full_avg);
|
||||
let uv_row = block_y * dims.stride_uv;
|
||||
let uv_offset = uv_row + block_x * 2u;
|
||||
write_uv_byte(uv_offset, u);
|
||||
write_uv_byte(uv_offset + 1u, v);
|
||||
}
|
||||
Reference in New Issue
Block a user