feat(render): 实现独立的 OpenGL 上下文管理器
- 将 GL 上下文管理从 ModelRenderPanel 抽离到独立的 GLContextManager 类- 实现离屏渲染上下文的创建、初始化和资源管理 - 支持动态调整渲染缓冲区大小和缩放功能 - 提供线程安全的任务队列机制用于在 GL 线程执行操作 - 实现像素数据读取和转换为 BufferedImage 的完整流程- 添加摄像机拖拽状态和缩放控制的支持 -重构 ModelRenderPanel以使用新的 GLContextManager- 更新所有 GL 相关操作的调用方式指向新的上下文管理器 - 修改 dispose 流程以正确释放所有 OpenGL 资源 - 优化渲染循环和平滑缩放逻辑实现
This commit is contained in:
@@ -127,7 +127,7 @@ public class ModelLayerPanel extends JPanel {
|
||||
// 使用更可靠的方式在GL上下文中创建纹理
|
||||
try {
|
||||
// 在GL上下文中同步执行所有图层的创建
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
List<ModelPart> createdParts = new ArrayList<>();
|
||||
|
||||
@@ -665,7 +665,7 @@ public class ModelLayerPanel extends JPanel {
|
||||
if (renderPanel != null) {
|
||||
final String texName = name + "_tex";
|
||||
final String filePath = f.getAbsolutePath();
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
Texture texture = Texture.createFromFile(texName, filePath);
|
||||
if (texture != null) {
|
||||
@@ -730,7 +730,7 @@ public class ModelLayerPanel extends JPanel {
|
||||
part.addMesh(mesh);
|
||||
|
||||
if (renderPanel != null) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
Texture tex = createTextureFromBufferedImageInGL(img, name + "_tex");
|
||||
if (tex != null) {
|
||||
@@ -799,7 +799,7 @@ public class ModelLayerPanel extends JPanel {
|
||||
final String texName = sel.getName() + "_tex";
|
||||
|
||||
if (renderPanel != null) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
Texture texture = Texture.createFromFile(texName, filePath);
|
||||
if (texture != null) {
|
||||
@@ -1087,7 +1087,7 @@ public class ModelLayerPanel extends JPanel {
|
||||
if (renderPanel == null) throw new IllegalStateException("需要 renderPanel 才能在 GL 上下文创建纹理");
|
||||
|
||||
try {
|
||||
return renderPanel.executeInGLContext(() -> {
|
||||
return renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
// 静态工厂尝试
|
||||
try {
|
||||
Method factory = findStaticMethod(Texture.class, "createFromBufferedImage", BufferedImage.class);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -236,7 +236,7 @@ public class TransformPanel extends JPanel implements ModelEvent {
|
||||
// 旋转按钮监听器修改(支持多选)
|
||||
rotate90CWButton.addActionListener(e -> {
|
||||
if (!selectedParts.isEmpty()) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
Map<ModelPart, Float> oldRotations = new HashMap<>();
|
||||
Map<ModelPart, Float> newRotations = new HashMap<>();
|
||||
|
||||
@@ -263,7 +263,7 @@ public class TransformPanel extends JPanel implements ModelEvent {
|
||||
|
||||
rotate90CCWButton.addActionListener(e -> {
|
||||
if (!selectedParts.isEmpty()) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
Map<ModelPart, Float> oldRotations = new HashMap<>();
|
||||
Map<ModelPart, Float> newRotations = new HashMap<>();
|
||||
|
||||
@@ -291,7 +291,7 @@ public class TransformPanel extends JPanel implements ModelEvent {
|
||||
// 翻转按钮监听器修改(支持多选)
|
||||
flipXButton.addActionListener(e -> {
|
||||
if (!selectedParts.isEmpty()) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
Map<ModelPart, Vector2f> oldScales = new HashMap<>();
|
||||
Map<ModelPart, Vector2f> newScales = new HashMap<>();
|
||||
|
||||
@@ -318,7 +318,7 @@ public class TransformPanel extends JPanel implements ModelEvent {
|
||||
|
||||
flipYButton.addActionListener(e -> {
|
||||
if (!selectedParts.isEmpty()) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
Map<ModelPart, Vector2f> oldScales = new HashMap<>();
|
||||
Map<ModelPart, Vector2f> newScales = new HashMap<>();
|
||||
|
||||
@@ -346,7 +346,7 @@ public class TransformPanel extends JPanel implements ModelEvent {
|
||||
// 重置缩放按钮监听器修改(支持多选)
|
||||
resetScaleButton.addActionListener(e -> {
|
||||
if (!selectedParts.isEmpty()) {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
Map<ModelPart, Vector2f> oldScales = new HashMap<>();
|
||||
Map<ModelPart, Vector2f> newScales = new HashMap<>();
|
||||
|
||||
@@ -495,7 +495,7 @@ public class TransformPanel extends JPanel implements ModelEvent {
|
||||
private void applyTransformChanges() {
|
||||
if (updatingUI || selectedParts.isEmpty()) return;
|
||||
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
try {
|
||||
float posX = Float.parseFloat(positionXField.getText());
|
||||
float posY = Float.parseFloat(positionYField.getText());
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
package com.chuangzhou.vivid2D.render.awt.manager;
|
||||
|
||||
import com.chuangzhou.vivid2D.render.ModelRender;
|
||||
import com.chuangzhou.vivid2D.render.model.Model2D;
|
||||
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
public class GLContextManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GLContextManager.class);
|
||||
private long windowId;
|
||||
private volatile boolean running = true;
|
||||
private Thread renderThread;
|
||||
// 改为可变的宽高以支持动态重建离屏上下文缓冲
|
||||
private volatile int width;
|
||||
private volatile int height;
|
||||
|
||||
private BufferedImage currentFrame;
|
||||
private volatile boolean contextInitialized = false;
|
||||
private final CompletableFuture<Void> contextReady = new CompletableFuture<>();
|
||||
private final String modelPath;
|
||||
private final AtomicReference<Model2D> modelRef = new AtomicReference<>();
|
||||
|
||||
private BufferedImage lastFrame = null;
|
||||
private ByteBuffer pixelBuffer = null;
|
||||
private int[] pixelInts = null;
|
||||
private int[] argbInts = null;
|
||||
public volatile float displayScale = 1.0f; // 当前可视缩放(用于检测阈值/角点等)
|
||||
public volatile float targetScale = 1.0f; // 目标缩放(鼠标滚轮/程序改变时设置)
|
||||
|
||||
// 任务队列,用于在 GL 上下文线程执行代码
|
||||
private final BlockingQueue<Runnable> glTaskQueue = new LinkedBlockingQueue<>();
|
||||
private final ExecutorService taskExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private volatile boolean cameraDragging = false;
|
||||
private static final float ZOOM_SMOOTHING = 0.18f; // 0..1, 越大收敛越快(建议 0.12-0.25)
|
||||
private RepaintCallback repaintCallback;
|
||||
|
||||
public GLContextManager(String modelPath, int width, int height) {
|
||||
this.modelPath = modelPath;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public GLContextManager(Model2D model, int width, int height) {
|
||||
this.modelPath = null;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.modelRef.set(model);
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建离屏 OpenGL 上下文
|
||||
*/
|
||||
private void createOffscreenContext() throws Exception {
|
||||
// 设置窗口提示
|
||||
GLFW.glfwDefaultWindowHints();
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GL11.GL_TRUE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 4);
|
||||
|
||||
// 创建离屏窗口(像素尺寸以当前 width/height 为准)
|
||||
windowId = GLFW.glfwCreateWindow(width, height, "Offscreen Render", MemoryUtil.NULL, MemoryUtil.NULL);
|
||||
if (windowId == MemoryUtil.NULL) {
|
||||
throw new Exception("无法创建离屏 OpenGL 上下文");
|
||||
}
|
||||
|
||||
// 设置为当前上下文并初始化
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
GL.createCapabilities();
|
||||
|
||||
logger.info("OpenGL context created successfully");
|
||||
|
||||
// 然后初始化 RenderSystem
|
||||
RenderSystem.beginInitialization();
|
||||
RenderSystem.initRenderThread();
|
||||
|
||||
// 使用 RenderSystem 设置视口
|
||||
RenderSystem.viewport(0, 0, width, height);
|
||||
|
||||
// 分配像素读取缓冲
|
||||
int pixelCount = Math.max(1, width * height);
|
||||
pixelBuffer = MemoryUtil.memAlloc(pixelCount * 4);
|
||||
pixelBuffer.order(ByteOrder.nativeOrder());
|
||||
pixelInts = new int[pixelCount];
|
||||
argbInts = new int[pixelCount];
|
||||
|
||||
// 初始化 ModelRender
|
||||
ModelRender.initialize();
|
||||
|
||||
RenderSystem.finishInitialization();
|
||||
|
||||
// 在正确的上下文中加载模型(可能会耗时)
|
||||
loadModelInContext();
|
||||
|
||||
// 标记上下文已初始化并完成通知(只 complete 一次)
|
||||
contextInitialized = true;
|
||||
contextReady.complete(null);
|
||||
|
||||
logger.info("Offscreen context initialization completed");
|
||||
}
|
||||
|
||||
public void setRepaintCallback(RepaintCallback callback) {
|
||||
this.repaintCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 OpenGL 上下文中加载模型
|
||||
*/
|
||||
private void loadModelInContext() {
|
||||
try {
|
||||
if (modelPath != null) {
|
||||
Model2D model = Model2D.loadFromFile(modelPath);
|
||||
modelRef.set(model);
|
||||
logger.info("模型加载成功: {}", modelPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("模型加载失败: {}", e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动渲染线程
|
||||
*/
|
||||
public void startRendering() {
|
||||
// 初始化 GLFW
|
||||
if (!GLFW.glfwInit()) {
|
||||
throw new RuntimeException("无法初始化 GLFW");
|
||||
}
|
||||
renderThread = new Thread(() -> {
|
||||
try {
|
||||
createOffscreenContext();
|
||||
|
||||
// 等待上下文就绪后再开始渲染循环(contextReady 由 createOffscreenContext 完成)
|
||||
contextReady.get();
|
||||
|
||||
// 确保当前线程一直持有该 GL 上下文(避免在每个任务/帧中重复 makeCurrent)
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
|
||||
final long targetNs = 1_000_000_000L / 60L; // 60 FPS
|
||||
while (running && !GLFW.glfwWindowShouldClose(windowId)) {
|
||||
long start = System.nanoTime();
|
||||
|
||||
processGLTasks();
|
||||
displayScale += (targetScale - displayScale) * ZOOM_SMOOTHING;
|
||||
renderFrame();
|
||||
|
||||
long elapsed = System.nanoTime() - start;
|
||||
long sleepNs = targetNs - elapsed;
|
||||
if (sleepNs > 0) {
|
||||
LockSupport.parkNanos(sleepNs);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("渲染线程异常", e);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
renderThread.setDaemon(true);
|
||||
renderThread.setName("GL-Render-Thread");
|
||||
renderThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染单帧并读取到 BufferedImage
|
||||
*/
|
||||
private void renderFrame() {
|
||||
if (!contextInitialized || windowId == 0) return;
|
||||
// 确保在当前上下文中
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
Model2D currentModel = modelRef.get();
|
||||
if (currentModel != null) {
|
||||
try {
|
||||
// 使用 RenderSystem 清除缓冲区
|
||||
RenderSystem.setClearColor(0.18f, 0.18f, 0.25f, 1f);
|
||||
RenderSystem.clear(RenderSystem.GL_COLOR_BUFFER_BIT | RenderSystem.GL_DEPTH_BUFFER_BIT);
|
||||
// 渲染模型
|
||||
ModelRender.render(1.0f / 60f, currentModel);
|
||||
// 读取像素数据到 BufferedImage
|
||||
readPixelsToImage();
|
||||
} catch (Exception e) {
|
||||
System.err.println("渲染错误: " + e.getMessage());
|
||||
renderErrorFrame(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 没有模型时显示默认背景
|
||||
renderDefaultBackground();
|
||||
}
|
||||
|
||||
// 在 Swing EDT 中更新显示
|
||||
if (repaintCallback != null) {
|
||||
repaintCallback.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染默认背景
|
||||
*/
|
||||
private void renderDefaultBackground() {
|
||||
RenderSystem.setClearColor(0.1f, 0.1f, 0.15f, 1f);
|
||||
RenderSystem.clear(RenderSystem.GL_COLOR_BUFFER_BIT | RenderSystem.GL_DEPTH_BUFFER_BIT);
|
||||
readPixelsToImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染错误帧
|
||||
*/
|
||||
private void renderErrorFrame(String errorMessage) {
|
||||
GL11.glClearColor(0.3f, 0.1f, 0.1f, 1f);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
readPixelsToImage();
|
||||
BufferedImage errorImage = new BufferedImage(Math.max(1, width), Math.max(1, height), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = errorImage.createGraphics();
|
||||
g2d.setColor(Color.DARK_GRAY);
|
||||
g2d.fillRect(0, 0, errorImage.getWidth(), errorImage.getHeight());
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.drawString("渲染错误: " + errorMessage, 10, 20);
|
||||
g2d.dispose();
|
||||
currentFrame = errorImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 OpenGL 像素数据到 BufferedImage
|
||||
*/
|
||||
private void readPixelsToImage() {
|
||||
try {
|
||||
final int w = Math.max(1, this.width);
|
||||
final int h = Math.max(1, this.height);
|
||||
final int pixelCount = w * h;
|
||||
|
||||
// 确保缓冲区大小匹配(可能在 resize 后需要重建)
|
||||
if (pixelBuffer == null || pixelInts == null || pixelInts.length != pixelCount) {
|
||||
if (pixelBuffer != null) {
|
||||
try {
|
||||
MemoryUtil.memFree(pixelBuffer);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
pixelBuffer = MemoryUtil.memAlloc(pixelCount * 4);
|
||||
pixelBuffer.order(ByteOrder.nativeOrder());
|
||||
pixelInts = new int[pixelCount];
|
||||
argbInts = new int[pixelCount];
|
||||
}
|
||||
|
||||
pixelBuffer.clear();
|
||||
// 从 GPU 读取 RGBA 字节到本地缓冲 - 使用 RenderSystem
|
||||
RenderSystem.readPixels(0, 0, w, h, RenderSystem.GL_RGBA, RenderSystem.GL_UNSIGNED_BYTE, pixelBuffer);
|
||||
|
||||
// 以 int 批量读取(依赖本机字节序),然后转换为带 alpha 的 ARGB 并垂直翻转
|
||||
IntBuffer ib = pixelBuffer.asIntBuffer();
|
||||
ib.get(pixelInts, 0, pixelCount);
|
||||
|
||||
// 转换并翻转(RGBA -> ARGB)
|
||||
for (int y = 0; y < h; y++) {
|
||||
int srcRow = (h - y - 1) * w;
|
||||
int dstRow = y * w;
|
||||
for (int x = 0; x < w; x++) {
|
||||
int rgba = pixelInts[srcRow + x];
|
||||
|
||||
// 提取字节(考虑 native order,按 RGBA 存放)
|
||||
int r = (rgba >> 0) & 0xFF;
|
||||
int g = (rgba >> 8) & 0xFF;
|
||||
int b = (rgba >> 16) & 0xFF;
|
||||
int a = (rgba >> 24) & 0xFF;
|
||||
|
||||
// 组合为 ARGB (BufferedImage 使用 ARGB)
|
||||
argbInts[dstRow + x] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用一次 setRGB 写入 BufferedImage(比逐像素 setRGB 快)
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
image.setRGB(0, 0, w, h, argbInts, 0, w);
|
||||
|
||||
currentFrame = image;
|
||||
lastFrame = image;
|
||||
} catch (Exception e) {
|
||||
logger.error("读取像素数据错误", e);
|
||||
// 创建错误图像(保持原逻辑)
|
||||
BufferedImage errorImage = new BufferedImage(Math.max(1, this.width), Math.max(1, this.height), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = errorImage.createGraphics();
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.fillRect(0, 0, errorImage.getWidth(), errorImage.getHeight());
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.drawString("像素读取失败", 10, 20);
|
||||
g2d.dispose();
|
||||
currentFrame = errorImage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 GL 上下文任务队列
|
||||
*/
|
||||
private void processGLTasks() {
|
||||
Runnable task;
|
||||
while ((task = glTaskQueue.poll()) != null) {
|
||||
try {
|
||||
// 在渲染线程中执行,渲染线程已将上下文设为 current
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
logger.error("执行 GL 任务时出错", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新设置面板大小
|
||||
* <p>
|
||||
* 说明:当 Swing 面板被放大时,需要同时调整离屏 GLFW 窗口像素大小、GL 视口以及重分配像素读取缓冲,
|
||||
* 否则将把较小分辨率的图像拉伸到更大面板上导致模糊。
|
||||
*/
|
||||
public void resize(int newWidth, int newHeight) {
|
||||
executeInGLContext(() -> {
|
||||
if (contextInitialized && windowId != 0) {
|
||||
this.width = Math.max(1, newWidth);
|
||||
this.height = Math.max(1, newHeight);
|
||||
GLFW.glfwMakeContextCurrent(windowId);
|
||||
GLFW.glfwSetWindowSize(windowId, this.width, this.height);
|
||||
RenderSystem.viewport(0, 0, this.width, this.height);
|
||||
ModelRender.setViewport(this.width, this.height);
|
||||
try {
|
||||
if (pixelBuffer != null) {
|
||||
MemoryUtil.memFree(pixelBuffer);
|
||||
pixelBuffer = null;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
int pixelCount = Math.max(1, this.width * this.height);
|
||||
pixelBuffer = MemoryUtil.memAlloc(pixelCount * 4);
|
||||
pixelBuffer.order(ByteOrder.nativeOrder());
|
||||
pixelInts = new int[pixelCount];
|
||||
argbInts = new int[pixelCount];
|
||||
currentFrame = null;
|
||||
} else {
|
||||
this.width = Math.max(1, newWidth);
|
||||
this.height = Math.max(1, newHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待渲染上下文准备就绪
|
||||
*/
|
||||
public CompletableFuture<Void> waitForContext() {
|
||||
return contextReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查渲染上下文是否已初始化
|
||||
* @return true 表示已初始化,false 表示未初始化
|
||||
*/
|
||||
public boolean isContextInitialized() {
|
||||
return contextInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否正在运行
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return running && contextInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
public void dispose() {
|
||||
running = false;
|
||||
cameraDragging = false;
|
||||
// 停止任务执行器
|
||||
taskExecutor.shutdown();
|
||||
|
||||
if (renderThread != null) {
|
||||
try {
|
||||
renderThread.join(2000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
// 清理 ModelRender
|
||||
try {
|
||||
if (ModelRender.isInitialized()) {
|
||||
ModelRender.cleanup();
|
||||
logger.info("ModelRender 已清理");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("清理 ModelRender 时出错: {}", e.getMessage());
|
||||
}
|
||||
|
||||
if (windowId != 0) {
|
||||
try {
|
||||
GLFW.glfwDestroyWindow(windowId);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
windowId = 0;
|
||||
}
|
||||
|
||||
// 释放像素缓冲
|
||||
try {
|
||||
if (pixelBuffer != null) {
|
||||
MemoryUtil.memFree(pixelBuffer);
|
||||
pixelBuffer = null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("释放 pixelBuffer 时出错: {}", t.getMessage());
|
||||
}
|
||||
|
||||
// 终止 GLFW(注意:如果应用中还有其他 GLFW 窗口,这里会影响它们)
|
||||
try {
|
||||
GLFW.glfwTerminate();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
logger.info("OpenGL 资源已清理");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 GL 上下文线程上异步执行任务
|
||||
*
|
||||
* @param task 要在 GL 上下文线程中执行的任务
|
||||
* @return CompletableFuture 用于获取任务执行结果
|
||||
*/
|
||||
public CompletableFuture<Void> executeInGLContext(Runnable task) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
if (!running) {
|
||||
future.completeExceptionally(new IllegalStateException("渲染线程已停止"));
|
||||
return future;
|
||||
}
|
||||
|
||||
// 等待上下文就绪后再提交任务
|
||||
contextReady.thenRun(() -> {
|
||||
try {
|
||||
// 使用 put 保证任务不会被丢弃,如果队列已满会阻塞调用者直到可入队
|
||||
glTaskQueue.put(() -> {
|
||||
try {
|
||||
task.run();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 GL 上下文线程上异步执行任务并返回结果
|
||||
*
|
||||
* @param task 要在 GL 上下文线程中执行的有返回值的任务
|
||||
* @return CompletableFuture 用于获取任务执行结果
|
||||
*/
|
||||
public <T> CompletableFuture<T> executeInGLContext(Callable<T> task) {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
|
||||
if (!running) {
|
||||
future.completeExceptionally(new IllegalStateException("渲染线程已停止"));
|
||||
return future;
|
||||
}
|
||||
|
||||
contextReady.thenRun(() -> {
|
||||
try {
|
||||
glTaskQueue.put(() -> {
|
||||
try {
|
||||
T result = task.call();
|
||||
future.complete(result);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步在 GL 上下文线程上执行任务(会阻塞当前线程直到任务完成)
|
||||
*
|
||||
* @param task 要在 GL 上下文线程中执行的任务
|
||||
* @throws Exception 如果任务执行出错
|
||||
*/
|
||||
public void executeInGLContextSync(Runnable task) throws Exception {
|
||||
if (!running) {
|
||||
throw new IllegalStateException("渲染线程已停止");
|
||||
}
|
||||
|
||||
CompletableFuture<Void> future = executeInGLContext(task);
|
||||
future.get(10, TimeUnit.SECONDS); // 设置超时时间
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步在 GL 上下文线程上执行任务并返回结果(会阻塞当前线程直到任务完成)
|
||||
*
|
||||
* @param task 要在 GL 上下文线程中执行的有返回值的任务
|
||||
* @return 任务执行结果
|
||||
* @throws Exception 如果任务执行出错或超时
|
||||
*/
|
||||
public <T> T executeInGLContextSync(Callable<T> task) throws Exception {
|
||||
if (!running) {
|
||||
throw new IllegalStateException("渲染线程已停止");
|
||||
}
|
||||
|
||||
CompletableFuture<T> future = executeInGLContext(task);
|
||||
return future.get(10, TimeUnit.SECONDS); // 设置超时时间
|
||||
}
|
||||
|
||||
public void setDisplayScale(float scale) {
|
||||
this.displayScale = scale;
|
||||
}
|
||||
|
||||
public void setTargetScale(float scale) {
|
||||
this.targetScale = scale;
|
||||
}
|
||||
|
||||
public float getDisplayScale() {
|
||||
return displayScale;
|
||||
}
|
||||
|
||||
public float getTargetScale() {
|
||||
return targetScale;
|
||||
}
|
||||
|
||||
public interface RepaintCallback {
|
||||
void repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前帧
|
||||
* @return 当前帧
|
||||
*/
|
||||
public BufferedImage getCurrentFrame() {
|
||||
return currentFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一帧
|
||||
* @return 上一帧
|
||||
*/
|
||||
public BufferedImage getLastFrame() {
|
||||
return lastFrame;
|
||||
}
|
||||
|
||||
public boolean isCameraDragging() {
|
||||
return cameraDragging;
|
||||
}
|
||||
|
||||
public void setCameraDragging(boolean cameraDragging) {
|
||||
this.cameraDragging = cameraDragging;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public class ModelLayerPanelTest {
|
||||
// 添加选中部件更新按钮
|
||||
JButton updateSelectionBtn = new JButton("更新选中部件");
|
||||
updateSelectionBtn.addActionListener(e -> {
|
||||
renderPanel.executeInGLContext(() -> {
|
||||
renderPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
List<ModelPart> selectedPart = renderPanel.getSelectedParts();
|
||||
transformPanel.setSelectedParts(selectedPart);
|
||||
});
|
||||
@@ -134,7 +134,7 @@ public class ModelLayerPanelTest {
|
||||
public void windowClosed(java.awt.event.WindowEvent e) {
|
||||
// 进程退出(确保彻底关闭)
|
||||
try {
|
||||
renderPanel.dispose();
|
||||
renderPanel.getGlContextManager().dispose();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
model.saveToFile("C:\\Users\\Administrator\\Desktop\\testing.model");
|
||||
|
||||
@@ -41,7 +41,7 @@ public class TestModelGLPanel {
|
||||
|
||||
// 在 GL 上下文中创建 mesh / part / physics 等资源
|
||||
ModelRenderPanel finalGlPanel = glPanel;
|
||||
glPanel.executeInGLContext(() -> {
|
||||
glPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
setupModelInGL(testModel);
|
||||
return null;
|
||||
});
|
||||
@@ -53,7 +53,7 @@ public class TestModelGLPanel {
|
||||
if (!animate) return;
|
||||
float dt = 1.0f / fps;
|
||||
// 在 GL 上下文中更新模型状态(旋转、参数、物理更新等)
|
||||
finalGlPanel.executeInGLContext(() -> {
|
||||
finalGlPanel.getGlContextManager().executeInGLContext(() -> {
|
||||
updateAnimation(testModel, dt);
|
||||
return null;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user