diff --git a/crates/ui-egui/src/objects.rs b/crates/ui-egui/src/objects.rs
--- a/crates/ui-egui/src/objects.rs
+++ b/crates/ui-egui/src/objects.rs
@@ -513,6 +513,14 @@ fn shows_pixels(d: &ObjectDrag) -> bool {
     (d.picture && d.object.spin.is_identity()) || d.drag.grab == Grab::Move || (d.drag.grab == Grab::Rotate && d.object.text_box.is_none())
 }
 
+/// The pixels-per-page-point for a `w` x `h` preview, lowered so neither side exceeds the GPU's
+/// `max_side` (the texture is painted into the object's rect, so a smaller one is only stretched).
+fn preview_scale(px: f32, w: f32, h: f32, max_side: usize) -> f32 {
+    let room = (max_side.min(wordcraft_render::MAX_SIDE as usize).max(2) - 1) as f32;
+    let px = if px.is_finite() { px } else { 1.0 };
+    px.min(room / w.max(h).max(1.0))
+}
+
 /// `o` drawn alone over its rotated bounds, as a texture, at the canvas's `scale` (screen points
 /// per page point).
 fn preview(app: &WordApp, ctx: &egui::Context, layout: &DocLayout, o: &ObjectHit, scale: f32) -> Option<egui::TextureHandle> {
@@ -522,7 +530,13 @@ fn preview(app: &WordApp, ctx: &egui::Context, layout: &DocLayout, o: &ObjectHit
     opts.dark = app.session.view.dark_mode;
     let items = wordcraft_layout::display::object_display(&app.session.doc, page, obj, o.rect, o.text_box, &opts.display);
     let b = o.bounds();
-    let img = wordcraft_render::render_items(&app.session.doc, &items, b.x, b.y, b.w, b.h, scale * ctx.pixels_per_point(), &opts);
+    let max_side = ctx.input(|i| i.max_texture_side);
+    let px = preview_scale(scale * ctx.pixels_per_point(), b.w, b.h, max_side);
+    let img = wordcraft_render::render_items(&app.session.doc, &items, b.x, b.y, b.w, b.h, px, &opts);
+    // A texture over the device limit panics wgpu outside `Session::run`; fall back to the outline.
+    if img.width as usize > max_side || img.height as usize > max_side {
+        return None;
+    }
     let ci = egui::ColorImage::from_rgba_unmultiplied([img.width as usize, img.height as usize], &img.to_straight());
     Some(ctx.load_texture("object-drag", ci, egui::TextureOptions::LINEAR))
 }
diff --git a/crates/ui-egui/src/objects/tests.rs b/crates/ui-egui/src/objects/tests.rs
--- a/crates/ui-egui/src/objects/tests.rs
+++ b/crates/ui-egui/src/objects/tests.rs
@@ -659,3 +659,14 @@ fn rotation_drag_keeps_text_boxes_outline_only_until_their_text_can_rotate() {
     assert_eq!(selected(h.state()).unwrap().1.frame().unwrap().2.rot, 90.0);
     assert_eq!(h.state().session.undo_depth(), depth + 1);
 }
+
+#[test]
+fn drag_preview_scale_stays_within_the_gpu_texture_limit() {
+    // A 14,400 pt picture at 2x on a GPU limited to 8,192 px.
+    let px = preview_scale(2.0, 14_400.0, 100.0, 8192);
+    assert!((14_400.0f32 * px).ceil() <= 8192.0);
+    assert_eq!(preview_scale(2.0, 100.0, 50.0, 8192), 2.0);
+    for bad in [f32::NAN, f32::INFINITY, 0.0] {
+        assert!((100.0f32 * preview_scale(bad, 100.0, 100.0, 0)).ceil() <= 2.0);
+    }
+}
