Changeset 238687 in webkit
- Timestamp:
- Nov 29, 2018 1:09:08 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 22 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r238686 r238687 1 2018-11-29 Justin Fan <justin_fan@apple.com> 2 3 [WebGPU] WebGPURenderPassEncoder::setPipeline, draw, and endPass prototypes 4 https://bugs.webkit.org/show_bug.cgi?id=192134 5 6 Reviewed by Dean Jackson. 7 8 Updating the basic rendering test with more commands, and a prototype WPT-based test for WebGPURenderCommandEncoder's 9 new functionality. 10 11 * webgpu/js/basic-webgpu-functions.js: 12 (render): 13 * webgpu/render-command-encoding-expected.txt: Added. 14 * webgpu/render-command-encoding.html: Added. 15 * webgpu/render-passes.html: 16 1 17 2018-11-29 Justin Michaud <justin_michaud@apple.com> 2 18 -
trunk/LayoutTests/webgpu/js/basic-webgpu-functions.js
r238629 r238687 150 150 } 151 151 152 renderPassEncoder.setPipeline(renderPipeline); 153 154 // Note that we didn't attach any buffers - the shader is generating the vertices for us. 155 renderPassEncoder.draw(3, 1, 0, 0); 156 let commandBufferEnd = renderPassEncoder.endPass(); 157 if (!commandBufferEnd) { 158 testFailed("Unable to create WebGPUCommandBuffer from WeGPURenderPassEncoder::endPass!"); 159 return; 160 } 161 152 162 // FIXME: Rest of rendering commands to follow. 153 163 } -
trunk/LayoutTests/webgpu/render-passes.html
r238629 r238687 7 7 window.testRunner.dumpAsText(); 8 8 9 let commandBuffer, texture, textureView, renderPassDescriptor,renderPassEncoder;9 let commandBuffer, renderPassEncoder; 10 10 11 11 function setUpBasicRenderPassEncoder() { 12 12 commandBuffer = defaultDevice.createCommandBuffer(); 13 texture = context.getNextTexture();14 textureView = texture.createDefaultTextureView();13 const texture = context.getNextTexture(); 14 const textureView = texture.createDefaultTextureView(); 15 15 16 16 // FIXME: Flesh out the rest of WebGPURenderPassDescriptor. 17 17 // Default a loadOp, storeOp, and clearColor in the implementation for now. 18 renderPassDescriptor = {18 const renderPassDescriptor = { 19 19 attachment : textureView 20 20 } -
trunk/Source/WebCore/ChangeLog
r238686 r238687 1 2018-11-29 Justin Fan <justin_fan@apple.com> 2 3 [WebGPU] WebGPURenderPassEncoder::setPipeline, draw, and endPass prototypes 4 https://bugs.webkit.org/show_bug.cgi?id=192134 5 6 Reviewed by Dean Jackson. 7 8 Wrap up prototype features for WebGPURenderPassEncoder. 9 10 Test: webgpu/render-command-encoding.html 11 12 * Modules/webgpu/WebGPUCommandBuffer.cpp: 13 (WebCore::WebGPUCommandBuffer::beginRenderPass): 14 * Modules/webgpu/WebGPUDevice.cpp: 15 (WebCore::WebGPUDevice::createRenderPipeline const): 16 * Modules/webgpu/WebGPUProgrammablePassEncoder.cpp: 17 (WebCore::WebGPUProgrammablePassEncoder::WebGPUProgrammablePassEncoder): 18 (WebCore::WebGPUProgrammablePassEncoder::endPass): Returns a reference to the WebGPUCommandBuffer that created this encoder. 19 (WebCore::WebGPUProgrammablePassEncoder::setPipeline): 20 * Modules/webgpu/WebGPUProgrammablePassEncoder.h: Updated to support endPass and setPipeline. 21 * Modules/webgpu/WebGPUProgrammablePassEncoder.idl: 22 * Modules/webgpu/WebGPURenderPassEncoder.cpp: 23 (WebCore::WebGPURenderPassEncoder::create): Must be provided with the WebGPUCommandBuffer. 24 (WebCore::WebGPURenderPassEncoder::WebGPURenderPassEncoder): 25 (WebCore::WebGPURenderPassEncoder::draw): 26 * Modules/webgpu/WebGPURenderPassEncoder.h: Updated to cache a reference to the WebGPUCommandBuffer, and for draw. 27 * Modules/webgpu/WebGPURenderPassEncoder.idl: 28 * Modules/webgpu/WebGPURenderPipeline.cpp: 29 (WebCore::WebGPURenderPipeline::create): 30 (WebCore::WebGPURenderPipeline::WebGPURenderPipeline): 31 * Modules/webgpu/WebGPURenderPipeline.h: 32 (WebCore::WebGPURenderPipeline::renderPipeline): 33 * Modules/webgpu/WebGPUTexture.cpp: 34 * Modules/webgpu/WebGPUTexture.h: Replaced include with forward declaration. 35 * platform/graphics/gpu/GPUProgrammablePassEncoder.h: Updated to support new WebGPU_PassEncoder functionality. 36 * platform/graphics/gpu/GPURenderPassEncoder.h: 37 (WebCore::GPURenderPassEncoder::~GPURenderPassEncoder): Now ends encoding on the MTLCommandEncoder, if not already ended, before freeing it. 38 * platform/graphics/gpu/GPURenderPipeline.h: Now remembers the GPUPrimitiveTopology that it was created with. 39 (WebCore::GPURenderPipeline::primitiveTopology const): 40 * platform/graphics/gpu/cocoa/GPUProgrammablePassEncoderMetal.mm: 41 (WebCore::GPUProgrammablePassEncoder::endPass): Calls endEncoding on the backing MTLCommandEncoder, if not already ended. 42 * platform/graphics/gpu/cocoa/GPURenderPassEncoderMetal.mm: 43 (WebCore::GPURenderPassEncoder::platformPassEncoder const): 44 (WebCore::GPURenderPassEncoder::setPipeline): Added. 45 (WebCore::primitiveTypeForGPUPrimitiveTopology): Added. Helper function to convert GPUPrimitiveTopology to MTLPrimitiveType. 46 (WebCore::GPURenderPassEncoder::draw): Added. Draws using primitive topology specified during pipeline creation. 47 * platform/graphics/gpu/cocoa/GPURenderPipelineMetal.mm: 48 (WebCore::setFunctionsForPipelineDescriptor): 49 (WebCore::GPURenderPipeline::create): 50 (WebCore::GPURenderPipeline::GPURenderPipeline): Now must store the GPURenderPipelineDescriptor for later reference. 51 1 52 2018-11-29 Justin Michaud <justin_michaud@apple.com> 2 53 -
trunk/Source/WebCore/Modules/webgpu/WebGPUCommandBuffer.cpp
r238629 r238687 64 64 return nullptr; 65 65 66 return WebGPURenderPassEncoder::create( encoder.releaseNonNull());66 return WebGPURenderPassEncoder::create(*this, encoder.releaseNonNull()); 67 67 } 68 68 -
trunk/Source/WebCore/Modules/webgpu/WebGPUDevice.cpp
r238451 r238687 118 118 } 119 119 120 return WebGPURenderPipeline::create(m_device->createRenderPipeline(GPURenderPipelineDescriptor { WTFMove(vertexStage), WTFMove(fragmentStage), descriptor.primitiveTopology })); 120 auto pipeline = m_device->createRenderPipeline(GPURenderPipelineDescriptor { WTFMove(vertexStage), WTFMove(fragmentStage), descriptor.primitiveTopology }); 121 122 if (!pipeline) 123 return nullptr; 124 125 return WebGPURenderPipeline::create(pipeline.releaseNonNull()); 121 126 } 122 127 -
trunk/Source/WebCore/Modules/webgpu/WebGPUProgrammablePassEncoder.cpp
r238629 r238687 29 29 #if ENABLE(WEBGPU) 30 30 31 #include "GPUProgrammablePassEncoder.h" 32 #include "WebGPUCommandBuffer.h" 33 #include "WebGPURenderPipeline.h" 34 31 35 namespace WebCore { 36 37 WebGPUProgrammablePassEncoder::WebGPUProgrammablePassEncoder(Ref<WebGPUCommandBuffer>&& creator) 38 : m_commandBuffer(WTFMove(creator)) 39 { 40 } 41 42 Ref<WebGPUCommandBuffer> WebGPUProgrammablePassEncoder::endPass() 43 { 44 passEncoder().endPass(); 45 return m_commandBuffer.copyRef(); 46 } 47 48 void WebGPUProgrammablePassEncoder::setPipeline(Ref<WebGPURenderPipeline>&& pipeline) 49 { 50 passEncoder().setPipeline(pipeline->renderPipeline()); 51 } 32 52 33 53 } // namespace WebCore -
trunk/Source/WebCore/Modules/webgpu/WebGPUProgrammablePassEncoder.h
r238629 r238687 33 33 34 34 class GPUProgrammablePassEncoder; 35 class WebGPUCommandBuffer; 36 class WebGPURenderPipeline; 35 37 36 38 class WebGPUProgrammablePassEncoder : public RefCounted<WebGPUProgrammablePassEncoder> { … … 38 40 virtual ~WebGPUProgrammablePassEncoder() = default; 39 41 42 Ref<WebGPUCommandBuffer> endPass(); 43 void setPipeline(Ref<WebGPURenderPipeline>&&); 44 40 45 protected: 46 WebGPUProgrammablePassEncoder(Ref<WebGPUCommandBuffer>&&); 47 41 48 virtual GPUProgrammablePassEncoder& passEncoder() const = 0; 49 50 private: 51 Ref<WebGPUCommandBuffer> m_commandBuffer; 42 52 }; 43 53 -
trunk/Source/WebCore/Modules/webgpu/WebGPUProgrammablePassEncoder.idl
r238629 r238687 30 30 SkipVTableValidation 31 31 ] interface WebGPUProgrammablePassEncoder { 32 WebGPUCommandBuffer endPass(); 33 34 // FIXME: Only support render pipelines for prototype. 35 void setPipeline(WebGPURenderPipeline pipeline); 32 36 /* Not Yet Implemented 33 WebGPUCommandBuffer endPass();34 37 // Allowed in both compute and render passes 35 38 // TODO: setPushConstants() ? 36 39 void setBindGroup(u32 index, WebGPUBindGroup bindGroup); 37 void setPipeline((WebGPUComputePipeline or WebGPURenderPipeline) pipeline);38 40 */ 39 41 }; -
trunk/Source/WebCore/Modules/webgpu/WebGPURenderPassEncoder.cpp
r238629 r238687 30 30 31 31 #include "GPUProgrammablePassEncoder.h" 32 #include "GPURenderPassEncoder.h" 32 33 33 34 namespace WebCore { 34 35 35 Ref<WebGPURenderPassEncoder> WebGPURenderPassEncoder::create(Ref< GPURenderPassEncoder>&& encoder)36 Ref<WebGPURenderPassEncoder> WebGPURenderPassEncoder::create(Ref<WebGPUCommandBuffer>&& creator, Ref<GPURenderPassEncoder>&& encoder) 36 37 { 37 return adoptRef(*new WebGPURenderPassEncoder(WTFMove( encoder)));38 return adoptRef(*new WebGPURenderPassEncoder(WTFMove(creator), WTFMove(encoder))); 38 39 } 39 40 40 WebGPURenderPassEncoder::WebGPURenderPassEncoder(Ref<GPURenderPassEncoder>&& encoder) 41 : m_passEncoder(WTFMove(encoder)) 41 WebGPURenderPassEncoder::WebGPURenderPassEncoder(Ref<WebGPUCommandBuffer>&& creator, Ref<GPURenderPassEncoder>&& encoder) 42 : WebGPUProgrammablePassEncoder(WTFMove(creator)) 43 , m_passEncoder(WTFMove(encoder)) 42 44 { 45 } 46 47 void WebGPURenderPassEncoder::draw(unsigned long vertexCount, unsigned long instanceCount, unsigned long firstVertex, unsigned long firstInstance) 48 { 49 // FIXME: What kind of validation do we need to handle here? 50 m_passEncoder->draw(vertexCount, instanceCount, firstVertex, firstInstance); 43 51 } 44 52 -
trunk/Source/WebCore/Modules/webgpu/WebGPURenderPassEncoder.h
r238629 r238687 28 28 #if ENABLE(WEBGPU) 29 29 30 #include "GPURenderPassEncoder.h"31 30 #include "WebGPUProgrammablePassEncoder.h" 32 31 … … 36 35 37 36 class GPUProgrammablePassEncoder; 37 class GPURenderPassEncoder; 38 38 39 39 class WebGPURenderPassEncoder final : public WebGPUProgrammablePassEncoder { 40 40 public: 41 static Ref<WebGPURenderPassEncoder> create(Ref<GPURenderPassEncoder>&&); 41 static Ref<WebGPURenderPassEncoder> create(Ref<WebGPUCommandBuffer>&&, Ref<GPURenderPassEncoder>&&); 42 43 void draw(unsigned long, unsigned long, unsigned long, unsigned long); 42 44 43 45 private: 44 WebGPURenderPassEncoder(Ref< GPURenderPassEncoder>&&);46 WebGPURenderPassEncoder(Ref<WebGPUCommandBuffer>&&, Ref<GPURenderPassEncoder>&&); 45 47 46 48 GPUProgrammablePassEncoder& passEncoder() const final; -
trunk/Source/WebCore/Modules/webgpu/WebGPURenderPassEncoder.idl
r238629 r238687 25 25 // https://github.com/gpuweb/gpuweb/blob/master/design/sketch.webidl 26 26 27 typedef unsigned long u32; 28 27 29 [ 28 30 Conditional=WEBGPU, 29 EnabledAtRuntime=WebGPU 31 EnabledAtRuntime=WebGPU, 32 JSGenerateToJSObject 30 33 ] interface WebGPURenderPassEncoder : WebGPUProgrammablePassEncoder { 34 void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance); 35 31 36 /* Not Yet Implemented 32 37 void setBlendColor(float r, float g, float b, float a); … … 34 39 void setVertexBuffers(u32 startSlot, sequence<WebGPUBuffer> buffers, sequence<u32> offsets); 35 40 36 void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance);37 41 void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, i32 baseVertex, u32 firstInstance); 38 42 -
trunk/Source/WebCore/Modules/webgpu/WebGPURenderPipeline.cpp
r237912 r238687 33 33 namespace WebCore { 34 34 35 RefPtr<WebGPURenderPipeline> WebGPURenderPipeline::create(Ref Ptr<GPURenderPipeline>&& pipeline)35 RefPtr<WebGPURenderPipeline> WebGPURenderPipeline::create(Ref<GPURenderPipeline>&& pipeline) 36 36 { 37 if (!pipeline)38 return nullptr;39 40 37 return adoptRef(new WebGPURenderPipeline(WTFMove(pipeline))); 41 38 } 42 39 43 WebGPURenderPipeline::WebGPURenderPipeline(Ref Ptr<GPURenderPipeline>&& pipeline)44 : m_renderPipeline( pipeline)40 WebGPURenderPipeline::WebGPURenderPipeline(Ref<GPURenderPipeline>&& pipeline) 41 : m_renderPipeline(WTFMove(pipeline)) 45 42 { 46 43 UNUSED_PARAM(m_renderPipeline); -
trunk/Source/WebCore/Modules/webgpu/WebGPURenderPipeline.h
r237912 r238687 37 37 class WebGPURenderPipeline : public RefCounted<WebGPURenderPipeline> { 38 38 public: 39 static RefPtr<WebGPURenderPipeline> create(RefPtr<GPURenderPipeline>&&); 39 static RefPtr<WebGPURenderPipeline> create(Ref<GPURenderPipeline>&&); 40 41 Ref<GPURenderPipeline> renderPipeline() { return m_renderPipeline.copyRef(); } 40 42 41 43 private: 42 WebGPURenderPipeline(Ref Ptr<GPURenderPipeline>&&);44 WebGPURenderPipeline(Ref<GPURenderPipeline>&&); 43 45 44 Ref Ptr<GPURenderPipeline> m_renderPipeline;46 Ref<GPURenderPipeline> m_renderPipeline; 45 47 }; 46 48 -
trunk/Source/WebCore/Modules/webgpu/WebGPUTexture.cpp
r238382 r238687 29 29 #if ENABLE(WEBGPU) 30 30 31 #include "GPUTexture.h" 31 32 #include "WebGPUTextureView.h" 32 33 -
trunk/Source/WebCore/Modules/webgpu/WebGPUTexture.h
r238382 r238687 28 28 #if ENABLE(WEBGPU) 29 29 30 #include "GPUTexture.h"31 32 30 #include <wtf/RefCounted.h> 33 31 #include <wtf/RefPtr.h> … … 35 33 namespace WebCore { 36 34 35 class GPUTexture; 37 36 class WebGPUTextureView; 38 37 -
trunk/Source/WebCore/platform/graphics/gpu/GPUProgrammablePassEncoder.h
r238629 r238687 34 34 namespace WebCore { 35 35 36 class GPURenderPipeline; 37 36 38 using PlatformProgrammablePassEncoder = MTLCommandEncoder; 37 39 … … 40 42 virtual ~GPUProgrammablePassEncoder() = default; 41 43 44 void endPass(); 45 46 virtual void setPipeline(Ref<GPURenderPipeline>&&) = 0; 47 42 48 protected: 43 49 virtual PlatformProgrammablePassEncoder* platformPassEncoder() const = 0; 50 51 private: 52 bool m_isEncoding { true }; 44 53 }; 45 54 -
trunk/Source/WebCore/platform/graphics/gpu/GPURenderPassEncoder.h
r238629 r238687 39 39 40 40 class GPUCommandBuffer; 41 class GPURenderPipeline; 41 42 42 43 struct GPURenderPassDescriptor; … … 49 50 static RefPtr<GPURenderPassEncoder> create(const GPUCommandBuffer&, GPURenderPassDescriptor&&); 50 51 52 void setPipeline(Ref<GPURenderPipeline>&&) final; 53 54 void draw(unsigned long, unsigned long, unsigned long, unsigned long); 55 51 56 private: 52 57 GPURenderPassEncoder(PlatformRenderPassEncoderSmartPtr&&); 53 ~GPURenderPassEncoder() ;58 ~GPURenderPassEncoder() { endPass(); } // Ensure that encoding has ended before release. 54 59 55 PlatformProgrammablePassEncoder *platformPassEncoder() const final;60 PlatformProgrammablePassEncoder* platformPassEncoder() const final; 56 61 57 62 PlatformRenderPassEncoderSmartPtr m_platformRenderPassEncoder; 63 RefPtr<GPURenderPipeline> m_pipeline; 58 64 }; 59 65 -
trunk/Source/WebCore/platform/graphics/gpu/GPURenderPipeline.h
r238419 r238687 28 28 #if ENABLE(WEBGPU) 29 29 30 #include "GPURenderPipelineDescriptor.h" 31 30 32 #include <wtf/RefCounted.h> 31 33 #include <wtf/RefPtr.h> … … 38 40 class GPUDevice; 39 41 40 struct GPURenderPipelineDescriptor;41 42 42 using PlatformRenderPipeline = MTLRenderPipelineState; 43 43 using PlatformRenderPipelineSmartPtr = RetainPtr<MTLRenderPipelineState>; 44 using PrimitiveTopology = GPURenderPipelineDescriptor::PrimitiveTopology; 44 45 45 46 class GPURenderPipeline : public RefCounted<GPURenderPipeline> { … … 49 50 PlatformRenderPipeline* platformRenderPipeline() const { return m_platformRenderPipeline.get(); } 50 51 52 PrimitiveTopology primitiveTopology() const { return m_descriptor.primitiveTopology; } 53 51 54 private: 52 GPURenderPipeline(PlatformRenderPipelineSmartPtr&& );55 GPURenderPipeline(PlatformRenderPipelineSmartPtr&&, GPURenderPipelineDescriptor&&); 53 56 54 57 PlatformRenderPipelineSmartPtr m_platformRenderPipeline; 58 GPURenderPipelineDescriptor m_descriptor; 55 59 }; 56 60 -
trunk/Source/WebCore/platform/graphics/gpu/cocoa/GPUProgrammablePassEncoderMetal.mm
r238629 r238687 29 29 #if ENABLE(WEBGPU) 30 30 31 #import <Metal/Metal.h> 32 31 33 namespace WebCore { 34 35 void GPUProgrammablePassEncoder::endPass() 36 { 37 if (!m_isEncoding) 38 return; 39 40 [platformPassEncoder() endEncoding]; 41 m_isEncoding = false; 42 } 32 43 33 44 } // namespace WebCore -
trunk/Source/WebCore/platform/graphics/gpu/cocoa/GPURenderPassEncoderMetal.mm
r238629 r238687 31 31 #import "GPUCommandBuffer.h" 32 32 #import "GPURenderPassDescriptor.h" 33 #import "GPURenderPipeline.h" 33 34 #import "Logging.h" 34 35 … … 69 70 } 70 71 71 GPURenderPassEncoder::~GPURenderPassEncoder()72 {73 // The MTLCommandEncoder must have finished encoding before it can be released.74 // FIXME: Only call this if we have not already ended encoding.75 [m_platformRenderPassEncoder endEncoding];76 }77 78 72 PlatformProgrammablePassEncoder *GPURenderPassEncoder::platformPassEncoder() const 79 73 { … … 81 75 } 82 76 77 void GPURenderPassEncoder::setPipeline(Ref<GPURenderPipeline>&& pipeline) 78 { 79 [m_platformRenderPassEncoder setRenderPipelineState:pipeline->platformRenderPipeline()]; 80 m_pipeline = WTFMove(pipeline); 81 } 82 83 static MTLPrimitiveType primitiveTypeForGPUPrimitiveTopology(PrimitiveTopology type) 84 { 85 switch (type) { 86 case PrimitiveTopology::PointList: 87 return MTLPrimitiveTypePoint; 88 case PrimitiveTopology::LineList: 89 return MTLPrimitiveTypeLine; 90 case PrimitiveTopology::LineStrip: 91 return MTLPrimitiveTypeLineStrip; 92 case PrimitiveTopology::TriangleList: 93 return MTLPrimitiveTypeTriangle; 94 case PrimitiveTopology::TriangleStrip: 95 return MTLPrimitiveTypeTriangleStrip; 96 } 97 } 98 99 void GPURenderPassEncoder::draw(unsigned long vertexCount, unsigned long instanceCount, unsigned long firstVertex, unsigned long firstInstance) 100 { 101 [m_platformRenderPassEncoder 102 drawPrimitives:primitiveTypeForGPUPrimitiveTopology(m_pipeline->primitiveTopology()) 103 vertexStart:firstVertex 104 vertexCount:vertexCount 105 instanceCount:instanceCount 106 baseInstance:firstInstance]; 107 } 108 83 109 } // namespace WebCore 84 110 -
trunk/Source/WebCore/platform/graphics/gpu/cocoa/GPURenderPipelineMetal.mm
r238419 r238687 29 29 #if ENABLE(WEBGPU) 30 30 31 #import "GPURenderPipelineDescriptor.h"32 31 #import "Logging.h" 33 32 … … 37 36 namespace WebCore { 38 37 39 static bool setFunctionsForPipelineDescriptor(const char* const functionName, MTLRenderPipelineDescriptor *mtlDescriptor, GPURenderPipelineDescriptor&& descriptor)38 static bool setFunctionsForPipelineDescriptor(const char* const functionName, MTLRenderPipelineDescriptor *mtlDescriptor, const GPURenderPipelineDescriptor& descriptor) 40 39 { 41 40 #if LOG_DISABLED … … 106 105 } 107 106 108 if (!setFunctionsForPipelineDescriptor(functionName, mtlDescriptor.get(), WTFMove(descriptor)))107 if (!setFunctionsForPipelineDescriptor(functionName, mtlDescriptor.get(), descriptor)) 109 108 return nullptr; 110 109 … … 125 124 } 126 125 127 return adoptRef(new GPURenderPipeline(WTFMove(pipeline) ));126 return adoptRef(new GPURenderPipeline(WTFMove(pipeline), WTFMove(descriptor))); 128 127 } 129 128 130 GPURenderPipeline::GPURenderPipeline(PlatformRenderPipelineSmartPtr&& pipeline )129 GPURenderPipeline::GPURenderPipeline(PlatformRenderPipelineSmartPtr&& pipeline, GPURenderPipelineDescriptor&& descriptor) 131 130 : m_platformRenderPipeline(WTFMove(pipeline)) 131 , m_descriptor(WTFMove(descriptor)) 132 132 { 133 133 }
Note: See TracChangeset
for help on using the changeset viewer.