用于3D纹理的OpenGL 4.2+和shader_image_load_store不起作用?

问题描述 投票:2回答:2

我试图弄清楚为什么我无法使用(现在内置的)shader_image_load_store扩展名写入3D纹理。

我创建了两个简单的例子(在python中使它更容易):一个写入2D纹理,一个工作,一个写入一个不起作用的3D纹理

(工作)2D版本如下:

#! /usr/bin/env python

from PyQt4 import QtGui, QtCore
from PyQt4.QtOpenGL import *
from OpenGL.GL import *
from OpenGL.GLU import *
import sys

iTexSize = 256


_vsClearSource =  """
    #version 440 compatibility
    void main() {
        gl_Position = ftransform();
        gl_FrontColor = gl_Color;
    }
    """     

_fsClearSource =   """
#version 440 compatibility
uniform int         iPrimitiveCount;
uniform int         iSliceIndex;

layout(size4x32, binding=0) coherent uniform   image2D volColorVolume;
const int iMaxTexSize = 255;  

void main() {
    ivec2 ivecVolumeCoordinate = ivec2(gl_FragCoord.x, gl_FragCoord.y ); //, iSliceIndex);

    vec4 vecVolumeValue =  vec4(0,1,0,1); // vec4(         float(iSlabIndex)/float(iPrimitiveCount)); //,0.0,0.0,0.0);

   imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);                  
   gl_FragData[0] = vec4(1,0,1,1);
}    
    """    

_fsFillSource =   """
#version 440 compatibility
uniform int         iPrimitiveCount;
uniform int         iSliceIndex;

layout(size4x32, binding=0) coherent uniform   image2D volColorVolume;
const int iMaxTexSize = 255;  

void main() {
    ivec2 ivecVolumeCoordinate = ivec2(gl_FragCoord.x, gl_FragCoord.y );

    vec4 vecVolumeValue =  vec4( float(gl_FragCoord.x)  / float(iMaxTexSize)  , float(gl_FragCoord.y)  / float(iMaxTexSize)  , 0 ,  1  );

   imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);                  
   gl_FragData[0] = vec4(1,0,1,1);
}    
"""       



class Viewer3DWidget(QGLWidget):

    def __init__(self, parent):
        QGLWidget.__init__(self, parent)

        self.uWidth = 0
        self.uHeight = 0

        self.texColorTexture = None
        self.fboRendering    = None
        self.texColorVolume = None

        self.vecBackgroundColor = (1.,1.,1.)

        self.vecDrawBuffers = [ GL_COLOR_ATTACHMENT0 , GL_COLOR_ATTACHMENT1 , GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 ]


        self.fSurfacesSpacing = 1.0
        self.fSurfacesTransparency = 1.0






    def initializeGL(self):
        self.shaShaderFill = QGLShaderProgram(self.context())
        self.shaShaderFill.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
        self.shaShaderFill.addShaderFromSourceCode(QGLShader.Fragment, _fsFillSource)   
        self.shaShaderFill.link()      

        self.shaShaderClear = QGLShaderProgram(self.context())
        self.shaShaderClear.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
        self.shaShaderClear.addShaderFromSourceCode(QGLShader.Fragment, _fsClearSource)   
        self.shaShaderClear.link()       



        glClearColor(1.0, 1.0, 1.0, 1.0)
        glClearDepth(1.0)


    def initRenderTargets(self):
        global iTexSize
        if (self.texColorTexture is None):
            self.texColorTexture = glGenTextures( 1 )
        if (self.fboRendering is  None):
            self.fboRendering = glGenFramebuffers(1)               

        glBindTexture( GL_TEXTURE_RECTANGLE, self.texColorTexture )
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA32F, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None)


        glBindTexture(GL_TEXTURE_RECTANGLE, 0);

        glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,  self.texColorTexture, 0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)    


    def deleteRenderTargets(self):
        if (self.fboAccumulation is  not  None):
            glDeleteFramebuffers(1,self.fboRendering)          
            self.fboAccumulation = None
        if (self.texColorTexture is not None):
            glDeleteTextures( self.texColorTexture )
            self.texColorTexture = None


    def initColorVolume(self):
        if (self.texColorVolume is None):
            self.texColorVolume = glGenTextures( 1 )


        glBindTexture( GL_TEXTURE_2D, self.texColorVolume )
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
        #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None);


        glBindTexture(GL_TEXTURE_2D, 0);    



    def fillVolume(self, bClear):
        global iTexSize

        shaShader = self.shaShaderClear
        if(not bClear):
            shaShader = self.shaShaderFill

        if (not self.fboRendering):
            self.initRenderTargets() 

        if (not self.texColorVolume):
            self.initColorVolume()

        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();            



        glBindImageTexture(0,self.texColorVolume,0,GL_FALSE,0,GL_READ_WRITE,GL_RGBA32F);

        glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering);
        glDrawBuffers(1, self.vecDrawBuffers);

        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT);            

        shaShader.bind()

        shaShader.setUniformValue("iPrimitiveCount", iTexSize)  
        shaShader.setUniformValue("volColorVolume", 0) 

        for i in range(iTexSize):


            shaShader.setUniformValue("iSliceIndex", i) 




            glBegin(GL_QUADS);
            glVertex2f(-1.0, -1.0); 
            glVertex2f(1.0, -1.0);
            glVertex2f(1.0, 1.0);
            glVertex2f(-1.0, 1.0);
            glEnd();


            #sync
            glMemoryBarrier(GL_ALL_BARRIER_BITS);


        glBindImageTexture(0,0,0,GL_FALSE,0,GL_READ_WRITE,GL_RGBA32F);
        shaShader.release()

        glBindFramebuffer(GL_FRAMEBUFFER, 0);


    def paintGL(self):
        if  (self.uWidth is 0):
            return           
        if (not self.fboRendering):
            self.initRenderTargets() 
            self.initColorVolume()

        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)


        self.fillVolume(True)
        #draw into the volume
        self.fillVolume(False)

        #slice the volume
        self.displayTexture()



        glFlush()




    def displayTexture(self):   #essentially not useable here
        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        glDisable(GL_BLEND)
        glDisable(GL_DEPTH_TEST);  
        glDisable(GL_LIGHTING)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glColor(1.0, 1.0,1.0)  

        glEnable(GL_TEXTURE_2D); 
        glBindTexture( GL_TEXTURE_2D, self.texColorVolume )

        glBegin(GL_QUADS);
        glTexCoord2f(0,0) #,0.5)
        glVertex2f(-1.0, -1.0); 
        glTexCoord2f(1,0) #,0.5)
        glVertex2f(1.0, -1.0);
        glTexCoord2f(1,1) #,0.5)
        glVertex2f(1.0, 1.0);
        glTexCoord2f(0,1) #,0.5)
        glVertex2f(-1.0, 1.0);
        glEnd();
        glBindTexture( GL_TEXTURE_2D, 0 )



    def resizeGL(self, widthInPixels, heightInPixels):
        if ((widthInPixels is not self.uWidth) or (heightInPixels is not self.uHeight)):        
            self.uWidth = widthInPixels
            self.uHeight = heightInPixels

            glViewport(0, 0, widthInPixels, heightInPixels)
            self.update()


