Advertisement
clockworksaint

memglitch.py

Jul 7th, 2013
2,215
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.23 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # Copyright 2011-2013, Andrew Wilson
  4. # Licensed under the MIT license:
  5. # http://www.opensource.org/licenses/MIT
  6.  
  7. # memglitch.py
  8.  
  9. from OpenGL import GL
  10. import sys
  11. import pygame
  12. import pygame.image
  13. import pygame.key
  14. import pygame as PG
  15. import numpy
  16. import hashlib
  17. import collections
  18. import ctypes
  19.  
  20.  
  21. ######## SHADERS ########
  22.  
  23. vertex_shader = '''\
  24. #version 330
  25.  
  26. uniform vec2 screen_dimensions;
  27. uniform vec2 cam_position;
  28. uniform float zoom;
  29.  
  30. layout(location=0) in vec2 position;
  31. layout(location=1) in vec2 size;
  32. layout(location=2) in vec2 other;
  33.  
  34. out VertexData
  35. {
  36.   vec2 position;
  37.   vec2 size;
  38.   float layer;
  39.   float rotation;
  40.  
  41. } outData;
  42.  
  43. void main()
  44. {
  45.    outData.position = position;
  46.    outData.size = size;
  47.    outData.rotation = other.x;
  48.    outData.layer = other.y;
  49. }
  50. '''
  51.  
  52. geometry_shader = '''\
  53. #version 330
  54. #extension GL_EXT_gpu_shader4 : enable
  55.  
  56. layout (points) in;
  57. layout (triangle_strip, max_vertices = 4) out;
  58.  
  59. uniform vec2 screen_dimensions;
  60. uniform vec2 cam_position;
  61. uniform float zoom;
  62.  
  63. in VertexData
  64. {
  65.   vec2 position;
  66.   vec2 size;
  67.   float rotation;
  68.   float layer;
  69. } vert[];
  70.  
  71. out FragData
  72. {
  73.    smooth vec2 texcoord;
  74.    smooth float layer;
  75. } vertOut;
  76.  
  77. vec4 calcPosition(in vec2 pos)
  78. {
  79.    // Transform a position in world-space into screen-space
  80.    vec4 result;
  81.    result.xy =
  82.        (
  83.            pos
  84.            - cam_position
  85.        )
  86.        * zoom
  87.        / screen_dimensions;
  88.    result.zw = vec2(0.0, 1.0);
  89.    return result;
  90. }
  91.  
  92. void main()
  93. {
  94.    // Inflate each input point into a quad.
  95.    float r = vert[0].rotation;
  96.    mat2 rotation_matrix = mat2(cos(r), -sin(r), sin(r), cos(r));
  97.    vec2 currentPos;
  98.    vec4 texcoords = vec4(0,0,1,1);
  99.  
  100.    currentPos = vert[0].position + vert[0].size * vec2(-0.5, -0.5) * rotation_matrix;
  101.    gl_Position = calcPosition(currentPos);
  102.    vertOut.texcoord = texcoords.xy;
  103.    vertOut.layer = vert[0].layer;
  104.    gl_PrimitiveID = gl_PrimitiveIDIn;
  105.    EmitVertex();
  106.  
  107.    currentPos = vert[0].position + vert[0].size * vec2(-0.5, 0.5) * rotation_matrix;
  108.    gl_Position = calcPosition(currentPos);
  109.    vertOut.texcoord = texcoords.xw;
  110.    vertOut.layer = vert[0].layer;
  111.    gl_PrimitiveID = gl_PrimitiveIDIn;
  112.    EmitVertex();
  113.  
  114.    currentPos = vert[0].position + vert[0].size * vec2(0.5, -0.5) * rotation_matrix;
  115.    gl_Position = calcPosition(currentPos);
  116.    vertOut.texcoord = texcoords.zy;
  117.    vertOut.layer = vert[0].layer;
  118.    gl_PrimitiveID = gl_PrimitiveIDIn;
  119.    EmitVertex();
  120.  
  121.    currentPos = vert[0].position + vert[0].size * vec2(0.5, 0.5) * rotation_matrix;
  122.    gl_Position = calcPosition(currentPos);
  123.    vertOut.texcoord = texcoords.zw;
  124.    vertOut.layer = vert[0].layer;
  125.    gl_PrimitiveID = gl_PrimitiveIDIn;
  126.    EmitVertex();
  127. }
  128.  
  129. '''
  130.  
  131. fragment_shader = '''\
  132. #version 330
  133. #extension GL_EXT_gpu_shader4 : enable
  134.  
  135. uniform sampler2DArray texture_atlas;
  136.  
  137. uniform float zoom;
  138.  
  139. in FragData
  140. {
  141.    smooth vec2 texcoord;
  142.    smooth float layer;
  143. };
  144.  
  145. out vec4 fragcolor;
  146.  
  147. void main()
  148. {
  149.    fragcolor = texture2DArray(
  150.        texture_atlas,
  151.        vec3(texcoord, float(layer)));
  152. }
  153. '''
  154.  
  155.  
  156. ######## TEXTURE_SETUP ########
  157.  
  158. def make_texture_array(
  159.         image,
  160.         across=8,
  161.         down=8):
  162.     '''
  163.    Split up an input image with a grid and assemble a
  164.    texture array from all of the sub-images.
  165.    '''
  166.  
  167.     source_width, source_height = image.get_size()
  168.     width = source_width // across
  169.     height = source_height // down
  170.     subpixels = []
  171.  
  172.     for y in xrange(down):
  173.         for x in xrange(across):
  174.             subimage = image.subsurface((x*width, y*height, width, height))
  175.             subpixels.append(pygame.image.tostring(subimage, "RGBA", True))
  176.  
  177.     pixels = "".join(subpixels)
  178.  
  179.     texture = GL.glGenTextures(1)
  180.     GL.glBindTexture(GL.GL_TEXTURE_2D_ARRAY, texture)
  181.  
  182.     def tex_param(name, value):
  183.         GL.glTexParameteri(GL.GL_TEXTURE_2D_ARRAY, name, value)
  184.  
  185.     tex_param(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST)
  186.     tex_param(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST)
  187.     tex_param(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE)
  188.     tex_param(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE)
  189.     tex_param(GL.GL_TEXTURE_BASE_LEVEL, 0)
  190.     tex_param(GL.GL_TEXTURE_MAX_LEVEL, 0)
  191.  
  192.     targetformat = GL.GL_RGBA8
  193.     sourceformat = GL.GL_RGBA
  194.  
  195.     GL.glTexImage3D(
  196.         GL.GL_TEXTURE_2D_ARRAY,
  197.         0,
  198.         targetformat,
  199.         width,
  200.         height,
  201.         across*down,
  202.         0,
  203.         sourceformat,
  204.         GL.GL_UNSIGNED_BYTE,
  205.         pixels)
  206.  
  207.     return texture
  208.  
  209.  
  210. ######## SHADER SETUP ########
  211.  
  212. def create_shader_program(resources):
  213.     '''
  214.    Compile the shader program. Populates resources.shader_program_object
  215.    with the OpenGL program object and active_uniforms with a dictionary
  216.    mapping uniform names to locations.
  217.    '''
  218.     writelog=sys.stderr.write
  219.     shaders = []
  220.     def compile_shader(source, gltype, name):
  221.         writelog("Compiling {0} shader...\n".format(name))
  222.         shader = make_shader(gltype, source)
  223.         infolog = GL.glGetShaderInfoLog(shader)
  224.         if len(infolog)==0:
  225.             writelog("...completed\n")
  226.         else:
  227.             writelog("...completed with messages:\n")
  228.             writelog(infolog)
  229.             writelog("\n")
  230.         shaders.append(shader)
  231.     compile_shader(vertex_shader, GL.GL_VERTEX_SHADER, 'vertex')
  232.     compile_shader(fragment_shader, GL.GL_FRAGMENT_SHADER, 'fragment')
  233.     compile_shader(geometry_shader, GL.GL_GEOMETRY_SHADER, 'geometry')
  234.     writelog("Compiling shader program...\n")
  235.     program = make_program(*shaders)
  236.     infolog = GL.glGetProgramInfoLog(program)
  237.     if len(infolog)==0:
  238.         writelog("...completed\n")
  239.     else:
  240.         writelog("...completed with messages:\n")
  241.         writelog(infolog)
  242.         writelog("\n")
  243.  
  244.     active_uniforms = GL.glGetProgramiv(program, GL.GL_ACTIVE_UNIFORMS)
  245.     resources.uniform_locations = {}
  246.     for i in range(active_uniforms):
  247.         name, size, data_type = GL.glGetActiveUniform(program, i)
  248.         resources.uniform_locations[name] = i
  249.     resources.shader_program_object = program
  250.  
  251. def make_shader(shadertype, source):
  252.     '''
  253.    Compile and return an OpenGL shader object.
  254.    '''
  255.     shader = GL.glCreateShader(shadertype)
  256.     GL.glShaderSource(shader, source)
  257.     GL.glCompileShader(shader)
  258.     retval = ctypes.c_uint(GL.GL_UNSIGNED_INT)
  259.     GL.glGetShaderiv(shader, GL.GL_COMPILE_STATUS, retval)
  260.     if not retval:
  261.         print >> sys.stderr, "Failed to compile shader."
  262.         print GL.glGetShaderInfoLog(shader)
  263.         GL.glDeleteShader(shader)
  264.         raise Exception("Failed to compile shader.")
  265.     return shader
  266.  
  267. def make_program(*shaders):
  268.     '''
  269.    Compile and return an OpenGL program object.
  270.    '''
  271.     program = GL.glCreateProgram()
  272.     for shader in shaders:
  273.         GL.glAttachShader(program, shader)
  274.     GL.glLinkProgram(program)
  275.     retval = ctypes.c_int()
  276.     GL.glGetProgramiv(program, GL.GL_LINK_STATUS, retval)
  277.     if not retval:
  278.         print >> sys.stderr, "Failed to link shader program."
  279.         print GL.glGetProgramInfoLog(program)
  280.         GL.glDeleteProgram(program)
  281.         raise Exception("Failed to link shader program.")
  282.     return program
  283.  
  284.  
  285. ######## RESOURCE ALLOCATION ########
  286.  
  287. class Resources(object):
  288.     pass
  289.  
  290. def make_resources(screen_dimensions):
  291.     loadimg = pygame.image.load
  292.     spacemen_image = loadimg('diagnostic_numbers.png')
  293.  
  294.     resources = Resources()
  295.     vertex_dtype = numpy.dtype([
  296.         ("position", ("f4", 2)),
  297.         ("size", ("f4", 2)),
  298.         ("other", ("f4", 2))])
  299.     resources.vertex_stride = 24
  300.     resources.position_stream_offset = 0
  301.     resources.size_stream_offset = 8
  302.     resources.other_stream_offset = 16
  303.     resources.vertex_array = numpy.zeros(512, dtype=vertex_dtype)
  304.     resources.spacemen_texture = make_texture_array(spacemen_image, 16, 16)
  305.  
  306.     create_shader_program(resources)
  307.  
  308.     resources.array_buffer = GL.glGenBuffers(1)
  309.  
  310.     w,h = screen_dimensions
  311.     resources.save_buffer = numpy.zeros((h,w,4),dtype="u1")
  312.  
  313.     return resources
  314.  
  315.  
  316. ######## SCREENSHOT #########
  317.  
  318. # pygame.surfarray.make_surface is broken in 1.9.1. It reads uninitialized
  319. # stack contents on 64-bit systems. :( Here we use numpy to do the copying
  320. # instead.
  321. def make_surface(array):
  322.     w,h,depth = array.shape
  323.     if depth == 4:
  324.         surf = pygame.Surface((w,h), depth=32, flags=pygame.SRCALPHA)
  325.         pixels = pygame.surfarray.pixels3d(surf)
  326.         pixels[:,:,:] = array[:,:,:3]
  327.         alpha = pygame.surfarray.pixels_alpha(surf)
  328.         alpha[:,:] = array[:,:,3]
  329.     elif depth == 3:
  330.         surf = pygame.Surface((w,h), depth=32)
  331.         pixels = pygame.surfarray.pixels3d(surf)
  332.         pixels[:,:,:depth] = array
  333.     else:
  334.         raise ValueError("Array must have minor dimension of 3 or 4.")
  335.     return surf
  336.  
  337. class Screenshotter(object):
  338.     '''
  339.    Captures screenshots from OpenGL and records them by SHA1 hash.
  340.    '''
  341.     def __init__(self, save_buffer, screen_dimensions):
  342.         self.hashes_seen = collections.Counter()
  343.         self.save_buffer = save_buffer
  344.         self.screen_dimensions = screen_dimensions
  345.     def get_filename(self, screen_hash):
  346.         return screen_hash + ".out.png"
  347.     def take_screenshot(self):
  348.         w,h = self.screen_dimensions
  349.         save_buffer = self.save_buffer
  350.         GL.glReadPixels(0, 0, w, h, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, self.save_buffer)
  351.         byte_view = save_buffer.view("u1")
  352.         screen_hash = hashlib.sha1(byte_view).hexdigest()
  353.         if self.hashes_seen[screen_hash] == 0:
  354.             oriented = numpy.swapaxes(save_buffer, 0, 1)[:,::-1,:]
  355.             surf = make_surface(oriented)
  356.             filename = self.get_filename(screen_hash)
  357.             pygame.image.save(surf,filename)
  358.             print filename
  359.         self.hashes_seen[screen_hash] += 1
  360.     def print_summary(self):
  361.         for screen_hash, count in sorted(self.hashes_seen.items(), key=lambda(h,c):-c):
  362.             print "{0} {1}".format(self.get_filename(screen_hash), count)
  363.  
  364.  
  365. ######## RENDERING ########
  366.  
  367. def prepare_context(resources, zoom, screen_dimensions):
  368.     '''
  369.    Prepare the OpenGL context for rendering.
  370.    '''
  371.     uniforms = resources.uniform_locations
  372.     screen_w, screen_h = screen_dimensions
  373.  
  374.     GL.glViewport(0,0,screen_w,screen_h)
  375.  
  376.     GL.glEnable(GL.GL_BLEND)
  377.     GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
  378.  
  379.     GL.glUseProgram(resources.shader_program_object)
  380.  
  381.     GL.glUniform2f(uniforms['cam_position'], 0, 0)
  382.     GL.glUniform1f(uniforms['zoom'], zoom)
  383.     GL.glUniform2f(uniforms['screen_dimensions'], screen_w, screen_h)
  384.     GL.glActiveTexture(GL.GL_TEXTURE0)
  385.     GL.glBindTexture(GL.GL_TEXTURE_2D_ARRAY, resources.spacemen_texture)
  386.     GL.glUniform1i(uniforms['texture_atlas'], 0)
  387.  
  388.     GL.glBindBuffer(GL.GL_ARRAY_BUFFER, resources.array_buffer)
  389.  
  390.     GL.glBufferData(GL.GL_ARRAY_BUFFER, resources.vertex_array.nbytes, resources.vertex_array, GL.GL_STATIC_DRAW)
  391.  
  392.     GL.glEnableVertexAttribArray(0)
  393.     GL.glEnableVertexAttribArray(1)
  394.     GL.glEnableVertexAttribArray(2)
  395.     GL.glVertexAttribPointer(
  396.             0, 2, GL.GL_FLOAT, GL.GL_FALSE, resources.vertex_stride,
  397.             ctypes.cast(resources.position_stream_offset, ctypes.c_void_p))
  398.     GL.glVertexAttribPointer(
  399.             1, 2, GL.GL_FLOAT, GL.GL_FALSE, resources.vertex_stride,
  400.             ctypes.cast(resources.size_stream_offset, ctypes.c_void_p))
  401.     GL.glVertexAttribPointer(
  402.             2, 2, GL.GL_FLOAT, GL.GL_FALSE, resources.vertex_stride,
  403.             ctypes.cast(resources.other_stream_offset, ctypes.c_void_p))
  404.  
  405. def render(resources, zoom, vertex_count):
  406.     '''
  407.    Render one frame.
  408.    '''
  409.     GL.glClearColor(0.4, 0.4, 0.4, 1.0)
  410.     GL.glClear(GL.GL_COLOR_BUFFER_BIT)
  411.     GL.glDrawArrays(
  412.         GL.GL_POINTS,
  413.         0,
  414.         vertex_count)
  415.     pygame.display.flip()
  416.  
  417.  
  418. ######## MAIN LOOP ########
  419.  
  420. def main():
  421.     video_flags = PG.OPENGL|PG.DOUBLEBUF
  422.     pygame.init()
  423.     screen_dimensions = 512, 256
  424.     pygame.display.set_mode(screen_dimensions, video_flags)
  425.     resources = make_resources(screen_dimensions)
  426.     frames = 3000
  427.     done = 0
  428.     zoom = 32.0
  429.     vertex_count = 512
  430.     screenshotter = Screenshotter(resources.save_buffer, screen_dimensions)
  431.     for i in xrange(vertex_count):
  432.         scale = 32.0
  433.         y = (15 - i // 32) / 32.0 * scale - scale/4.0 + (scale/2.0/32.0)
  434.         x = (i % 32) / 32.0 * scale - scale/2.0 + (scale/2.0/32.0)
  435.         xx = i // 2
  436.         lo = xx % 16
  437.         hi = (xx // 16) % 16
  438.         flavour = hi if i%2==0 else lo
  439.         resources.vertex_array[i] =  ((x,y), (1,1), (0, flavour))
  440.  
  441.     prepare_context(resources, zoom, screen_dimensions)
  442.  
  443.     for i in xrange(frames):
  444.         if done:
  445.             break
  446.         if i%100==0:
  447.             print "{0}/{1}".format(i, frames)
  448.         while 1:
  449.             event = pygame.event.poll()
  450.             if event.type == PG.NOEVENT:
  451.                 break
  452.             if event.type == PG.QUIT:
  453.                 done = 1
  454.         render(resources, zoom, vertex_count)
  455.         screenshotter.take_screenshot()
  456.     print "---"
  457.     screenshotter.print_summary()
  458.  
  459. if __name__ == '__main__':
  460.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement