OPenGL--Transform feedback示例解析
简述
英文地址https://open.gl/feedback
到目前为止,我们总是将顶点数据发送到图形处理器,并且只在帧缓存中生成绘制的像素。如果我们想要在经过顶点着色器或几何着色器之后捕获这些顶点呢?在这一章中,我们将探讨一种方法,即transform feedback。
到目前为止,我们已经使用了VBO(顶点缓冲区对象)来存储用于绘制操作的顶点。
Transform feedback 允许着色器把顶点写回这些。
例如,您可以构建一个顶点着色器,它模拟重力并将更新后的顶点位置写回缓冲区。这样,您就不必将这些数据从图形内存传输到主内存。最重要的是,你可以从如今GPU的巨大的并行处理能力中获益。
基本Transform feedback
我们将从头开始,以便最终程序能够清楚地演示Transform feedback是多么简单。
不幸的是,这次没有预览,因为我们不会在这一章里画任何东西!尽管这个特性可以用来简化像粒子模拟这样的效果,但是解释这些内容超出了这些文章的范围。在您了解了转换反馈的基础之后,您将能够发现并理解关于这些主题的大量文章。
让我们从一个简单的顶点着色器开始。
const GLchar* vertexShaderSrc = R"glsl(in float inValue;out float outValue;void main(){outValue = sqrt(inValue);}
)glsl";
这个顶点着色器似乎没有多大意义。它没有设置一个gl_position,它只接受一个任意的浮点数作为输入。幸运的是,我们可以使用Transform feedback来捕获结果,我们马上就会看到。
GLuint shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader, 1, &vertexShaderSrc, nullptr);
glCompileShader(shader);GLuint program = glCreateProgram();
glAttachShader(program, shader);
编译着色器,创建一个程序并附加着色器,但是不要调用glLinkProgram()!在链接程序之前,我们必须告诉OpenGL,我们想要捕获到一个缓冲区中的输出属性。
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
第一个参数是不言而喻的,是着色器程序。第二个参数和第三个参数指定了输出名称数组和数组本身的长度,最后一个参数指定了应该如何写入数据。
以下两种格式可供选择:
--GL_INTERLEAVED_ATTRIBS:将所有属性写入一个缓冲区对象。。
--GL_SEPARATE_ATTRIBS: 将属性写入多个缓冲区对象,或将不同的偏移量写入缓冲区。
有时对于每个属性都有单独的缓冲区是很有用的,但是为了让我们对这个演示保持简单。现在您已经指定了输出变量,您可以链接并激活该程序。这是因为链接过程依赖于关于输出的设置。
glLinkProgram(program);
glUseProgram(program);
之后,创建和绑定VAO:
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
现在,创建的顶点着色器的输入数据缓冲区:
GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
数据中的数字是我们想要的数字,用来计算平方根,Transform feedback将帮助我们得到结果。
GLint inputAttrib = glGetAttribLocation(program, "inValue");
glEnableVertexAttribArray(inputAttrib);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
Transform feedback将返回outValue的值,但是首先我们需要创建一个VBO来保存这些值,就像输入的顶点一样:
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);
注意,我们现在传递了一个nullptr,以创建一个足够大的缓冲区,以便容纳所有生成的浮点数,但是没有指定任何初始数据。设置一个适当的使用类型GL_STATIC_READ,它表示我们打算将OpenGL写入到这个缓冲区中,我们的应用程序可以从中读取。(请参阅使用类型的参考资料)
现在我们已经为渲染计算过程做了所有的准备。由于我们不打算画任何东西,光栅化器应该被禁用:
glEnable(GL_RASTERIZER_DISCARD);
为了实际绑定我们在上面创建的缓冲区作为转换反馈缓冲区,我们必须使用一个名为glBindBufferBase的新函数。
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
第一个参数是目前需gl_transform_feedback_buffer让未来的扩展。第二个参数是输出 变量的指数,这是简单的零因为我们只有一个。最后一个参数 指定绑定的缓冲区对象。
做画的电话之前,你必须输入变换反馈模式:
目前,第一个参数是GL_TRANSFORM_FEEDBACK_BUFFER,以支持将来的扩展。第二个参数是输出变量的索引,它是0,因为我们只有一个。最后一个参数指定要绑定的缓冲区对象。
在进行绘制调用之前,您必须设置transform_feedback模式:
glBeginTransformFeedback(GL_POINTS);
它肯定会让人想起昔日的“美好时光”!就像上一章的几何着色一样,原始模式的可能值也有一些限制。
--GL_POINTS — GL_POINTS
--GL_LINES — GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY
--GL_TRIANGLES — GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES_ADJACENCY,
--GL_TRIANGLE_STRIP_ADJACENCY
如果你只有一个顶点着色器,就像我们现在所做的那样,普通模式必须与被绘制的那个元素匹配:
glDrawArrays(GL_POINTS, 0, 5);
即使我们现在使用的是数据,单个数字仍然可以看作是单独的“点”,所以我们使用普通模式。
结束转换反馈模式:
glEndTransformFeedback();
通常,在绘图操作结束时,我们将交换缓冲区以在屏幕上显示结果。我们仍然希望在尝试访问结果之前确保渲染操作已经完成,所以我们刷新OpenGL的命令缓冲区:
glFlush();
将结果返回现在就像将缓冲区数据复制回数组一样简单:
GLfloat feedback[5];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
如果您现在在数组中打印值,那么您应该在终端中看到输入的平方根:
printf("%f %f %f %f %f\n", feedback[0], feedback[1], feedback[2], feedback[3], feedback[4]);
打印结果
恭喜你,你现在知道如何让你的GPU执行带有顶点着色器的通用任务!当然,像OpenCL这样的真正的GPGPU框架通常更好,但是Feedback transform的优势在于,您可以直接在绘图操作中重新使用数据,
例如将Feedback transform缓冲区作为数组缓冲区进行绑定,并执行正常的绘图调用。
如果你有显卡和驱动程序支持它,你也可以在OpenGL4.3中使用计算着色器,它实际上是为与绘图相关的任务设计的。
以下是完整的代码
// Link statically with GLEW
#define GLEW_STATIC// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(#version 150 corein float inValue;out float outValue;void main(){outValue = sqrt(inValue);}
)glsl";int main()
{sf::ContextSettings settings;settings.depthBits = 24;settings.stencilBits = 8;sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);// Initialize GLEWglewExperimental = GL_TRUE;glewInit();// Compile shaderGLuint shader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(shader, 1, &vertexShaderSrc, nullptr);glCompileShader(shader);// Create program and specify transform feedback variablesGLuint program = glCreateProgram();glAttachShader(program, shader);const GLchar* feedbackVaryings[] = { "outValue" };glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);glLinkProgram(program);glUseProgram(program);// Create VAOGLuint vao;glGenVertexArrays(1, &vao);glBindVertexArray(vao);// Create input VBO and vertex formatGLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };GLuint vbo;glGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);GLint inputAttrib = glGetAttribLocation(program, "inValue");glEnableVertexAttribArray(inputAttrib);glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);// Create transform feedback bufferGLuint tbo;glGenBuffers(1, &tbo);glBindBuffer(GL_ARRAY_BUFFER, tbo);glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);// Perform feedback transformglEnable(GL_RASTERIZER_DISCARD);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);glBeginTransformFeedback(GL_POINTS);glDrawArrays(GL_POINTS, 0, 5);glEndTransformFeedback();glDisable(GL_RASTERIZER_DISCARD);glFlush();// Fetch and print resultsGLfloat feedback[5];glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);printf("%f %f %f %f %f\n", feedback[0], feedback[1], feedback[2], feedback[3], feedback[4]);glDeleteProgram(program);glDeleteShader(shader);glDeleteBuffers(1, &tbo);glDeleteBuffers(1, &vbo);glDeleteVertexArrays(1, &vao);window.close();return 0;
}
Feedback transform和几何着色器
当你有几何着色器,变换反馈操作将 捕捉几何着色器的输出而不是顶点着色器。对于 例子:
// 顶点着色器
const GLchar* vertexShaderSrc = R"glsl(in float inValue;out float geoValue;void main(){geoValue = sqrt(inValue);}
)glsl";//几何着色器
const GLchar* geoShaderSrc = R"glsl(layout(points) in;layout(triangle_strip, max_vertices = 3) out;in float[] geoValue;out float outValue;void main(){for (int i = 0; i < 3; i++) {outValue = geoValue[0] + i;EmitVertex();}EndPrimitive();}
)glsl";
几何着色器接受顶点着色器处理的一个点,生成一个三角形,每个点都有一个更高的值。
GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
glCompileShader(geoShader);...glAttachShader(program, geoShader);
编译并将几何着色器附加到程序中以开始使用它。
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
虽然现在的输出现在来自于几何着色器,但我们没有改变名称,所以这段代码保持不变。
因为每个输入顶点将生成3个顶点作为输出,所以Transform feedback 缓冲区现在需要3倍于输入缓冲区的大小:
glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);
当使用几何着色器时,指定为glBeginTransformFeedback的类型必须与几何着色器的输出类型匹配:
glBeginTransformFeedback(GL_TRIANGLES);
检索输出仍然是相同的:
//获取并打印结果
GLfloat feedback[15];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);for (int i = 0; i < 15; i++) {printf("%f\n", feedback[i]);
}
打印结果
虽然你要注意反馈的数据类型和你的缓冲区的大小 ,但添加几何着色器的方程并没有太大变化, 除了负责输出着色器。
以下是完整的代码
// Link statically with GLEW
#define GLEW_STATIC// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(#version 150 corein float inValue;out float geoValue;void main(){geoValue = sqrt(inValue);}
)glsl";// Geometry shader
const GLchar* geoShaderSrc = R"glsl(#version 150 corelayout(points) in;layout(triangle_strip, max_vertices = 3) out;in float[] geoValue;out float outValue;void main(){for (int i = 0; i < 3; i++) {outValue = geoValue[0] + i;EmitVertex();}EndPrimitive();}
)glsl";int main()
{sf::ContextSettings settings;settings.depthBits = 24;settings.stencilBits = 8;sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);// Initialize GLEWglewExperimental = GL_TRUE;glewInit();// Compile shadersGLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr);glCompileShader(vertexShader);GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);glCompileShader(geoShader);// Create program and specify transform feedback variablesGLuint program = glCreateProgram();glAttachShader(program, vertexShader);glAttachShader(program, geoShader);const GLchar* feedbackVaryings[] = { "outValue" };glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);glLinkProgram(program);glUseProgram(program);// Create VAOGLuint vao;glGenVertexArrays(1, &vao);glBindVertexArray(vao);// Create input VBO and vertex formatGLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };GLuint vbo;glGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);GLint inputAttrib = glGetAttribLocation(program, "inValue");glEnableVertexAttribArray(inputAttrib);glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);// Create transform feedback bufferGLuint tbo;glGenBuffers(1, &tbo);glBindBuffer(GL_ARRAY_BUFFER, tbo);glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);// Perform feedback transformglEnable(GL_RASTERIZER_DISCARD);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);glBeginTransformFeedback(GL_TRIANGLES);glDrawArrays(GL_POINTS, 0, 5);glEndTransformFeedback();glDisable(GL_RASTERIZER_DISCARD);glFlush();// Fetch and print resultsGLfloat feedback[15];glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);for (int i = 0; i < 15; i++) {printf("%f\n", feedback[i]);}glDeleteProgram(program);glDeleteShader(geoShader);glDeleteShader(vertexShader);glDeleteBuffers(1, &tbo);glDeleteBuffers(1, &vbo);glDeleteVertexArrays(1, &vao);window.close();return 0;
}
Transform feedback变量反馈
正如我们在前一章中看到的,几何着色器有惟一的属性来生成一个可变数量的数据。幸运的是,有一些方法可以通过"查询对象"获得输出图元的个数。
就像OpenGL中的所有其他对象一样,你必须先创建一个:
GLuint query;
glGenQueries(1, &query);
然后在调用glBeginTransformFeedback之前,你必须告诉OpenGL跟踪图元的数量:
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
调用glendtransformfeedback后,你可以停止“记录”:
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
检索结果如下:
GLuint primitives;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);
然后,您可以将该值与其他数据一起打印出来:
printf("%u primitives written!\n\n", primitives);
打印结果
以下是完整的代码
// Link statically with GLEW
#define GLEW_STATIC// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(#version 150 corein float inValue;out float geoValue;void main(){geoValue = sqrt(inValue);}
)glsl";// Geometry shader
const GLchar* geoShaderSrc = R"glsl(#version 150 corelayout(points) in;layout(triangle_strip, max_vertices = 3) out;in float[] geoValue;out float outValue;void main(){for (int i = 0; i < 3; i++) {outValue = geoValue[0] + i;EmitVertex();}EndPrimitive();}
)glsl";int main()
{sf::ContextSettings settings;settings.depthBits = 24;settings.stencilBits = 8;sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);// Initialize GLEWglewExperimental = GL_TRUE;glewInit();// Compile shadersGLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr);glCompileShader(vertexShader);GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);glCompileShader(geoShader);// Create program and specify transform feedback variablesGLuint program = glCreateProgram();glAttachShader(program, vertexShader);glAttachShader(program, geoShader);const GLchar* feedbackVaryings[] = { "outValue" };glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);glLinkProgram(program);glUseProgram(program);// Create VAOGLuint vao;glGenVertexArrays(1, &vao);glBindVertexArray(vao);// Create input VBO and vertex formatGLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };GLuint vbo;glGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);GLint inputAttrib = glGetAttribLocation(program, "inValue");glEnableVertexAttribArray(inputAttrib);glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);// Create transform feedback bufferGLuint tbo;glGenBuffers(1, &tbo);glBindBuffer(GL_ARRAY_BUFFER, tbo);glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);// Create query object to collect infoGLuint query;glGenQueries(1, &query);// Perform feedback transformglEnable(GL_RASTERIZER_DISCARD);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);glBeginTransformFeedback(GL_TRIANGLES);glDrawArrays(GL_POINTS, 0, 5);glEndTransformFeedback();glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);glDisable(GL_RASTERIZER_DISCARD);glFlush();// Fetch and print resultsGLuint primitives;glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);GLfloat feedback[15];glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);printf("%u primitives written!\n\n", primitives);for (int i = 0; i < 15; i++) {printf("%f\n", feedback[i]);}glDeleteQueries(1, &query);glDeleteProgram(program);glDeleteShader(geoShader);glDeleteShader(vertexShader);glDeleteBuffers(1, &tbo);glDeleteBuffers(1, &vbo);glDeleteVertexArrays(1, &vao);window.close();return 0;
}
注意,它返回的是图元的数量,而不是顶点的数量。因为我们有15个顶点,每个三角形都有3个,我们有5个图元。
查询对象也可以用来记录一些东西,比如在处理几何着色器和gltime用时,用来测量在服务器上花费的时间(图形卡)工作的时间。如果你被困在某个地方,
结论
你现在已经对几何学的阴影和Transform feedback有了足够的了解,让你的图形卡做一些非常有趣的工作,而不仅仅是绘画!您甚至可以组合Variable feedback和光栅化来更新顶点并在同一时间绘制它们!
OPenGL--Transform feedback示例解析相关推荐
- OpenGL Transform Feedback转换反馈的实例
OpenGL Transform Feedback转换反馈 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include "vapp.h" ...
- OpenGL Transform Feedback
官网链接 使用TransFrom Feedback捕获图元数据,必须注意的几点: 必须在链接着色器之前设置要捕获的输出变量:glTransformFeedbackVaryings 顶点着色器.细分着色 ...
- OpenGL学习笔记 transform feedback缓存粒子系统示例分析
http://blog.csdn.net/coderling/article/details/44742077 transform feedback是OpenGL中比较酷炫的特性之一,他让客户端应用程 ...
- OpenGL Partical System by Transform Feedback
使用OpenGL的变换反馈(transform feedback)构造粒子系统 粒子系统是三维计算机图形学中用来模拟火,爆炸,雾,雪,流行尾迹或者发光轨迹等视觉效果的技术.粒子系统模拟这些现象的步奏简 ...
- NDK OpenGL ES 3.0 开发(七):Transform Feedback
该原创文章首发于微信公众号:字节流动 什么是 Transform Feedback Transform Feedback(变换反馈)是在 OpenGLES3.0 渲染管线中,顶点处理阶段结束之后,图元 ...
- OpenGL ES之变换反馈Transform Feedback的使用流程
一.什么是 Transform Feedback ? Transform Feedback(变换反馈)是在 OpenGL ES 3.0 渲染管线中,顶点处理阶段结束之后,图元装配和光栅化之前的一个步骤 ...
- java聊天程序步骤解析_java网络之基于UDP的聊天程序示例解析
基于UDP的Socket通信 UDP协议不是一种基于稳定连接的协议,是一种面向数据报包的通信协议,不需要通信双方建立稳定的连接,也没有所谓服务端和客户的概念,数据报包在传输的时候不保证一定及时到达,也 ...
- Java 中pdf部分内容加边线_Java 在PDF中添加骑缝章示例解析
骑缝章是用于往来业务合同,以确保合同真实.有效的印章加盖方法,是一种防范风险的重要方式.在Java程序中,可以通过使用工具来辅助加盖这种骑缝章. 工具:Free Spire.PDF for Java ...
- java spring省略jsp,Java +Tomcat + SpringMVC实现页面访问示例解析
window7下Java环境安装记录: 一.安装Tomcat 1.下载tomcat 7.0,解压,无需安装,放置到目录:D:\apache-tomcat-7.0.90. 2.配置系统环境变量,CATA ...
最新文章
- 新版中青——青龙羊毛
- 创建交互式shell脚本对话框
- C#文件夹权限操作工具类
- 代码动态创建checkbox
- 浩鲸科技与帆软达成战略合作,重磅推出数据中台联合解决方案
- Lua学习笔记5:类及继承的实现
- libuv 网络库设计概览译
- Java-static-静态方法/全局方法
- vue require图片_手把手教你使用require.context实现前端自动化
- 磕头如捣的拼音及解释
- 满纸辛酸泪 —— 红楼梦中话
- 《CLR via C#》精髓:静态类
- Tomcat 加载外部dll时如何配置
- mybatis查询返回一个三级目录
- echarts Map(地图) 波纹数据点
- 空格符号复制html,cf空格符号复制(cf空格代码)
- AVR32单片机 矩阵按键 按键键值函数解析
- BZOJ 3813 奇数国
- Foldor for Mac(文件夹图标样式修改工具)
- qt夜神android,夜神安卓模拟器命令行整理贴
热门文章
- 概念结构设计、逻辑结构设计、物理设计的区分
- RestfulToolKit:便捷的 IDEA 接口测试神器
- Dapper 下划线
- 关于《创业创新执行力》课程中邀请同学参与调查的“无效问卷”补救办法
- POJ1049 Microprocessor Simulation
- GPU和cuda的区别
- java解压报错java.io.IOException: failed to skip current tar entry
- 05.敬业、牺牲与奋斗
- 【附源码】计算机毕业设计SSM校园二手物品交易网站
- ​【原创】基于SSM的校园二手物品交易商城(毕业设计源代码)