class TestImageLoadStore2D(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.setWindowTitle('TestImageLoadStore2D')
        self.statusBar().showMessage("Hello there")

        exit = QtGui.QAction("Exit", self)
        exit.setShortcut("Ctrl+Q")
        exit.setStatusTip('Exit application')
        self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))


        self.viewer3D = Viewer3DWidget(self)

        self.setCentralWidget(self.viewer3D)

        self.resize(500,500)

    def closeEvent(self, event):
        event.accept()


if __name__ == '__main__':
    # app = QtGui.QApplication(['Python Qt OpenGL Demo'])
    app = QtGui.QApplication(sys.argv)
    window = TestImageLoadStore2D()
    window.show()
    sys.exit(app.exec_())

而非工作3D版本如下:

#! /usr/bin/env python

from PyQt4 import QtGui, QtCore
from PyQt4.QtOpenGL import *
from OpenGL.GL import *
from OpenGL.GLU import *
import sys
iTexSize = 256


_vsClearSource =  """
    #version 440 compatibility
    void main() {
        gl_Position = ftransform();
        gl_FrontColor = gl_Color;
    }
    """     

_fsClearSource =   """
#version 440 compatibility
uniform int         iPrimitiveCount;
uniform int         iSliceIndex;

layout(size4x32, binding=0) writeonly uniform   image3D volColorVolume;
//layout(rgba32f, binding=0) writeonly uniform   image3D volColorVolume;
const int iMaxTexSize = 255;  

void main() {
    ivec3 ivecVolumeCoordinate = ivec3(gl_FragCoord.x, gl_FragCoord.y, 0); //iSliceIndex);

    vec4 vecVolumeValue =  vec4(0,1,0,1); // vec4(         float(iSlabIndex)/float(iPrimitiveCount)); //,0.0,0.0,0.0);

   imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue); 
   memoryBarrier();                 
   gl_FragData[0] = vec4(1,0,1,1);
}    
    """    

