记一次GL error: Out of memory!的崩溃

UVC摄像头压测故障解析
现象描述:

设备外接UVC摄像头,使用uvccamera库去打开,在进行打开->关闭压测的过程中,发现到了940多次进程就崩溃,大致log如下:

2020-05-04 17:23:52.983 8657-8684/com.xx.xx E/Parcel: dup() failed in Parcel::read, i is 1, fds[i] is -1, fd_count is 2, error: Too many open files
2020-05-04 17:23:52.983 8657-8684/com.xx.xx E/Surface: dequeueBuffer: IGraphicBufferProducer::requestBuffer failed: -22
2020-05-04 17:23:52.984 8657-8684/com.xx.xx E/mali_so: encounter the first mali_error : 0x0002 : failed to allocate CPU memory (gles_fb_first_drawcall_cb at hardware/arm/mali_so_src_of_midgard/driver/product/gles/src/fb/mali_gles_fb_module_api.c:961)
2020-05-04 17:23:52.985 8657-8684/com.xx.xx E/OpenGLRenderer: GL error:  Out of memory!
2020-05-04 17:23:52.985 8657-8684/com.xx.xx A/OpenGLRenderer: GL errors! frameworks/base/libs/hwui/renderthread/CanvasContext.cpp:550
    
    --------- beginning of crash
2020-05-04 17:23:52.986 8657-8684/com.xx.xx A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 8684 (RenderThread)
2020-05-04 17:23:53.089 20578-20578/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2020-05-04 17:23:53.089 20578-20578/? A/DEBUG: Build fingerprint: 'xx/xx/xx:7.1.2/NHG47K/41:user/release-keys'
2020-05-04 17:23:53.089 20578-20578/? A/DEBUG: Revision: '0'
2020-05-04 17:23:53.089 20578-20578/? A/DEBUG: ABI: 'arm64'
2020-05-04 17:23:53.090 20578-20578/? A/DEBUG: pid: 8657, tid: 8684, name: RenderThread  >>> com.xx.xx <<<
2020-05-04 17:23:53.090 20578-20578/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
2020-05-04 17:23:53.101 20578-20578/? A/DEBUG: Abort message: 'GL errors! frameworks/base/libs/hwui/renderthread/CanvasContext.cpp:550'
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x0   0000000000000000  x1   00000000000021ec  x2   0000000000000006  x3   0000000000000008
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x4   00006e6174736e69  x5   0000000000000000  x6   0000007c2158f000  x7   0000000000ad38f8
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x8   0000000000000083  x9   ffffffffffffffdf  x10  0000000000000000  x11  0000000000000001
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x12  0000000000000018  x13  000000005eafdf28  x14  003aa793d8b06ae8  x15  00001b0e452dab8b
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x16  0000007c1ed2aee0  x17  0000007c1ecd4b38  x18  0000000000000000  x19  0000007c142824f8
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x20  0000000000000006  x21  0000007c14282450  x22  0000000000000018  x23  0000007c14281c90
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x24  0000007c1f73a34c  x25  0000007c1f73a040  x26  0000000000000001  x27  0000007bdcc11920
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     x28  0000007c115cd270  x29  0000007c14281710  x30  0000007c1ecd1f64
2020-05-04 17:23:53.102 20578-20578/? A/DEBUG:     sp   0000007c142816f0  pc   0000007c1ecd4b40  pstate 0000000060000000
2020-05-04 17:23:53.121 20578-20578/? A/DEBUG: backtrace:
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #00 pc 000000000006bb40  /system/lib64/libc.so (tgkill+8)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #01 pc 0000000000068f60  /system/lib64/libc.so (pthread_kill+64)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #02 pc 0000000000023f58  /system/lib64/libc.so (raise+24)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #03 pc 000000000001c810  /system/lib64/libc.so (abort+52)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #04 pc 0000000000010d10  /system/lib64/libcutils.so (__android_log_assert+224)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #05 pc 0000000000032b28  /system/lib64/libhwui.so
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #06 pc 0000000000035260  /system/lib64/libhwui.so
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #07 pc 000000000003ab30  /system/lib64/libhwui.so (_ZN7android10uirenderer12renderthread12RenderThread10threadLoopEv+152)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #08 pc 00000000000124b4  /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+272)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #09 pc 000000000009f210  /system/lib64/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+116)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #10 pc 0000000000068748  /system/lib64/libc.so (_ZL15__pthread_startPv+208)
2020-05-04 17:23:53.122 20578-20578/? A/DEBUG:     #11 pc 000000000001da7c  /system/lib64/libc.so (__start_thread+16)
2020-05-04 17:23:54.607 600-20582/? W/ActivityManager:   Force finishing activity com.sensetime.demo/.ui.MainActivity
解决思路

粗略一看,感觉好像与renderthread有关,毕竟是直接现场;再一看,好像是内存泄漏了, 毕竟报了Out of memory;然后就去用free、dumpsys meminfo等监测内存的变化,发现就算到了崩溃之前,内存占用也是正常的,奇怪了。

