简述

英文地址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示例解析相关推荐

  1. OpenGL Transform Feedback转换反馈的实例

    OpenGL Transform Feedback转换反馈 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include "vapp.h" ...

  2. OpenGL Transform Feedback

    官网链接 使用TransFrom Feedback捕获图元数据,必须注意的几点: 必须在链接着色器之前设置要捕获的输出变量:glTransformFeedbackVaryings 顶点着色器.细分着色 ...

  3. OpenGL学习笔记 transform feedback缓存粒子系统示例分析

    http://blog.csdn.net/coderling/article/details/44742077 transform feedback是OpenGL中比较酷炫的特性之一,他让客户端应用程 ...

  4. OpenGL Partical System by Transform Feedback

    使用OpenGL的变换反馈(transform feedback)构造粒子系统 粒子系统是三维计算机图形学中用来模拟火,爆炸,雾,雪,流行尾迹或者发光轨迹等视觉效果的技术.粒子系统模拟这些现象的步奏简 ...

  5. NDK OpenGL ES 3.0 开发(七):Transform Feedback

    该原创文章首发于微信公众号:字节流动 什么是 Transform Feedback Transform Feedback(变换反馈)是在 OpenGLES3.0 渲染管线中,顶点处理阶段结束之后,图元 ...

  6. OpenGL ES之变换反馈Transform Feedback的使用流程

    一.什么是 Transform Feedback ? Transform Feedback(变换反馈)是在 OpenGL ES 3.0 渲染管线中,顶点处理阶段结束之后,图元装配和光栅化之前的一个步骤 ...

  7. java聊天程序步骤解析_java网络之基于UDP的聊天程序示例解析

    基于UDP的Socket通信 UDP协议不是一种基于稳定连接的协议,是一种面向数据报包的通信协议,不需要通信双方建立稳定的连接,也没有所谓服务端和客户的概念,数据报包在传输的时候不保证一定及时到达,也 ...

  8. Java 中pdf部分内容加边线_Java 在PDF中添加骑缝章示例解析

    骑缝章是用于往来业务合同,以确保合同真实.有效的印章加盖方法,是一种防范风险的重要方式.在Java程序中,可以通过使用工具来辅助加盖这种骑缝章. 工具:Free Spire.PDF for Java ...

  9. java spring省略jsp,Java +Tomcat + SpringMVC实现页面访问示例解析

    window7下Java环境安装记录: 一.安装Tomcat 1.下载tomcat 7.0,解压,无需安装,放置到目录:D:\apache-tomcat-7.0.90. 2.配置系统环境变量,CATA ...

最新文章

  1. 新版中青——青龙羊毛
  2. 创建交互式shell脚本对话框
  3. C#文件夹权限操作工具类
  4. 代码动态创建checkbox
  5. 浩鲸科技与帆软达成战略合作,重磅推出数据中台联合解决方案
  6. Lua学习笔记5:类及继承的实现
  7. libuv 网络库设计概览译
  8. Java-static-静态方法/全局方法
  9. vue require图片_手把手教你使用require.context实现前端自动化
  10. 磕头如捣的拼音及解释
  11. 满纸辛酸泪 —— 红楼梦中话
  12. 《CLR via C#》精髓:静态类
  13. Tomcat 加载外部dll时如何配置
  14. mybatis查询返回一个三级目录
  15. echarts Map(地图) 波纹数据点
  16. 空格符号复制html,cf空格符号复制(cf空格代码)
  17. AVR32单片机 矩阵按键 按键键值函数解析
  18. BZOJ 3813 奇数国
  19. Foldor for Mac(文件夹图标样式修改工具)
  20. qt夜神android,夜神安卓模拟器命令行整理贴

热门文章

  1. 概念结构设计、逻辑结构设计、物理设计的区分
  2. RestfulToolKit:便捷的 IDEA 接口测试神器
  3. Dapper 下划线
  4. 关于《创业创新执行力》课程中邀请同学参与调查的“无效问卷”补救办法
  5. POJ1049 Microprocessor Simulation
  6. GPU和cuda的区别
  7. java解压报错java.io.IOException: failed to skip current tar entry
  8. 05.敬业、牺牲与奋斗
  9. 【附源码】计算机毕业设计SSM校园二手物品交易网站
  10. ​【原创】基于SSM的校园二手物品交易商城(毕业设计源代码)