_fsFillSource =   """
#version 440 compatibility
uniform int         iPrimitiveCount;
uniform int         iSliceIndex;

layout(size4x32, binding=0) writeonly uniform   image3D volColorVolume;
const int iMaxTexSize = 255;  

void main() {
    ivec3 ivecVolumeCoordinate = ivec3(gl_FragCoord.x, gl_FragCoord.y, 0); //iSliceIndex);

    vec4 vecVolumeValue =  vec4( float(gl_FragCoord.x)  / float(iMaxTexSize)  , float(gl_FragCoord.y)  / float(iMaxTexSize)  , float(iSliceIndex)/float(iPrimitiveCount) ,  1  );

   imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);   
   memoryBarrier();               
   gl_FragData[0] = vec4(1,0,1,1);
}    
"""       



class Viewer3DWidget(QGLWidget):

    def __init__(self, parent):
        QGLWidget.__init__(self, parent)

        self.uWidth = 0
        self.uHeight = 0

        self.texColorTexture = None
        self.fboRendering    = None
        self.texColorVolume = None

        self.vecBackgroundColor = (1.,1.,1.)

        self.vecDrawBuffers = [ GL_COLOR_ATTACHMENT0 , GL_COLOR_ATTACHMENT1 , GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 ]


        self.fSurfacesSpacing = 1.0
        self.fSurfacesTransparency = 1.0
        self.fZCoord = 0.0


    def setZCoordinate(self, fZCoordinate):
        self.fZCoord = fZCoordinate
        self.update()



    def initializeGL(self):
        self.shaShaderFill = QGLShaderProgram(self.context())
        self.shaShaderFill.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
        self.shaShaderFill.addShaderFromSourceCode(QGLShader.Fragment, _fsFillSource)   
        self.shaShaderFill.link()      

        self.shaShaderClear = QGLShaderProgram(self.context())
        self.shaShaderClear.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
        self.shaShaderClear.addShaderFromSourceCode(QGLShader.Fragment, _fsClearSource)   
        self.shaShaderClear.link()       



        glClearColor(1.0, 1.0, 1.0, 1.0)
        glClearDepth(1.0)


    def initRenderTargets(self):
        global iTexSize
        if (self.texColorTexture is None):
            self.texColorTexture = glGenTextures( 1 )
        if (self.fboRendering is  None):
            self.fboRendering = glGenFramebuffers(1)               

        glBindTexture( GL_TEXTURE_RECTANGLE, self.texColorTexture )
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA32F, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None)


        glBindTexture(GL_TEXTURE_RECTANGLE, 0);

        glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,  self.texColorTexture, 0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)    


    def deleteRenderTargets(self):
        if (self.fboAccumulation is  not  None):
            glDeleteFramebuffers(1,self.fboRendering)          
            self.fboAccumulation = None
        if (self.texColorTexture is not None):
            glDeleteTextures( self.texColorTexture )
            self.texColorTexture = None


    def initColorVolume(self):
        if (self.texColorVolume is None):
            self.texColorVolume = glGenTextures( 1 )


        glBindTexture( GL_TEXTURE_3D, self.texColorVolume )
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32F, iTexSize, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None);


        glBindTexture(GL_TEXTURE_3D, 0);    



    def fillVolume(self, bClear):
        global iTexSize

        shaShader = self.shaShaderClear
        if(not bClear):
            shaShader = self.shaShaderFill

        if (not self.fboRendering):
            self.initRenderTargets() 

        if (not self.texColorVolume):
            self.initColorVolume()

        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();            



        glBindImageTexture(0,self.texColorVolume,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA32F);

        glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering);
        glDrawBuffers(1, self.vecDrawBuffers);

        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT);            

        shaShader.bind()

        shaShader.setUniformValue("iPrimitiveCount", iTexSize)  
        shaShader.setUniformValue("volColorVolume", 0) 

        for i in range(iTexSize):


            shaShader.setUniformValue("iSliceIndex", i) 




            glBegin(GL_QUADS);
            glVertex2f(-1.0, -1.0); 
            glVertex2f(1.0, -1.0);
            glVertex2f(1.0, 1.0);
            glVertex2f(-1.0, 1.0);
            glEnd();


            #sync
            glMemoryBarrier(GL_ALL_BARRIER_BITS);
        glMemoryBarrier(GL_ALL_BARRIER_BITS);

        glBindImageTexture(0,0,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA32F);
        shaShader.release()

        glBindFramebuffer(GL_FRAMEBUFFER, 0);


    def paintGL(self):
        if  (self.uWidth is 0):
            return           
        if (not self.fboRendering):
            self.initRenderTargets() 
            self.initColorVolume()

        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)


        self.fillVolume(True)
        #draw into the volume
        #self.fillVolume(False)

        #slice the volume
        self.displayTexture()



        glFlush()




    def displayTexture(self):   #essentially not useable here
        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        glDisable(GL_BLEND)
        glDisable(GL_DEPTH_TEST);  
        glDisable(GL_LIGHTING)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glColor(1.0, 1.0,1.0)  

        glEnable(GL_TEXTURE_3D); 
        glBindTexture( GL_TEXTURE_3D, self.texColorVolume )

        fZCoord = self.fZCoord        

        glBegin(GL_QUADS);
        glTexCoord3f(0,0,fZCoord)
        glVertex2f(-1.0, -1.0); 
        glTexCoord3f(1,0,fZCoord)
        glVertex2f(1.0, -1.0);
        glTexCoord3f(1,1,fZCoord)
        glVertex2f(1.0, 1.0);
        glTexCoord3f(0,1,fZCoord)
        glVertex2f(-1.0, 1.0);
        glEnd();
        glBindTexture( GL_TEXTURE_3D, 0 )



    def resizeGL(self, widthInPixels, heightInPixels):
        if ((widthInPixels is not self.uWidth) or (heightInPixels is not self.uHeight)):        
            self.uWidth = widthInPixels
            self.uHeight = heightInPixels

            glViewport(0, 0, widthInPixels, heightInPixels)
            self.update()