root cause

注意到最上面一行
2020-05-04 17:23:52.983 8657-8684/com.xx.xx E/Parcel: dup() failed in Parcel::read, i is 1, fds[i] is -1, fd_count is 2, error: Too many open files
报了fd的错误,Too many open files,于是可以去看看proc下面具体打开的fd是哪些

ls -la  proc/pid/fd/   

pid换成要监控的进程号,发现每次打开预览都保留了usb下面的fd,关闭的时候却没有释放,导致单进程持有fd超过1024的限制所以崩溃了,也能解释为什么每次都是到940多次的时候崩溃而内存其实并没有异常。

# worm_gear_v15_ug_quality_plus_lines_optimized_v2.py # 1. 补 STL 导入 # 2. 主线程 GLFW + Tkinter 非阻塞 # 3. 错误路径清理 GLFW # 4. 启用面剔除 # 5. 热更新网格(不重启窗口) import numpy as np import math, glm, numba as nb, logging, time, os, glfw, OpenGL.GL as gl, OpenGL.GL.shaders as shaders import tkinter as tk, tkinter.messagebox as msg, tkinter.filedialog as fd from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt from collections import namedtuple from typing import Tuple, List, Optional from stl import mesh # <<< NEW 1 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s') # ---------------- 参数结构 ---------------- class MachiningError: __slots__ = ('tool_rx_err_deg', 'tool_ry_err_deg', 'tool_rz_err_deg', 'pitch_err_um', 'tool_dx_um', 'tool_dy_um') def __init__(self, rx=0.0, ry=0.0, rz=0.0, pitch=0.0, dx=0.0, dy=0.0): self.tool_rx_err_deg = rx self.tool_ry_err_deg = ry self.tool_rz_err_deg = rz self.pitch_err_um = pitch self.tool_dx_um = dx self.tool_dy_um = dy class WormParams: __slots__ = ('module', 'teeth', 'length', 'pressure_angle', 'helix_angle', 'backlash', 'root_factor', 'pitch_radius', 'base_radius', 'lead', 'root_radius', 'tip_radius', 'axial_res', 'tooth_res') def __init__(self, m=2, z=2, L=50, alpha=20, beta=5, bl=0.1, rf=1.0): self.module, self.teeth, self.length = m, z, L self.pressure_angle = math.radians(alpha) self.helix_angle = math.radians(beta) self.backlash, self.root_factor = bl, rf self._derive() def _derive(self): self.pitch_radius = self.module * self.teeth / 2 self.base_radius = self.pitch_radius * math.cos(self.pressure_angle) self.lead = 2 * math.pi * self.pitch_radius / math.tan(self.helix_angle) self.root_radius = self.pitch_radius - 1.25 * self.module * self.root_factor self.tip_radius = self.pitch_radius + self.module complexity = max(1, min(5, int(10 / self.module))) self.axial_res = 100 * complexity self.tooth_res = 100 * complexity # ---------------- 几何计算 ---------------- @nb.njit(cache=True, fastmath=True) def theory_profile(p: WormParams) -> np.ndarray: rb, ra, rf = p.base_radius, p.tip_radius, p.root_radius n = p.tooth_res // 2 theta_max = math.sqrt((ra/rb)**2 - 1) t = np.linspace(0, theta_max, n) right = np.empty((n, 2), np.float32) for i in range(n): ti = t[i] right[i, 0] = rb * (math.cos(ti) + ti * math.sin(ti)) right[i, 1] = rb * (math.sin(ti) - ti * math.cos(ti)) left = np.empty((n, 2), np.float32) for i in range(n): ti = t[i] left[i, 0] = rb * (math.cos(-ti) - ti * math.sin(-ti)) left[i, 1] = rb * (math.sin(-ti) + ti * math.cos(-ti)) left = left[::-1] n_root = p.tooth_res // 3 angles = np.linspace(-np.pi/2 + p.pressure_angle, np.pi/2 - p.pressure_angle, n_root) root = np.empty((n_root, 2), np.float32) for i in range(n_root): ang = angles[i] root[i, 0], root[i, 1] = rf * math.cos(ang), rf * math.sin(ang) profile = np.vstack((right, root, left)) if p.backlash > 0: off = np.array([math.cos(p.pressure_angle), math.sin(p.pressure_angle)]) * p.backlash / 2 profile[:len(right)] -= off profile[-len(left):] += off return profile @nb.njit(cache=True, fastmath=True, parallel=True) def build_mesh_90_norm_color(p: WormParams, profile: np.ndarray, err: MachiningError) -> Tuple: n_prof = len(profile) z = np.linspace(0, p.length, p.axial_res, dtype=np.float32) phase = (2 * np.pi * z / p.lead).astype(np.float32) offset = (2 * np.pi * np.arange(p.teeth) / p.teeth).astype(np.float32) rx, ry, rz = math.radians(err.tool_rx_err_deg), math.radians(err.tool_ry_err_deg), math.radians(err.tool_rz_err_deg) cos_rz, sin_rz = math.cos(rz), math.sin(rz) max_vertices = p.axial_res * n_prof * p.teeth vertices = np.empty((max_vertices, 3), np.float32) normals = np.empty((max_vertices, 3), np.float32) colors = np.empty((max_vertices, 3), np.float32) vertex_count = 0 faces = [] for i in nb.prange(p.axial_res): cos_p, sin_p = math.cos(phase[i]), math.sin(phase[i]) z_val = z[i] for j in range(n_prof): x0, y0 = profile[j] x0 += err.pitch_err_um * 1e-3 * math.cos(phase[i]) y0 += err.pitch_err_um * 1e-3 * math.sin(phase[i]) x0 += err.tool_dx_um * 1e-3; y0 += err.tool_dy_um * 1e-3 new_x = x0 * cos_rz - y0 * sin_rz new_y = x0 * sin_rz + y0 * cos_rz x0, y0 = new_x, new_y for k in range(p.teeth): cos_t, sin_t = math.cos(offset[k]), math.sin(offset[k]) x = x0*(cos_p*cos_t - sin_p*sin_t) - y0*(cos_p*sin_t + sin_p*cos_t) y = x0*(sin_p*cos_t + cos_p*sin_t) + y0*(cos_p*cos_t - sin_p*sin_t) ang = math.atan2(y, x) if ang < 0: ang += 2*math.pi if ang <= 3*math.pi/2: idx = vertex_count vertices[idx] = (x, y, z_val) norm_len = math.sqrt(x*x + y*y) normals[idx] = (x/norm_len, y/norm_len, 0.0) if norm_len > 1e-6 else (0,0,1) err_val = math.sqrt((x-profile[j,0])**2 + (y-profile[j,1])**2)*1e3 colors[idx] = (min(1.0, err_val/50.0), 0.0, max(0.0, 1.0 - err_val/50.0)) vertex_count += 1 vertices = vertices[:vertex_count]; normals = normals[:vertex_count]; colors = colors[:vertex_count] n_per_layer = vertex_count // p.axial_res for i in range(p.axial_res - 1): start = i*n_per_layer; next_start = (i+1)*n_per_layer if next_start + n_per_layer > vertex_count: break for j in range(n_per_layer - 1): a, b, c, d = start+j, start+j+1, next_start+j, next_start+j+1 if d < vertex_count: faces.append((a, b, c)); faces.append((b, d, c)) return vertices, normals, colors, np.array(faces, dtype=np.uint32) def build_overlay_lines(p: WormParams, err: MachiningError) -> Tuple[np.ndarray, np.ndarray]: prof_perf = theory_profile(p) prof_err = apply_machining_errors(prof_perf, p, err) z0 = 0.0 line_perf = np.column_stack([prof_perf[:, 0], prof_perf[:, 1], np.full(len(prof_perf), z0)]) line_err = np.column_stack([prof_err[:, 0], prof_err[:, 1], np.full(len(prof_err), z0)]) return line_perf.astype(np.float32), line_err.astype(np.float32) @nb.njit(cache=True, fastmath=True) def apply_machining_errors(profile: np.ndarray, p: WormParams, err: MachiningError) -> np.ndarray: result = np.empty_like(profile) rx_rad, ry_rad, rz_rad = math.radians(err.tool_rx_err_deg), math.radians(err.tool_ry_err_deg), math.radians(err.tool_rz_err_deg) cos_rz, sin_rz = math.cos(rz_rad), math.sin(rz_rad) for i in range(len(profile)): x, y = profile[i] x += err.tool_dx_um * 1e-3; y += err.tool_dy_um * 1e-3 new_x = x * cos_rz - y * sin_rz new_y = x * sin_rz + y * cos_rz result[i] = (new_x, new_y) return result # ---------------- GLRenderer ---------------- class GLRenderer: def __init__(self): self.vao = self.ebo = self.shader = self.line_shader = None self.face_count = 0 self.line_vao_perf = self.line_vao_err = None self.line_count_perf = self.line_count_err = 0 def setup_shaders(self): vert = """#version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNorm; layout(location = 2) in vec3 aColor; uniform mat4 MVP; uniform vec3 lightDir; out vec3 color; void main(){ gl_Position = MVP * vec4(aPos, 1.0); vec3 norm = normalize(aNorm); float diff = max(dot(norm, normalize(lightDir)), 0.0); color = aColor * (0.3 + 0.7 * diff); }""" frag = """#version 330 core in vec3 color; out vec4 FragColor; void main(){ FragColor = vec4(color, 1.0); }""" self.shader = shaders.compileProgram(shaders.compileShader(vert, gl.GL_VERTEX_SHADER), shaders.compileShader(frag, gl.GL_FRAGMENT_SHADER)) line_vert = """#version 330 core layout(location = 0) in vec3 aPos; uniform mat4 MVP; void main(){ gl_Position = MVP * vec4(aPos, 1.0); }""" line_frag = """#version 330 core uniform vec3 lineColor; out vec4 FragColor; void main(){ FragColor = vec4(lineColor, 1.0); }""" self.line_shader = shaders.compileProgram(shaders.compileShader(line_vert, gl.GL_VERTEX_SHADER), shaders.compileShader(line_frag, gl.GL_FRAGMENT_SHADER)) def upload_mesh(self, vertices, normals, colors, faces): if self.vao is None: self.vao = gl.glGenVertexArrays(1) gl.glBindVertexArray(self.vao) # 顶点 if gl.glIsBuffer(gl.GLuint(0)) == gl.GL_FALSE: self.vbo = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo) gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices, gl.GL_DYNAMIC_DRAW) # <<< NEW 5 gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None); gl.glEnableVertexAttribArray(0) # 法线 if gl.glIsBuffer(gl.GLuint(0)) == gl.GL_FALSE: self.nbo = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.nbo) gl.glBufferData(gl.GL_ARRAY_BUFFER, normals.nbytes, normals, gl.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None); gl.glEnableVertexAttribArray(1) # 颜色 if gl.glIsBuffer(gl.GLuint(0)) == gl.GL_FALSE: self.cbo = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.cbo) gl.glBufferData(gl.GL_ARRAY_BUFFER, colors.nbytes, colors, gl.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(2, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None); gl.glEnableVertexAttribArray(2) # 索引 if self.ebo is None: self.ebo = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ebo) gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, faces.nbytes, faces, gl.GL_DYNAMIC_DRAW) self.face_count = len(faces) gl.glBindVertexArray(0) def upload_lines(self, line_perf, line_err): # 理论线 if self.line_vao_perf is None: self.line_vao_perf = gl.glGenVertexArrays(1) gl.glBindVertexArray(self.line_vao_perf) if gl.glIsBuffer(gl.GLuint(0)) == gl.GL_FALSE: self.line_vbo_perf = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.line_vbo_perf) gl.glBufferData(gl.GL_ARRAY_BUFFER, line_perf.nbytes, line_perf, gl.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None); gl.glEnableVertexAttribArray(0) self.line_count_perf = len(line_perf) # 误差线 if self.line_vao_err is None: self.line_vao_err = gl.glGenVertexArrays(1) gl.glBindVertexArray(self.line_vao_err) if gl.glIsBuffer(gl.GLuint(0)) == gl.GL_FALSE: self.line_vbo_err = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.line_vbo_err) gl.glBufferData(gl.GL_ARRAY_BUFFER, line_err.nbytes, line_err, gl.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None); gl.glEnableVertexAttribArray(0) self.line_count_err = len(line_err) gl.glBindVertexArray(0) def render(self, mvp, light_dir): gl.glUseProgram(self.shader) gl.glUniformMatrix4fv(gl.glGetUniformLocation(self.shader, "MVP"), 1, gl.GL_FALSE, glm.value_ptr(mvp)) gl.glUniform3f(gl.glGetUniformLocation(self.shader, "lightDir"), *light_dir) gl.glBindVertexArray(self.vao) gl.glDrawElements(gl.GL_TRIANGLES, self.face_count, gl.GL_UNSIGNED_INT, None) # 线条 gl.glUseProgram(self.line_shader) gl.glUniformMatrix4fv(gl.glGetUniformLocation(self.line_shader, "MVP"), 1, gl.GL_FALSE, glm.value_ptr(mvp)) gl.glUniform3f(gl.glGetUniformLocation(self.line_shader, "lineColor"), 1.0, 1.0, 1.0) gl.glBindVertexArray(self.line_vao_perf); gl.glDrawArrays(gl.GL_LINE_LOOP, 0, self.line_count_perf) gl.glUniform3f(gl.glGetUniformLocation(self.line_shader, "lineColor"), 1.0, 0.0, 0.0) gl.glBindVertexArray(self.line_vao_err); gl.glDrawArrays(gl.GL_LINE_LOOP, 0, self.line_count_err) gl.glBindVertexArray(0) # ---------------- GLWindow ---------------- class GLWindow: def __init__(self, params: WormParams, errors: MachiningError): self.params, self.errors = params, errors self.renderer = GLRenderer() self.camera_distance = 80.0 self.camera_rotation = glm.vec2(0.0, 0.0) self.last_mouse_pos = None self.window = None self._init_glfw() self._init_scene() def _init_glfw(self): if not glfw.init(): msg.showerror("初始化失败", "GLFW 初始化失败,请检查显卡驱动") raise RuntimeError("GLFW init failed") # <<< NEW 3 glfw.window_hint(glfw.SAMPLES, 8) glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) self.window = glfw.create_window(1400, 900, "UG级蜗杆3D - 90°切除+误差热图+叠加线", None, None) if not self.window: glfw.terminate() # <<< NEW 3 raise RuntimeError("窗口创建失败") glfw.make_context_current(self.window) glfw.set_cursor_pos_callback(self.window, self._on_mouse_move) glfw.set_scroll_callback(self.window, self._on_scroll) glfw.set_key_callback(self.window, self._on_key) gl.glEnable(gl.GL_DEPTH_TEST) gl.glEnable(gl.GL_MULTISAMPLE) gl.glEnable(gl.GL_CULL_FACE); gl.glCullFace(gl.GL_BACK) # <<< NEW 4 gl.glClearColor(0.05, 0.05, 0.05, 1.0) def _init_scene(self): self.renderer.setup_shaders() self.update_mesh() # <<< NEW 5 def update_mesh(self): # <<< NEW 5 profile = theory_profile(self.params) vertices, normals, colors, faces = build_mesh_90_norm_color(self.params, profile, self.errors) self.renderer.upload_mesh(vertices, normals, colors, faces) line_perf, line_err = build_overlay_lines(self.params, self.errors) self.renderer.upload_lines(line_perf, line_err) def _get_mvp_matrix(self): proj = glm.perspective(glm.radians(45.0), 1400/900, 0.1, 1000.0) eye = glm.vec3(0, 0, self.camera_distance) rot_x = glm.rotate(glm.mat4(1.0), self.camera_rotation.x, glm.vec3(1, 0, 0)) rot_y = glm.rotate(rot_x, self.camera_rotation.y, glm.vec3(0, 1, 0)) eye = glm.vec3(rot_y * glm.vec4(eye, 1.0)) return proj * glm.lookAt(eye, glm.vec3(0), glm.vec3(0, 1, 0)) def _on_mouse_move(self, window, x, y): if self.last_mouse_pos is not None and glfw.get_mouse_button(window, glfw.MOUSE_BUTTON_LEFT) == glfw.PRESS: dx, dy = x - self.last_mouse_pos[0], y - self.last_mouse_pos[1] self.camera_rotation.x += dy * 0.01 self.camera_rotation.y += dx * 0.01 self.last_mouse_pos = (x, y) def _on_scroll(self, window, dx, dy): self.camera_distance = max(10.0, min(200.0, self.camera_distance - dy)) def _on_key(self, window, key, scancode, action, mods): if action == glfw.PRESS and key == glfw.KEY_R: self.camera_distance = 80.0; self.camera_rotation = glm.vec2(0.0, 0.0) elif action == glfw.PRESS and key == glfw.KEY_S: self._screenshot() def _screenshot(self): width, height = glfw.get_window_size(self.window) gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) data = gl.glReadPixels(0, 0, width, height, gl.GL_RGB, gl.GL_UNSIGNED_BYTE) os.makedirs("screenshots", exist_ok=True) import imageio, time filename = f"screenshots/worm_gear_{time.strftime('%Y%m%d_%H%M%S')}.png" image = np.flipud(np.frombuffer(data, dtype=np.uint8).reshape(height, width, 3)) imageio.imwrite(filename, image) logging.info(f"截图已保存: {filename}") def main_loop(self): while not glfw.window_should_close(self.window): glfw.poll_events() gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) mvp = self._get_mvp_matrix() light_dir = glm.normalize(glm.vec3(0.5, 0.5, 1.0)) self.renderer.render(mvp, light_dir) glfw.swap_buffers(self.window) glfw.terminate() def shutdown(self): # <<< NEW 5 if self.window: glfw.set_window_should_close(self.window, True) # ---------------- Tkinter 控制 ---------------- class ControlPanel: def __init__(self, root): self.root = root self.root.title("蜗杆参数控制") self.params, self.errors = WormParams(), MachiningError() self.gl_window = None # <<< NEW 5 self._build_ui() def _build_ui(self): # 参数区 pf = tk.LabelFrame(self.root, text="蜗杆参数"); pf.pack(fill=tk.X, padx=10, pady=5) self.param_vars = {} for i, (lab, attr, minv, maxv, step) in enumerate([ ("模数 (mm)", "module", 1.0, 10.0, 0.1), ("齿数", "teeth", 1, 50, 1), ("长度 (mm)", "length", 10, 200, 1), ("压力角 (°)", "pressure_angle", 10, 30, 1), ("螺旋角 (°)", "helix_angle", 1, 20, 1), ("齿侧间隙 (mm)", "backlash", 0.0, 0.5, 0.01), ("齿根系数", "root_factor", 0.8, 1.4, 0.05)]): fr = tk.Frame(pf); fr.grid(row=i//2, column=i%2, sticky="ew", padx=5, pady=2) tk.Label(fr, text=lab, width=12).pack(side=tk.LEFT) var = tk.DoubleVar(value=getattr(self.params, attr)) tk.Spinbox(fr, from_=minv, to=maxv, increment=step, textvariable=var, width=8).pack(side=tk.RIGHT) self.param_vars[attr] = var # 误差区 ef = tk.LabelFrame(self.root, text="加工误差"); ef.pack(fill=tk.X, padx=10, pady=5) self.error_vars = {} for i, (lab, attr, minv, maxv, step) in enumerate([ ("X向误差 (μm)", "tool_dx_um", -50, 50, 1), ("Y向误差 (μm)", "tool_dy_um", -50, 50, 1), ("RX误差 (°)", "tool_rx_err_deg", -5, 5, 0.1), ("RY误差 (°)", "tool_ry_err_deg", -5, 5, 0.1), ("RZ误差 (°)", "tool_rz_err_deg", -5, 5, 0.1), ("螺距误差 (μm)", "pitch_err_um", -50, 50, 1)]): fr = tk.Frame(ef); fr.grid(row=i//2, column=i%2, sticky="ew", padx=5, pady=2) tk.Label(fr, text=lab, width=12).pack(side=tk.LEFT) var = tk.DoubleVar(value=getattr(self.errors, attr)) tk.Spinbox(fr, from_=minv, to=maxv, increment=step, textvariable=var, width=8).pack(side=tk.RIGHT) self.error_vars[attr] = var # 按钮区 bf = tk.Frame(self.root); bf.pack(fill=tk.X, padx=10, pady=10) tk.Button(bf, text="更新模型", command=self.update_model).pack(side=tk.LEFT, padx=5) tk.Button(bf, text="重置参数", command=self.reset_params).pack(side=tk.LEFT, padx=5) tk.Button(bf, text="导出STL", command=self.export_stl).pack(side=tk.LEFT, padx=5) tk.Button(bf, text="退出", command=self.root.quit).pack(side=tk.RIGHT, padx=5) def update_model(self): for attr, var in self.param_vars.items(): setattr(self.params, attr, var.get()) for attr, var in self.error_vars.items(): setattr(self.errors, attr, var.get()) self.params._derive() if self.gl_window is None: # 首次创建 self.gl_window = GLWindow(self.params, self.errors) self.root.after(100, self._tk_poll_gl) # 非阻塞轮询 else: # 热更新 self.gl_window.params = self.params self.gl_window.errors = self.errors self.gl_window.update_mesh() def _tk_poll_gl(self): # <<< NEW 2 if self.gl_window and glfw.window_should_close(self.gl_window.window): self.gl_window.shutdown() self.gl_window = None if self.gl_window: glfw.poll_events() self.root.after(100, self._tk_poll_gl) def reset_params(self): self.params, self.errors = WormParams(), MachiningError() for attr, var in {**self.param_vars, **self.error_vars}.items(): var.set(getattr(self.params if attr in self.param_vars else self.errors, attr)) def export_stl(self): profile = theory_profile(self.params) vertices, normals, colors, faces = build_mesh_90_norm_color(self.params, profile, self.errors) stl_mesh = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype)) for i, face in enumerate(faces): stl_mesh.vectors[i] = vertices[face] filename = fd.asksaveasfilename(defaultextension=".stl", filetypes=[("STL文件", "*.stl")], title="保存蜗杆STL文件") if filename: stl_mesh.save(filename) msg.showinfo("导出成功", f"STL文件已保存到: {filename}") # ---------------- main ---------------- def main(): root = tk.Tk() ControlPanel(root) root.mainloop() if __name__ == "__main__": main()
最新发布
09-19
<think> 我们面对的是一个复杂的Python代码优化任务,涉及多个功能模块。根据引用内容,用户提到了几个需要优化的功能点:STL导入、GLFW与Tkinter在主线程非阻塞协作、GLFW错误路径清理、启用面剔除、热更新网格。同时用户提到这些功能是“新增功能”,需要进行错误检查和优化。 由于没有提供具体的代码,我将从通用角度分析每个功能的优化和错误检查方向,并给出改进建议。同时,用户引用[1]中提到了一系列编译和运行时错误,这提醒我们在优化时要特别注意避免类似问题。 1. STL导入: - 错误检查:确保导入的STL文件路径正确,文件格式合法(二进制或ASCII),文件未被损坏。 - 优化:使用高效的STL解析库(如`numpy-stl`)来加速导入过程;对于大型STL文件,采用分块加载和增量显示。 2. 主线程GLFW + Tkinter非阻塞: - 问题:GLFW和Tkinter都是事件循环驱动,在主线程同时运行会阻塞。 - 优化方案:将GLFW的渲染循环放在单独线程,或者使用异步方式(如`asyncio`)来整合两个事件循环。另一种方案是使用GLFW的`glfwPostEmptyEvent`来唤醒事件循环,同时设置Tkinter的定时器定期检查消息队列。 - 错误检查:避免多线程同时操作OpenGL上下文(GLFW必须在创建它的线程中使用)。确保线程安全,例如通过队列传递消息。 3. 错误路径清理GLFW: - 问题:GLFW初始化失败或资源释放不当可能导致崩溃。 - 优化:在GLFW初始化、窗口创建、上下文创建等步骤后检查错误,并清理已分配的资源。使用`glfwGetError`获取错误信息。 - 创建清理函数:在失败时调用清理函数释放已分配的资源(如销毁窗口、终止GLFW)。 4. 启用面剔除: - 优化:正确设置面剔除(`glEnable(GL_CULL_FACE)`)和剔除背面(`glCullFace(GL_BACK)`),注意顶点绕序(默认逆时针为正面)。 - 错误检查:检查OpenGL上下文是否已正确创建;在面剔除后渲染异常(如模型部分消失)时,检查模型数据的绕序是否正确。 5. 热更新网格(不重启窗口): - 优化:动态更新顶点缓冲对象(VBO)或顶点数组对象(VAO)。当网格数据变化时,重新绑定VBO数据(使用`glBufferData`或`glBufferSubData`)。 - 错误检查:确保更新时OpenGL上下文有效;避免在更新过程中渲染,可使用同步机制(如互斥锁)。同时,检查新的网格数据是否合法(如顶点索引在有效范围内)。 考虑到引用[1]中提到的各种编译和运行时错误,我们还需要注意: - 避免在Python中使用可能导致崩溃的C扩展(特别是与OpenGL交互的部分),确保内存安全。 - 在重构时,每次只修改一小部分,并充分测试。 - 录清晰的错误日志,方便定位问题。 由于没有具体代码,以下提供一个概念性的代码片段,展示如何实现热更新网格和错误清理GLFW: 热更新网格的伪代码: ```python def update_mesh(new_vertices, new_indices): # 绑定VBO和EBO glBindBuffer(GL_ARRAY_BUFFER, vbo) glBufferData(GL_ARRAY_BUFFER, new_vertices.nbytes, new_vertices, GL_DYNAMIC_DRAW) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) glBufferData(GL_ELEMENT_ARRAY_BUFFER, new_indices.nbytes, new_indices, GL_DYNAMIC_DRAW) # 更新顶点属性指针(如果布局改变) ``` GLFW错误清理的伪代码: ```python def init_glfw(): if not glfw.init(): return False # 设置错误回调 glfw.set_error_callback(error_callback) window = glfw.create_window(...) if not window: glfw.terminate() # 清理 return False return window def error_callback(error_code, description): logging.error("GLFW error [{}]: {}".format(error_code, description)) ``` 对于STL导入,建议使用现有库,例如: ```python from stl import mesh def load_stl(file_path): try: stl_mesh = mesh.Mesh.from_file(file_path) return stl_mesh.vectors.reshape(-1, 3) # 转换为顶点数组 except Exception as e: print(f"Load STL failed: {e}") return None ``` 对于GLFW+Tkinter非阻塞,一个可行的方案是使用线程运行GLFW,同时Tkinter在主线程运行,通过线程安全的队列通信。但是注意,Tkinter不是线程安全的,所以更新Tkinter UI必须通过主线程。可以使用`threading`和`queue`模块,或者使用`asyncio`(但GLFW目前没有官方asyncio支持)。 由于用户提到“新增功能”的优化和错误检查,建议在代码中增加详细的日志录,并在关键步骤进行异常捕获。 最后,针对每个功能点,应该编写单元测试和集成测试,特别是模拟错误场景(如无效文件、GLFW初始化失败等)。 根据用户引用[2],项目使用了CMake、Boost等,但我们的代码是Python,所以主要关注Python环境的依赖和兼容性。确保安装的Python包(如PyOpenGL, glfw, numpy-stl)版本兼容。 由于问题复杂,以上仅为方向性指导。如果有具体代码,可以进一步分析。 </think> 针对您提到的几个新增功能模块,我将从优化和错误检查角度进行分析并提供解决方案建议: ### 1. STL导入模块优化 **错误检查重点:** - 文件格式验证(二进制/ASCII STL头识别) - 顶点法向量数据校验 - 内存溢出防护(大型STL文件) - 无效路径处理(关联引用[1]路径问题) **优化建议:** ```python import trimesh # 推荐使用专业库替代手动解析 def load_stl_optimized(path): try: # 增加路径规范化处理(解决引用[1]路径问题) clean_path = os.path.normpath(path) if not os.path.exists(clean_path): raise FileNotFoundError(f"STL路径不存在: {clean_path}") mesh = trimesh.load(clean_path, force='mesh') # 数据完整性校验 if not mesh.is_watertight: print("警告: 网格非封闭模型,可能导致渲染异常") # 内存安全限制 if len(mesh.vertices) > 1000000: print("警告: 顶点数超过安全阈值,建议简化模型") return mesh except Exception as e: # 增强错误分类处理 if isinstance(e, UnicodeDecodeError): print("错误: 二进制STL解码异常,尝试指定文件格式") elif isinstance(e, MemoryError): print("错误: STL文件过大导致内存溢出") else: print(f"STL导入未知错误: {str(e)}") return None ``` ### 2. GLFW + Tkinter非阻塞主线程 **关键问题:** - GLFW事件循环阻塞(关联引用[1]终端阻塞问题) - 跨线程OpenGL上下文访问冲突 - Tkinter界面冻结 **优化方案:** ```python import threading import glfw import tkinter as tk def glfw_thread(): # GLFW独立线程初始化 if not glfw.init(): return window = glfw.create_window(800, 600, "GLFW Window", None, None) glfw.make_context_current(window) # 非阻塞事件循环 while not glfw.window_should_close(window): glfw.poll_events() # 渲染代码... glfw.swap_buffers(window) glfw.terminate() def start_glfw(): # 分离GLFW到独立线程 threading.Thread(target=glfw_thread, daemon=True).start() # Tkinter主线程 root = tk.Tk() tk.Button(root, text="启动GLFW", command=start_glfw).pack() root.mainloop() ``` ### 3. GLFW错误路径清理 **错误检查重点:** - 多实例资源泄漏(关联引用[1]重定义错误) - 上下文残留问题 - 回调函数未注销 **安全清理方案:** ```python _glfw_initialized = False def safe_glfw_init(): global _glfw_initialized if not _glfw_initialized: glfw.set_error_callback(error_callback) # 注册错误回调 if glfw.init(): _glfw_initialized = True return _glfw_initialized def cleanup_glfw(): global _glfw_initialized if _glfw_initialized: # 注销所有窗口回调 for window in _active_windows: glfw.set_window_close_callback(window, None) # ...其他回调注销 glfw.terminate() glfw.set_error_callback(None) # 清除错误回调 _glfw_initialized = False def error_callback(error, description): print(f"GLFW错误 [{error}]: {description}") ``` ### 4. 面剔除优化 **常见错误检查:** - 背面法向量计算错误 - 绕序不一致导致错误剔除 - 未正确启用功能 **优化实现:** ```python def enable_face_culling(): glEnable(GL_CULL_FACE) # 自动检测法线方向 if mesh_has_consistent_winding(): glCullFace(GL_BACK) # 默认剔除背面 else: print("警告:模型绕序不一致,改用前向面剔除") glCullFace(GL_FRONT) # 性能优化:避免不必要的状态切换 glFrontFace(GL_CCW) # 逆时针为正面 # 错误检测 if glGetIntegerv(GL_CULL_FACE_MODE) != GL_BACK: print("错误:面剔除模式设置失败") ``` ### 5. 网格热更新(不重启窗口) **关键技术难点:** - 顶点缓冲对象(VBO)动态更新 - 资源同步(关联引用[1]线程安全问题) - 拓扑变化处理 **优化方案:** ```python class DynamicMesh: def __init__(self): self.vao = glGenVertexArrays(1) self.vbo = glGenBuffers(1) self.last_update_time = 0 def update_mesh(self, new_vertices): # 检查数据有效性 if not validate_vertex_data(new_vertices): print("错误:无效的顶点数据格式") return False glBindVertexArray(self.vao) glBindBuffer(GL_ARRAY_BUFFER, self.vbo) # 双缓冲策略避免卡顿 glBufferData(GL_ARRAY_BUFFER, new_vertices.nbytes, new_vertices, GL_DYNAMIC_DRAW) # 异步更新标 self.last_update_time = time.time() return True def hot_reload(self, file_path): try: new_mesh = load_stl_optimized(file_path) if self.update_mesh(new_mesh.vertices): print("网格热更新成功") except Exception as e: print(f"热更新失败: {str(e)}") # 使用示例 mesh = DynamicMesh() mesh.hot_reload("new_model.stl") ``` ### 综合错误检查建议 1. **路径处理增强**: ```python # 解决引用[1]中的路径问题 def safe_path(path): return os.path.abspath(os.path.expanduser(path)) ``` 2. **OpenGL状态检查器**: ```python def check_gl_error(): error = glGetError() if error != GL_NO_ERROR: print(f"OpenGL错误: {error}") ``` 3. **资源泄漏检测**: ```python # 使用弱引用跟踪资源 import weakref _gl_resources = weakref.WeakValueDictionary() ``` 4. **版本兼容性处理**: ```python # 处理引用[1]中的版本冲突问题 if glfw_version < (3, 3): print("警告:GLFW版本过低,某些功能可能受限") ``` ### 注意事项 1. **线程安全**:所有GLFW操作必须在创建它的线程执行(引用OpenGL上下文线程模型) 2. **错误恢复**:实现状态保存/恢复机制应对意外错误 3. **性能监控**:添加帧时间日志识别性能瓶颈 4. **兼容性测试**:需在Windows/Linux/macOS多平台验证(关联引用[1]编译问题) > 建议添加单元测试覆盖以下场景: > - 无效STL文件输入 > - 窗口创建失败处理 > - 显存溢出时的优雅降级 > - 高频热更新压力测试 > - 跨平台路径格式验证 这些优化方案针对您提到的各个功能模块进行了错误防护和性能改进,同时解决了引用[1]中提到的编译路径、资源管理和兼容性问题[^1]。对于Poly2Tri集成,需要注意C++和Python的边界数据转换(引用[2])[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值