class TestImageLoadStore3D(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.setWindowTitle('TestImageLoadStore3D')
        self.statusBar().showMessage("Hello there")

        exit = QtGui.QAction("Exit", self)
        exit.setShortcut("Ctrl+Q")
        exit.setStatusTip('Exit application')
        self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))


        self.setToolTip('This is a window, or <b>something</b>')

        self.viewer3D = Viewer3DWidget(self)

        parentWidget = QtGui.QWidget()
        slider1 = QtGui.QSlider(QtCore.Qt.Horizontal, None)
        slider1.setRange(0,10000)
        slider1.setValue(5000)
        slider1.setMaximumWidth(120)
        slider1.valueChanged.connect(self.slider1Handler)
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(slider1)
        vbox.addStretch(1)
        self.viewer3D.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding )
        hbox = QtGui.QHBoxLayout()
        hbox.addLayout(vbox)
        hbox.addWidget(self.viewer3D)

        parentWidget.setLayout(hbox)        
        self.setCentralWidget(parentWidget)


        self.resize(500,500)

    def closeEvent(self, event):
        event.accept()
    def slider1Handler(self, iVal):    
        fVal = iVal / 10000.0
        #print "zcoord: ",fVal
        self.viewer3D.setZCoordinate(fVal)           

if __name__ == '__main__':
    # app = QtGui.QApplication(['Python Qt OpenGL Demo'])
    app = QtGui.QApplication(sys.argv)
    window = TestImageLoadStore3D()
    window.show()
    sys.exit(app.exec_())

代码不是额外的整洁,也因为我复制/粘贴/修改了一些较旧的pyopengl代码。问题是用3D纹理写的值绝对没有意义。我使用最新版本的PyOpenGL(实验版),Quadro K5000和最新的驱动程序(332.76)运行它,它们也提供对OpenGL 4.4的支持。

我不确定我可能做错了什么,也因为我没有找到很多写入3D纹理的例子(实际上没有,我也查看了红皮书的最新版本)

有人可以开导我吗?

python opengl glsl gpu-programming opengl-4
2个回答
3
投票

您的问题出在您的碎片着色器中:

layout(size4x32, binding=0) writeonly uniform   image3D volColorVolume;

您通过绑定3D纹理

glBindImageTexture(0,self.texColorVolume,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA32F);

让我引用OpenGL 4.4规范,第8.26节“纹理图像载荷和存储”(强调我的):

如果纹理识别的纹理是一维数组,二维数组,三维立方体图,立方体图数组或二维多重采样数组纹理,则可以绑定整个纹理级别或单个纹理级别纹理层的图层或面。如果layered为TRUE,则绑定整个级别。如果layered为FALSE,则只绑定由layer标识的单个图层。当layered为FALSE时,单个绑定图层将被视为图像访问的不同纹理目标:

  • 一维数组纹理图层被视为一维纹理;
  • 二维数组,三维立方体图,立方体图数组纹理图层被视为二维纹理;和
  • 二维多重采样数组纹理被视为二维多重采样纹理。

所以,如果你只是将一个3D纹理层与layered参数设置为GL_FALSE(正如你现在所做的那样),那么它就好像它只是一个2D纹理,所以要么只是使用image2D并用2D访问它coords,或用layered设置为GL_TRUE和有效的图层范围将它绑定,您可以使用image3D和3维图像坐标将其写入纹理的特定图层/切片。


-3
投票

你在python中有60个分号。 Python不使用分号。事实上,我很确定python是唯一一种实际上不使用分号的编程语言。

© www.soinside.com 2019 - 2024. All rights reserved.